| | |
| | | _config = config ?? throw new ArgumentNullException(nameof(config)); |
| | | |
| | | _mRegion = new MRegion(config.MRegionSize); |
| | | _dbRegion = new DBRegion((ushort)config.DBBlockCount, config.DBBlockSize); |
| | | 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); |
| | |
| | | return regionType switch |
| | | { |
| | | "M" => ReadMRegion<T>(offset), |
| | | "DB" => ReadDBRegion<T>(dbNumber.Value, offset), |
| | | "DB" => ReadDBRegion<T>(dbNumber.Value, offset, bitOffset), |
| | | "I" => ReadIRegion<T>(offset, bitOffset), |
| | | "Q" => ReadQRegion<T>(offset, bitOffset), |
| | | "T" => ReadTRegion<T>(offset), |
| | |
| | | WriteMRegion(offset, value); |
| | | break; |
| | | case "DB": |
| | | WriteDBRegion(dbNumber.Value, offset, value); |
| | | WriteDBRegion(dbNumber.Value, offset, bitOffset, value); |
| | | break; |
| | | case "I": |
| | | WriteIRegion(offset, bitOffset, value); |
| | |
| | | |
| | | return new Dictionary<string, byte[]> |
| | | { |
| | | ["M"] = _mRegion.Read(0, (ushort)_mRegion.Size), |
| | | ["M"] = ReadAllFromRegion(_mRegion, _mRegion.Size), |
| | | ["DB"] = ExportDBRegion(), |
| | | ["I"] = _iRegion.Read(0, (ushort)_iRegion.Size), |
| | | ["Q"] = _qRegion.Read(0, (ushort)_qRegion.Size), |
| | | ["T"] = _tRegion.Read(0, (ushort)_tRegion.Size), |
| | | ["C"] = _cRegion.Read(0, (ushort)_cRegion.Size) |
| | | ["I"] = ReadAllFromRegion(_iRegion, _iRegion.Size), |
| | | ["Q"] = ReadAllFromRegion(_qRegion, _qRegion.Size), |
| | | ["T"] = ReadAllFromRegion(_tRegion, _tRegion.Size), |
| | | ["C"] = ReadAllFromRegion(_cRegion, _cRegion.Size) |
| | | }; |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | |
| | | /// 注意:string类型不受此泛型方法支持(受where T : struct约束) |
| | | /// 请使用WriteBytes/ReadBytes或通过DBRegion直接访问字符串 |
| | | /// </summary> |
| | | private T ReadDBRegion<T>(ushort dbNumber, ushort offset) where T : struct |
| | | 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, 0), |
| | | "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类型不受此泛型方法支持(受where T : struct约束) |
| | | /// 请使用WriteBytes/ReadBytes或通过DBRegion直接访问字符串 |
| | | /// </summary> |
| | | private void WriteDBRegion<T>(ushort dbNumber, ushort offset, T value) where T : struct |
| | | 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, 0, (bool)(object)value); |
| | | _dbRegion.WriteBool(dbNumber, offset, bitOffset ?? 0, (bool)(object)value); |
| | | break; |
| | | case "Int16": |
| | | _dbRegion.WriteInt(dbNumber, offset, (short)(object)value); |
| | |
| | | private byte[] ExportDBRegion() |
| | | { |
| | | var result = new List<byte>(); |
| | | for (ushort i = 1; i <= _config.DBBlockCount; i++) |
| | | foreach (var dbNumber in ResolveConfiguredDbNumbers()) |
| | | { |
| | | try |
| | | { |
| | | var blockData = _dbRegion.Read(i, 0, (ushort)_config.DBBlockSize); |
| | | var blockData = ReadAllFromDbBlock(dbNumber, _config.DBBlockSize); |
| | | result.AddRange(blockData); |
| | | } |
| | | catch (ArgumentException) |
| | |
| | | private void ImportDBRegion(byte[] data) |
| | | { |
| | | int offset = 0; |
| | | for (ushort i = 1; i <= _config.DBBlockCount && offset < data.Length; i++) |
| | | 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(i, 0, blockData); |
| | | _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); |