using System;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using WIDESEAWCS_S7Simulator.Core.Entities;
|
using WIDESEAWCS_S7Simulator.Core.Interfaces;
|
|
namespace WIDESEAWCS_S7Simulator.Core.Memory
|
{
|
/// <summary>
|
/// 内存存储实现
|
/// 管理所有S7 PLC内存区域,提供统一的地址访问接口
|
/// </summary>
|
public class MemoryStore : IMemoryStore
|
{
|
/// <summary>
|
/// 标识对象是否已被释放
|
/// </summary>
|
private bool _disposed = false;
|
|
/// <summary>
|
/// M区(位存储器)
|
/// </summary>
|
private readonly MRegion _mRegion;
|
|
/// <summary>
|
/// DB区(数据块)
|
/// </summary>
|
private readonly DBRegion _dbRegion;
|
|
/// <summary>
|
/// I区(输入区)
|
/// </summary>
|
private readonly IRegion _iRegion;
|
|
/// <summary>
|
/// Q区(输出区)
|
/// </summary>
|
private readonly QRegion _qRegion;
|
|
/// <summary>
|
/// T区(定时器区)
|
/// </summary>
|
private readonly TRegion _tRegion;
|
|
/// <summary>
|
/// C区(计数器区)
|
/// </summary>
|
private readonly CRegion _cRegion;
|
|
/// <summary>
|
/// 内存区域配置
|
/// </summary>
|
private readonly MemoryRegionConfig _config;
|
|
/// <summary>
|
/// 构造函数
|
/// </summary>
|
/// <param name="config">内存区域配置</param>
|
public MemoryStore(MemoryRegionConfig config)
|
{
|
_config = config ?? throw new ArgumentNullException(nameof(config));
|
|
_mRegion = new MRegion(config.MRegionSize);
|
var configuredDbNumbers = ResolveConfiguredDbNumbers();
|
var initialDbNumber = configuredDbNumbers.FirstOrDefault();
|
_dbRegion = new DBRegion(initialDbNumber == 0 ? (ushort)1 : initialDbNumber, config.DBBlockSize);
|
foreach (var dbNumber in configuredDbNumbers)
|
{
|
_dbRegion.EnsureBlock(dbNumber);
|
}
|
_iRegion = new IRegion(config.IRegionSize);
|
_qRegion = new QRegion(config.QRegionSize);
|
_tRegion = new TRegion(config.TRegionCount);
|
_cRegion = new CRegion(config.CRegionCount);
|
}
|
|
/// <summary>
|
/// 读取字节数据
|
/// </summary>
|
/// <param name="address">地址(如 "M100", "DB1.DBD0", "I0.0", "T1")</param>
|
/// <param name="length">长度(必须大于0)</param>
|
/// <returns>字节数组</returns>
|
/// <exception cref="ArgumentException">地址为空或空白,或长度为0</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public byte[] ReadBytes(string address, ushort length)
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
if (string.IsNullOrWhiteSpace(address))
|
throw new ArgumentException("地址不能为空或空白", nameof(address));
|
|
if (length == 0)
|
throw new ArgumentException("长度必须大于0", nameof(length));
|
|
var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address);
|
|
return regionType switch
|
{
|
"M" => _mRegion.Read(offset, length),
|
"DB" => _dbRegion.Read(dbNumber.Value, offset, length),
|
"I" => _iRegion.Read(offset, length),
|
"Q" => _qRegion.Read(offset, length),
|
"T" => throw new NotSupportedException("T区不支持直接读取字节,请使用Read<T>"),
|
"C" => throw new NotSupportedException("C区不支持直接读取字节,请使用Read<T>"),
|
_ => throw new ArgumentException($"不支持的地址区域: {regionType}")
|
};
|
}
|
|
/// <summary>
|
/// 读取指定类型数据
|
/// 支持的类型:bool, short, int, ushort, float
|
/// 注意:string类型不受支持,请使用WriteBytes/ReadBytes或通过DBRegion直接访问
|
/// </summary>
|
/// <typeparam name="T">值类型(bool, short, int, ushort, float)</typeparam>
|
/// <param name="address">地址</param>
|
/// <returns>数据值</returns>
|
/// <exception cref="ArgumentException">地址为空或空白,或类型不支持</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public T Read<T>(string address) where T : struct
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
if (string.IsNullOrWhiteSpace(address))
|
throw new ArgumentException("地址不能为空或空白", nameof(address));
|
|
var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address);
|
|
return regionType switch
|
{
|
"M" => ReadMRegion<T>(offset),
|
"DB" => ReadDBRegion<T>(dbNumber.Value, offset, bitOffset),
|
"I" => ReadIRegion<T>(offset, bitOffset),
|
"Q" => ReadQRegion<T>(offset, bitOffset),
|
"T" => ReadTRegion<T>(offset),
|
"C" => ReadCRegion<T>(offset),
|
_ => throw new ArgumentException($"不支持的地址区域: {regionType}")
|
};
|
}
|
|
/// <summary>
|
/// 写入字节数据
|
/// </summary>
|
/// <param name="address">地址</param>
|
/// <param name="data">数据</param>
|
/// <exception cref="ArgumentException">地址为空或空白</exception>
|
/// <exception cref="ArgumentNullException">数据为null</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public void WriteBytes(string address, byte[] data)
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
if (string.IsNullOrWhiteSpace(address))
|
throw new ArgumentException("地址不能为空或空白", nameof(address));
|
|
if (data == null)
|
throw new ArgumentNullException(nameof(data));
|
|
var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address);
|
|
switch (regionType)
|
{
|
case "M":
|
_mRegion.Write(offset, data);
|
break;
|
case "DB":
|
_dbRegion.Write(dbNumber.Value, offset, data);
|
break;
|
case "I":
|
_iRegion.Write(offset, data);
|
break;
|
case "Q":
|
_qRegion.Write(offset, data);
|
break;
|
case "T":
|
throw new NotSupportedException("T区不支持直接写入字节");
|
case "C":
|
throw new NotSupportedException("C区不支持直接写入字节");
|
default:
|
throw new ArgumentException($"不支持的地址区域: {regionType}");
|
}
|
}
|
|
/// <summary>
|
/// 写入指定类型数据
|
/// 支持的类型:bool, short, int, ushort, float
|
/// 注意:string类型不受支持,请使用WriteBytes/ReadBytes或通过DBRegion直接访问
|
/// </summary>
|
/// <typeparam name="T">值类型(bool, short, int, ushort, float)</typeparam>
|
/// <param name="address">地址</param>
|
/// <param name="value">数据值</param>
|
/// <exception cref="ArgumentException">地址为空或空白,或类型不支持</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public void Write<T>(string address, T value) where T : struct
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
if (string.IsNullOrWhiteSpace(address))
|
throw new ArgumentException("地址不能为空或空白", nameof(address));
|
|
var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address);
|
|
switch (regionType)
|
{
|
case "M":
|
WriteMRegion(offset, value);
|
break;
|
case "DB":
|
WriteDBRegion(dbNumber.Value, offset, bitOffset, value);
|
break;
|
case "I":
|
WriteIRegion(offset, bitOffset, value);
|
break;
|
case "Q":
|
WriteQRegion(offset, bitOffset, value);
|
break;
|
case "T":
|
WriteTRegion(offset, value);
|
break;
|
case "C":
|
WriteCRegion(offset, value);
|
break;
|
default:
|
throw new ArgumentException($"不支持的地址区域: {regionType}");
|
}
|
}
|
|
/// <summary>
|
/// 获取内存区域
|
/// 警告:返回的区域是可变的直接引用,调用者负责生命周期协调。
|
/// 此方法主要用于高级场景和内部使用。一般情况下应使用Read/Write方法。
|
/// </summary>
|
/// <param name="regionType">区域类型(M/DB/I/Q/T/C)</param>
|
/// <returns>内存区域接口</returns>
|
/// <exception cref="ArgumentException">区域类型不支持</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public IMemoryRegion GetRegion(string regionType)
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
return regionType.ToUpper() switch
|
{
|
"M" => _mRegion,
|
"DB" => _dbRegion,
|
"I" => _iRegion,
|
"Q" => _qRegion,
|
"T" => _tRegion,
|
"C" => _cRegion,
|
_ => throw new ArgumentException($"不支持的内存区域: {regionType}")
|
};
|
}
|
|
/// <summary>
|
/// 清空所有内存
|
/// </summary>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public void Clear()
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
_mRegion.Clear();
|
_dbRegion.Clear();
|
_iRegion.Clear();
|
_qRegion.Clear();
|
_tRegion.Clear();
|
_cRegion.Clear();
|
}
|
|
/// <summary>
|
/// 导出内存数据
|
/// 警告:这是一个昂贵的操作,会分配大量内存并复制所有区域数据。
|
/// 建议仅在需要持久化或调试时使用。
|
/// </summary>
|
/// <returns>区域类型 -> 字节数组的字典</returns>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public Dictionary<string, byte[]> Export()
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
return new Dictionary<string, byte[]>
|
{
|
["M"] = ReadAllFromRegion(_mRegion, _mRegion.Size),
|
["DB"] = ExportDBRegion(),
|
["I"] = ReadAllFromRegion(_iRegion, _iRegion.Size),
|
["Q"] = ReadAllFromRegion(_qRegion, _qRegion.Size),
|
["T"] = ReadAllFromRegion(_tRegion, _tRegion.Size),
|
["C"] = ReadAllFromRegion(_cRegion, _cRegion.Size)
|
};
|
}
|
|
/// <summary>
|
/// 导入内存数据
|
/// 会验证数据长度是否匹配区域大小,不匹配时会抛出异常。
|
/// DB区支持自动创建块(如果写入时块不存在)。
|
/// </summary>
|
/// <param name="data">区域类型 -> 字节数组的字典</param>
|
/// <exception cref="ArgumentNullException">数据为null</exception>
|
/// <exception cref="ArgumentException">数据长度不匹配</exception>
|
/// <exception cref="ObjectDisposedException">对象已释放</exception>
|
public void Import(Dictionary<string, byte[]> data)
|
{
|
if (_disposed)
|
throw new ObjectDisposedException(nameof(MemoryStore));
|
|
if (data == null)
|
throw new ArgumentNullException(nameof(data));
|
|
if (data.ContainsKey("M"))
|
{
|
if (data["M"].Length != _config.MRegionSize)
|
throw new ArgumentException($"M区数据长度不匹配:期望{_config.MRegionSize},实际{data["M"].Length}");
|
_mRegion.Write(0, data["M"]);
|
}
|
|
if (data.ContainsKey("DB"))
|
{
|
ImportDBRegion(data["DB"]);
|
}
|
|
if (data.ContainsKey("I"))
|
{
|
if (data["I"].Length != _config.IRegionSize)
|
throw new ArgumentException($"I区数据长度不匹配:期望{_config.IRegionSize},实际{data["I"].Length}");
|
_iRegion.Write(0, data["I"]);
|
}
|
|
if (data.ContainsKey("Q"))
|
{
|
if (data["Q"].Length != _config.QRegionSize)
|
throw new ArgumentException($"Q区数据长度不匹配:期望{_config.QRegionSize},实际{data["Q"].Length}");
|
_qRegion.Write(0, data["Q"]);
|
}
|
|
if (data.ContainsKey("T"))
|
{
|
var expectedTSize = _config.TRegionCount * 2; // 每个定时器2字节
|
if (data["T"].Length != expectedTSize)
|
throw new ArgumentException($"T区数据长度不匹配:期望{expectedTSize},实际{data["T"].Length}");
|
_tRegion.Write(0, data["T"]);
|
}
|
|
if (data.ContainsKey("C"))
|
{
|
var expectedCSize = _config.CRegionCount * 2; // 每个计数器2字节
|
if (data["C"].Length != expectedCSize)
|
throw new ArgumentException($"C区数据长度不匹配:期望{expectedCSize},实际{data["C"].Length}");
|
_cRegion.Write(0, data["C"]);
|
}
|
}
|
|
/// <summary>
|
/// 解析S7地址格式
|
/// 支持格式: M100, DB1.DBD0, I0.0, Q0.0, T1, C1
|
/// </summary>
|
/// <param name="address">地址字符串</param>
|
/// <returns>解析结果:区域类型、偏移量、DB编号(可选)、位偏移(可选)</returns>
|
/// <exception cref="FormatException">地址格式无效时抛出,包含详细的上下文信息</exception>
|
private (string regionType, ushort offset, ushort? dbNumber, byte? bitOffset) ParseAddress(string address)
|
{
|
address = address.Trim().ToUpper();
|
|
try
|
{
|
// DB块地址: DB1.DBD0, DB1.DBW10, DB1.DBB20
|
if (address.StartsWith("DB"))
|
{
|
var parts = address.Split(new[] { '.' }, 2);
|
var dbPart = parts[0]; // DB1
|
var offsetPart = parts.Length > 1 ? parts[1] : null; // DBD0, DBW10, etc.
|
|
var dbNumber = ushort.Parse(dbPart.Substring(2));
|
ushort offset = 0;
|
|
if (offsetPart != null)
|
{
|
// 解析偏移类型: DBD(4字节), DBW(2字节), DBB(1字节)
|
if (offsetPart.StartsWith("DBD"))
|
offset = ushort.Parse(offsetPart.Substring(3));
|
else if (offsetPart.StartsWith("DBW"))
|
offset = ushort.Parse(offsetPart.Substring(3));
|
else if (offsetPart.StartsWith("DBB"))
|
offset = ushort.Parse(offsetPart.Substring(3));
|
else if (offsetPart.StartsWith("DBX"))
|
{
|
var dbxParts = offsetPart.Substring(3).Split('.');
|
offset = ushort.Parse(dbxParts[0]);
|
var bit = dbxParts.Length > 1 ? byte.Parse(dbxParts[1]) : (byte)0;
|
return ("DB", offset, dbNumber, bit);
|
}
|
else
|
offset = ushort.Parse(offsetPart);
|
}
|
|
return ("DB", offset, dbNumber, null);
|
}
|
|
// 带位地址: I0.0, Q0.0, M0.0
|
if (address.Contains("."))
|
{
|
var parts = address.Split('.');
|
var regionType = new string(parts[0].TakeWhile(char.IsLetter).ToArray());
|
var byteOffset = ushort.Parse(new string(parts[0].SkipWhile(char.IsLetter).ToArray()));
|
var bitOffset = byte.Parse(parts[1]);
|
|
return (regionType, byteOffset, null, (byte?)bitOffset);
|
}
|
|
// 定时器/计数器: T1, C1
|
if (address.StartsWith("T") || address.StartsWith("C"))
|
{
|
var regionType = address[0].ToString();
|
var number = ushort.Parse(address.Substring(1));
|
return (regionType, number, null, null);
|
}
|
|
// 普通地址: M100, I100, Q100
|
var region = new string(address.TakeWhile(char.IsLetter).ToArray());
|
var addr = ushort.Parse(new string(address.SkipWhile(char.IsLetter).ToArray()));
|
return (region, addr, null, null);
|
}
|
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
|
{
|
throw new FormatException($"地址格式无效: '{address}'。{ex.Message}", ex);
|
}
|
}
|
|
/// <summary>
|
/// 读取M区数据
|
/// </summary>
|
private T ReadMRegion<T>(ushort offset) where T : struct
|
{
|
var size = System.Runtime.InteropServices.Marshal.SizeOf<T>();
|
var data = _mRegion.Read(offset, (ushort)size);
|
|
if (typeof(T) == typeof(bool))
|
throw new NotSupportedException("M区不支持bool读取,应该使用Mx.x格式(位地址)");
|
|
return ConvertFromBytes<T>(data);
|
}
|
|
/// <summary>
|
/// 读取DB区数据
|
/// 注意:string类型不受此泛型方法支持(受where T : struct约束)
|
/// 请使用WriteBytes/ReadBytes或通过DBRegion直接访问字符串
|
/// </summary>
|
private T ReadDBRegion<T>(ushort dbNumber, ushort offset, byte? bitOffset) where T : struct
|
{
|
return typeof(T).Name switch
|
{
|
"Boolean" => (T)(object)_dbRegion.ReadBool(dbNumber, offset, bitOffset ?? 0),
|
"Int16" => (T)(object)_dbRegion.ReadInt(dbNumber, offset),
|
"Int32" => (T)(object)_dbRegion.ReadDInt(dbNumber, offset),
|
"Single" => (T)(object)_dbRegion.ReadReal(dbNumber, offset),
|
// String类型在泛型约束下不可达,但保留注释说明
|
// "String" => (T)(object)_dbRegion.ReadString(dbNumber, offset, 254),
|
_ => ConvertFromBytes<T>(_dbRegion.Read(dbNumber, offset, (ushort)System.Runtime.InteropServices.Marshal.SizeOf<T>()))
|
};
|
}
|
|
/// <summary>
|
/// 读取I区数据
|
/// </summary>
|
private T ReadIRegion<T>(ushort offset, byte? bitOffset) where T : struct
|
{
|
if (typeof(T) == typeof(bool) && bitOffset.HasValue)
|
return (T)(object)_iRegion.ReadBit(offset, bitOffset.Value);
|
|
var size = System.Runtime.InteropServices.Marshal.SizeOf<T>();
|
var data = _iRegion.Read(offset, (ushort)size);
|
return ConvertFromBytes<T>(data);
|
}
|
|
/// <summary>
|
/// 读取Q区数据
|
/// </summary>
|
private T ReadQRegion<T>(ushort offset, byte? bitOffset) where T : struct
|
{
|
if (typeof(T) == typeof(bool) && bitOffset.HasValue)
|
return (T)(object)_qRegion.ReadBit(offset, bitOffset.Value);
|
|
var size = System.Runtime.InteropServices.Marshal.SizeOf<T>();
|
var data = _qRegion.Read(offset, (ushort)size);
|
return ConvertFromBytes<T>(data);
|
}
|
|
/// <summary>
|
/// 读取T区数据
|
/// </summary>
|
private T ReadTRegion<T>(ushort timerNumber) where T : struct
|
{
|
return (T)(object)_tRegion.ReadTimer(timerNumber);
|
}
|
|
/// <summary>
|
/// 读取C区数据
|
/// </summary>
|
private T ReadCRegion<T>(ushort counterNumber) where T : struct
|
{
|
return (T)(object)_cRegion.ReadCounter(counterNumber);
|
}
|
|
/// <summary>
|
/// 写入M区数据
|
/// </summary>
|
private void WriteMRegion<T>(ushort offset, T value) where T : struct
|
{
|
var data = ConvertToBytes(value);
|
_mRegion.Write(offset, data);
|
}
|
|
/// <summary>
|
/// 写入DB区数据
|
/// 注意:string类型不受此泛型方法支持(受where T : struct约束)
|
/// 请使用WriteBytes/ReadBytes或通过DBRegion直接访问字符串
|
/// </summary>
|
private void WriteDBRegion<T>(ushort dbNumber, ushort offset, byte? bitOffset, T value) where T : struct
|
{
|
switch (typeof(T).Name)
|
{
|
case "Boolean":
|
_dbRegion.WriteBool(dbNumber, offset, bitOffset ?? 0, (bool)(object)value);
|
break;
|
case "Int16":
|
_dbRegion.WriteInt(dbNumber, offset, (short)(object)value);
|
break;
|
case "Int32":
|
_dbRegion.WriteDInt(dbNumber, offset, (int)(object)value);
|
break;
|
case "Single":
|
_dbRegion.WriteReal(dbNumber, offset, (float)(object)value);
|
break;
|
// String类型在泛型约束下不可达,但保留注释说明
|
// case "String":
|
// _dbRegion.WriteString(dbNumber, offset, (string)(object)value, 254);
|
// break;
|
default:
|
var data = ConvertToBytes(value);
|
_dbRegion.Write(dbNumber, offset, data);
|
break;
|
}
|
}
|
|
/// <summary>
|
/// 写入I区数据
|
/// </summary>
|
private void WriteIRegion<T>(ushort offset, byte? bitOffset, T value) where T : struct
|
{
|
if (typeof(T) == typeof(bool) && bitOffset.HasValue)
|
{
|
_iRegion.WriteBit(offset, bitOffset.Value, (bool)(object)value);
|
return;
|
}
|
|
var data = ConvertToBytes(value);
|
_iRegion.Write(offset, data);
|
}
|
|
/// <summary>
|
/// 写入Q区区数据
|
/// </summary>
|
private void WriteQRegion<T>(ushort offset, byte? bitOffset, T value) where T : struct
|
{
|
if (typeof(T) == typeof(bool) && bitOffset.HasValue)
|
{
|
_qRegion.WriteBit(offset, bitOffset.Value, (bool)(object)value);
|
return;
|
}
|
|
var data = ConvertToBytes(value);
|
_qRegion.Write(offset, data);
|
}
|
|
/// <summary>
|
/// 写入T区数据
|
/// </summary>
|
private void WriteTRegion<T>(ushort timerNumber, T value) where T : struct
|
{
|
_tRegion.WriteTimer(timerNumber, (ushort)(object)value);
|
}
|
|
/// <summary>
|
/// 写入C区数据
|
/// </summary>
|
private void WriteCRegion<T>(ushort counterNumber, T value) where T : struct
|
{
|
_cRegion.WriteCounter(counterNumber, (ushort)(object)value);
|
}
|
|
/// <summary>
|
/// 从字节数组转换为值类型
|
/// 使用大端字节序(Big-Endian),与S7 PLC规范一致
|
/// </summary>
|
/// <exception cref="NotSupportedException">类型不支持时抛出</exception>
|
private T ConvertFromBytes<T>(byte[] data) where T : struct
|
{
|
if (typeof(T) == typeof(short))
|
return (T)(object)(short)((data[0] << 8) | data[1]);
|
|
if (typeof(T) == typeof(int))
|
return (T)(object)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]);
|
|
if (typeof(T) == typeof(ushort))
|
return (T)(object)((ushort)((data[0] << 8) | data[1]));
|
|
if (typeof(T) == typeof(uint))
|
return (T)(object)((uint)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]));
|
|
if (typeof(T) == typeof(float))
|
{
|
// BitConverter默认使用系统字节序,需要转换为大端
|
if (BitConverter.IsLittleEndian)
|
Array.Reverse(data);
|
return (T)(object)BitConverter.ToSingle(data, 0);
|
}
|
|
if (typeof(T) == typeof(bool))
|
return (T)(object)(data[0] != 0);
|
|
if (typeof(T) == typeof(byte))
|
return (T)(object)data[0];
|
|
throw new NotSupportedException($"不支持的类型: {typeof(T).Name}。支持的类型:bool, byte, short, int, ushort, uint, float");
|
}
|
|
/// <summary>
|
/// 将值类型转换为字节数组
|
/// 使用大端字节序(Big-Endian),与S7 PLC规范一致
|
/// </summary>
|
/// <exception cref="NotSupportedException">类型不支持时抛出</exception>
|
private byte[] ConvertToBytes<T>(T value) where T : struct
|
{
|
if (typeof(T) == typeof(short))
|
return new[] { (byte)((short)(object)value >> 8), (byte)((short)(object)value & 0xFF) };
|
|
if (typeof(T) == typeof(int))
|
return new[] {
|
(byte)((int)(object)value >> 24),
|
(byte)(((int)(object)value >> 16) & 0xFF),
|
(byte)(((int)(object)value >> 8) & 0xFF),
|
(byte)((int)(object)value & 0xFF)
|
};
|
|
if (typeof(T) == typeof(ushort))
|
return new[] { (byte)((ushort)(object)value >> 8), (byte)((ushort)(object)value & 0xFF) };
|
|
if (typeof(T) == typeof(uint))
|
return new[] {
|
(byte)((uint)(object)value >> 24),
|
(byte)(((uint)(object)value >> 16) & 0xFF),
|
(byte)(((uint)(object)value >> 8) & 0xFF),
|
(byte)((uint)(object)value & 0xFF)
|
};
|
|
if (typeof(T) == typeof(float))
|
{
|
var bytes = BitConverter.GetBytes((float)(object)value);
|
// BitConverter默认使用系统字节序,需要转换为大端
|
if (BitConverter.IsLittleEndian)
|
Array.Reverse(bytes);
|
return bytes;
|
}
|
|
if (typeof(T) == typeof(bool))
|
return new[] { (byte)((bool)(object)value ? 1 : 0) };
|
|
if (typeof(T) == typeof(byte))
|
return new[] { (byte)(object)value };
|
|
throw new NotSupportedException($"不支持的类型: {typeof(T).Name}。支持的类型:bool, byte, short, int, ushort, uint, float");
|
}
|
|
/// <summary>
|
/// 导出DB区数据
|
/// 导出所有DB块的连续数据
|
/// 注意:DB区支持自动创建块,导出时只包含已创建的块
|
/// </summary>
|
private byte[] ExportDBRegion()
|
{
|
var result = new List<byte>();
|
foreach (var dbNumber in ResolveConfiguredDbNumbers())
|
{
|
try
|
{
|
var blockData = ReadAllFromDbBlock(dbNumber, _config.DBBlockSize);
|
result.AddRange(blockData);
|
}
|
catch (ArgumentException)
|
{
|
// DB块不存在,跳过
|
// 填充0以保持连续性
|
result.AddRange(new byte[_config.DBBlockSize]);
|
}
|
}
|
return result.ToArray();
|
}
|
|
/// <summary>
|
/// 导入DB区数据
|
/// 按顺序导入所有DB块
|
/// 注意:DB区支持自动创建块(如果写入时块不存在)
|
/// </summary>
|
private void ImportDBRegion(byte[] data)
|
{
|
int offset = 0;
|
foreach (var dbNumber in ResolveConfiguredDbNumbers())
|
{
|
if (offset >= data.Length)
|
{
|
break;
|
}
|
|
var blockSize = Math.Min(_config.DBBlockSize, data.Length - offset);
|
var blockData = new byte[blockSize];
|
Array.Copy(data, offset, blockData, 0, blockSize);
|
_dbRegion.Write(dbNumber, 0, blockData);
|
offset += blockSize;
|
}
|
}
|
|
/// <summary>
|
/// 按分片读取整个区域,避免长度超过 ushort 导致溢出。
|
/// </summary>
|
private static byte[] ReadAllFromRegion(IMemoryRegion region, int totalSize)
|
{
|
if (totalSize <= 0)
|
{
|
return Array.Empty<byte>();
|
}
|
|
var result = new byte[totalSize];
|
var copied = 0;
|
while (copied < totalSize)
|
{
|
var chunkLength = (ushort)Math.Min(ushort.MaxValue, totalSize - copied);
|
var chunk = region.Read((ushort)copied, chunkLength);
|
Buffer.BlockCopy(chunk, 0, result, copied, chunkLength);
|
copied += chunkLength;
|
}
|
|
return result;
|
}
|
|
/// <summary>
|
/// 按分片读取整个 DB 块,避免 DBBlockSize=65536 时 ushort 转换为 0。
|
/// </summary>
|
private byte[] ReadAllFromDbBlock(ushort dbNumber, int blockSize)
|
{
|
if (blockSize <= 0)
|
{
|
return Array.Empty<byte>();
|
}
|
|
var result = new byte[blockSize];
|
var copied = 0;
|
while (copied < blockSize)
|
{
|
var chunkLength = (ushort)Math.Min(ushort.MaxValue, blockSize - copied);
|
var chunk = _dbRegion.Read(dbNumber, (ushort)copied, chunkLength);
|
Buffer.BlockCopy(chunk, 0, result, copied, chunkLength);
|
copied += chunkLength;
|
}
|
|
return result;
|
}
|
|
/// <summary>
|
/// 释放资源
|
/// </summary>
|
private List<ushort> ResolveConfiguredDbNumbers()
|
{
|
if (_config.DBBlockNumbers != null && _config.DBBlockNumbers.Count > 0)
|
{
|
return _config.DBBlockNumbers
|
.Where(x => x > 0 && x <= ushort.MaxValue)
|
.Distinct()
|
.Select(x => (ushort)x)
|
.ToList();
|
}
|
|
return Enumerable
|
.Range(1, Math.Max(1, _config.DBBlockCount))
|
.Select(x => (ushort)x)
|
.ToList();
|
}
|
|
public void Dispose()
|
{
|
Dispose(true);
|
GC.SuppressFinalize(this);
|
}
|
|
/// <summary>
|
/// 释放资源
|
/// </summary>
|
/// <param name="disposing">是否正在释放托管资源</param>
|
protected virtual void Dispose(bool disposing)
|
{
|
if (!_disposed)
|
{
|
if (disposing)
|
{
|
_mRegion?.Dispose();
|
_dbRegion?.Dispose();
|
_iRegion?.Dispose();
|
_qRegion?.Dispose();
|
_tRegion?.Dispose();
|
_cRegion?.Dispose();
|
}
|
_disposed = true;
|
}
|
}
|
}
|
}
|