wanshenmean
2026-03-13 939e343f3a1fd8f6109b16de3e84bcf6294cea2c
feat: implement MemoryStore with address-based access

- Add IMemoryStore interface with Read/Write/Export/Import methods
- Add MemoryStore implementation aggregating all memory regions
- Support S7 address formats: M100, DB1.DBD0, I0.0, Q0.0, T1, C1
- Add type-safe read/write for basic types (short, int, ushort, float, bool)
- Add memory export/import functionality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
已添加2个文件
672 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IMemoryStore.cs 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MemoryStore.cs 605 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IMemoryStore.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
namespace WIDESEAWCS_S7Simulator.Core.Interfaces
{
    /// <summary>
    /// å†…存存储接口
    /// æä¾›ç»Ÿä¸€çš„内存访问接口,支持地址格式读写
    /// </summary>
    public interface IMemoryStore : IDisposable
    {
        /// <summary>
        /// è¯»å–字节数据
        /// </summary>
        /// <param name="address">地址(如 "M100", "DB1.DBD0", "I0.0", "T1")</param>
        /// <param name="length">长度</param>
        /// <returns>字节数组</returns>
        byte[] ReadBytes(string address, ushort length);
        /// <summary>
        /// è¯»å–指定类型数据
        /// </summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="address">地址</param>
        /// <returns>数据值</returns>
        T Read<T>(string address) where T : struct;
        /// <summary>
        /// å†™å…¥å­—节数据
        /// </summary>
        /// <param name="address">地址</param>
        /// <param name="data">数据</param>
        void WriteBytes(string address, byte[] data);
        /// <summary>
        /// å†™å…¥æŒ‡å®šç±»åž‹æ•°æ®
        /// </summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="address">地址</param>
        /// <param name="value">数据值</param>
        void Write<T>(string address, T value) where T : struct;
        /// <summary>
        /// èŽ·å–å†…å­˜åŒºåŸŸ
        /// </summary>
        /// <param name="regionType">区域类型(M/DB/I/Q/T/C)</param>
        /// <returns>内存区域接口</returns>
        IMemoryRegion GetRegion(string regionType);
        /// <summary>
        /// æ¸…空所有内存
        /// </summary>
        void Clear();
        /// <summary>
        /// å¯¼å‡ºå†…存数据
        /// </summary>
        /// <returns>区域类型 -> å­—节数组的字典</returns>
        Dictionary<string, byte[]> Export();
        /// <summary>
        /// å¯¼å…¥å†…存数据
        /// </summary>
        /// <param name="data">区域类型 -> å­—节数组的字典</param>
        void Import(Dictionary<string, byte[]> data);
    }
}
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MemoryStore.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,605 @@
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);
            _dbRegion = new DBRegion(1, config.DBBlockSize);
            _iRegion = new IRegion(config.IRegionSize);
            _qRegion = new QRegion(config.QRegionSize);
            _tRegion = new TRegion(config.TRegionCount);
            _cRegion = new CRegion(config.CRegionCount);
        }
        /// <summary>
        /// è¯»å–字节数据
        /// </summary>
        public byte[] ReadBytes(string address, ushort length)
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            if (string.IsNullOrEmpty(address))
                throw new ArgumentException("地址不能为空", nameof(address));
            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>
        /// è¯»å–指定类型数据
        /// </summary>
        public T Read<T>(string address) where T : struct
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            if (string.IsNullOrEmpty(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),
                "I" => ReadIRegion<T>(offset, bitOffset),
                "Q" => ReadQRegion<T>(offset, bitOffset),
                "T" => ReadTRegion<T>(offset),
                "C" => ReadCRegion<T>(offset),
                _ => throw new ArgumentException($"不支持的地址区域: {regionType}")
            };
        }
        /// <summary>
        /// å†™å…¥å­—节数据
        /// </summary>
        public void WriteBytes(string address, byte[] data)
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            if (string.IsNullOrEmpty(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>
        /// å†™å…¥æŒ‡å®šç±»åž‹æ•°æ®
        /// </summary>
        public void Write<T>(string address, T value) where T : struct
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            if (string.IsNullOrEmpty(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, 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>
        /// èŽ·å–å†…å­˜åŒºåŸŸ
        /// </summary>
        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>
        public void Clear()
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            _mRegion.Clear();
            _dbRegion.Clear();
            _iRegion.Clear();
            _qRegion.Clear();
            _tRegion.Clear();
            _cRegion.Clear();
        }
        /// <summary>
        /// å¯¼å‡ºå†…存数据
        /// </summary>
        public Dictionary<string, byte[]> Export()
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(MemoryStore));
            return new Dictionary<string, byte[]>
            {
                ["M"] = _mRegion.Read(0, (ushort)_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)
            };
        }
        /// <summary>
        /// å¯¼å…¥å†…存数据
        /// </summary>
        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"))
                _mRegion.Write(0, data["M"]);
            if (data.ContainsKey("DB"))
                ImportDBRegion(data["DB"]);
            if (data.ContainsKey("I"))
                _iRegion.Write(0, data["I"]);
            if (data.ContainsKey("Q"))
                _qRegion.Write(0, data["Q"]);
            if (data.ContainsKey("T"))
                _tRegion.Write(0, data["T"]);
            if (data.ContainsKey("C"))
                _cRegion.Write(0, data["C"]);
        }
        /// <summary>
        /// è§£æžS7地址格式
        /// æ”¯æŒæ ¼å¼: M100, DB1.DBD0, I0.0, Q0.0, T1, C1
        /// </summary>
        private (string regionType, ushort offset, ushort? dbNumber, byte? bitOffset) ParseAddress(string address)
        {
            address = address.Trim().ToUpper();
            // 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
                        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);
        }
        /// <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))
                return default; // M区不支持bool读取,应该用Mx.x格式
            return ConvertFromBytes<T>(data);
        }
        /// <summary>
        /// è¯»å–DB区数据
        /// </summary>
        private T ReadDBRegion<T>(ushort dbNumber, ushort offset) where T : struct
        {
            return typeof(T).Name switch
            {
                "Boolean" => (T)(object)_dbRegion.ReadBool(dbNumber, offset, 0),
                "Int16" => (T)(object)_dbRegion.ReadInt(dbNumber, offset),
                "Int32" => (T)(object)_dbRegion.ReadDInt(dbNumber, offset),
                "Single" => (T)(object)_dbRegion.ReadReal(dbNumber, offset),
                "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区数据
        /// </summary>
        private void WriteDBRegion<T>(ushort dbNumber, ushort offset, T value) where T : struct
        {
            switch (typeof(T).Name)
            {
                case "Boolean":
                    _dbRegion.WriteBool(dbNumber, offset, 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;
                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>
        /// ä»Žå­—节数组转换为值类型
        /// </summary>
        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(float))
                return (T)(object)BitConverter.ToSingle(data, 0);
            return default;
        }
        /// <summary>
        /// å°†å€¼ç±»åž‹è½¬æ¢ä¸ºå­—节数组
        /// </summary>
        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(float))
                return BitConverter.GetBytes((float)(object)value);
            if (typeof(T) == typeof(bool))
                return new[] { (byte)((bool)(object)value ? 1 : 0) };
            throw new NotSupportedException($"不支持的类型: {typeof(T).Name}");
        }
        /// <summary>
        /// å¯¼å‡ºDB区数据
        /// </summary>
        private byte[] ExportDBRegion()
        {
            // ç®€åŒ–版本:导出所有DB块的连续数据
            // å®žé™…实现可能需要更复杂的序列化格式
            var result = new List<byte>();
            for (ushort i = 1; i <= _config.DBBlockCount; i++)
            {
                var blockData = _dbRegion.Read(i, 0, (ushort)_config.DBBlockSize);
                result.AddRange(blockData);
            }
            return result.ToArray();
        }
        /// <summary>
        /// å¯¼å…¥DB区数据
        /// </summary>
        private void ImportDBRegion(byte[] data)
        {
            // ç®€åŒ–版本:按顺序导入所有DB块
            int offset = 0;
            for (ushort i = 1; i <= _config.DBBlockCount && offset < data.Length; i++)
            {
                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);
                offset += blockSize;
            }
        }
        /// <summary>
        /// é‡Šæ”¾èµ„源
        /// </summary>
        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;
            }
        }
    }
}