通过Emit优化Script脚本的解释执行;出于后期更新的方向,暂时隐藏表达式节点、条件表达式节点、全局数据节点;流程图转c#代码新增对于Script脚本的支持,Script脚本现在可以原生导出为C#代码。

This commit is contained in:
fengjiayi
2025-07-26 19:36:54 +08:00
parent 9a8de6b571
commit 29f2be5c80
32 changed files with 2175 additions and 432 deletions

View File

@@ -342,7 +342,7 @@ namespace Serein.Library
args[i] = mainArgTasks[i].Result;
}
// 并发处理 params 参数
// 并发处理 params 类型的入参参数
if (paramsArgs != null)
{
int paramsLength = paramsArgs.Length;

View File

@@ -11,41 +11,81 @@ using static Serein.Library.Utils.EmitHelper;
namespace Serein.Library
{
/// <summary>
/// Emit创建委托描述用于WebApi、WebSocket、NodeFlow动态调用方法的场景
/// 通过 Emit 创建委托,代替反射调用方法,实现高性能的动态调用
/// 一般情况下你无须内部细节,只需要调用 Invoke() 方法即可。
/// </summary>
public class DelegateDetails
{
private readonly EmitType emitType = EmitType.None;
/// <summary>
/// 创建的委托类型
/// </summary>
public enum EmitType
{
/// <summary>
/// 默认
/// </summary>
None,
/// <summary>
/// 方法调用
/// </summary>
MethodInvoke,
/// <summary>
/// 字段赋值
/// </summary>
FieldSetter,
/// <summary>
/// 字段取值
/// </summary>
FieldGetter,
/// <summary>
/// 属性赋值
/// </summary>
PropertySetter,
/// <summary>
/// 属性取值
/// </summary>
PropertyGetter,
/// <summary>
/// 集合取值
/// </summary>
CollectionGetter,
/// <summary>
/// 集合赋值
/// </summary>
CollectionSetter
}
public enum GSType
{
Get,
Set,
}
/// <summary>
/// 根据方法信息构建Emit委托
/// </summary>
/// <param name="methodInfo"></param>
public DelegateDetails(MethodInfo methodInfo)
{
emitType = EmitType.MethodInvoke;
var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var emitDelegate);
_emitMethodInfo = emitMethodType;
_emitDelegate = emitDelegate;
SetFunc();
}
private void SetFunc()
{
methodType = _emitMethodInfo.EmitMethodType;
if (_emitDelegate is Func<object, object[], Task<object>> hasResultTask)
{
this.hasResultTask = hasResultTask;
funcType = 2;
this.methodHasResultTask = hasResultTask;
}
else if (_emitDelegate is Func<object, object[], Task> task)
{
this.task = task;
funcType = 1;
this.methodTask = task;
}
else if (_emitDelegate is Func<object, object[], object> func)
{
this.func = func;
funcType = 0;
this.methodInvoke = func;
}
else
{
@@ -54,9 +94,97 @@ namespace Serein.Library
}
public Func<object, object[], Task<object>> hasResultTask;
public Func<object, object[], Task> task;
public Func<object, object[], object> func;
/// <summary>
/// 根据字段信息构建Emit取/赋值委托
/// </summary>
/// <param name="fieldInfo">字段信息</param>
/// <param name="gsType">是否为 get如果不是则为 set</param>
public DelegateDetails(FieldInfo fieldInfo, GSType gsType)
{
if (gsType == GSType.Get)
{
emitType = EmitType.FieldGetter;
getter = EmitHelper.CreateFieldGetter(fieldInfo);
}
else if (gsType == GSType.Set)
{
emitType = EmitType.FieldSetter;
setter = EmitHelper.CreateFieldSetter(fieldInfo);
}
else
{
throw new NotSupportedException("错误的构建类型");
}
}
/// <summary>
/// 根据字段信息构建Emit取/赋值委托
/// </summary>
/// <param name="propertyInfo">字段信息</param>
/// <param name="gsType">是否为 get如果不是则为 set</param>
public DelegateDetails(PropertyInfo propertyInfo, GSType gsType)
{
if (gsType == GSType.Get)
{
emitType = EmitType.PropertyGetter;
getter = EmitHelper.CreatePropertyGetter(propertyInfo);
}
else if (gsType == GSType.Set)
{
emitType = EmitType.PropertySetter;
setter = EmitHelper.CreatePropertySetter(propertyInfo);
}
else
{
throw new NotSupportedException("错误的构建类型");
}
}
/// <summary>
/// 目前提供了创建集合取值/赋值委托
/// </summary>
/// <param name="type">类型信息</param>
/// <param name="gsType">操作类型</param>
public DelegateDetails(Type type, EmitType emitType)
{
if (emitType == EmitType.CollectionSetter)
{
emitType = EmitType.CollectionSetter;
collectionSetter = EmitHelper.CreateCollectionSetter(type);
}
else if (emitType == EmitType.CollectionGetter)
{
emitType = EmitType.CollectionGetter;
collectionGetter = EmitHelper.CreateCollectionGetter(type);
}
else
{
throw new NotSupportedException("错误的构建类型");
}
}
private Func<object,object, object> collectionGetter= null;
private Action<object,object, object> collectionSetter = null;
private Func<object, object> getter = null;
private Action<object, object> setter = null;
private Func<object, object[], Task<object>> methodHasResultTask = null;
private Func<object, object[], Task> methodTask = null;
private Func<object, object[], object> methodInvoke = null;
/*/// <summary>
@@ -72,12 +200,9 @@ namespace Serein.Library
private Delegate _emitDelegate;
private EmitMethodInfo _emitMethodInfo;
/// <summary>
/// 0是普通
/// 1是异步无返回值
/// 2是异步有返回值
/// </summary>
private int funcType;
private EmitMethodType methodType;
/// <summary>
/// 该Emit委托的相应信息
@@ -96,50 +221,65 @@ namespace Serein.Library
//public EmitMethodType EmitMethodType { get => _emitMethodType; }
public async Task<object> AutoInvokeAsync(object[] args)
{
if (_emitMethodInfo.IsStatic)
{
return await InvokeAsync(null, args);
}
else
{
var obj = Activator.CreateInstance(_emitMethodInfo.DeclaringType);
return await InvokeAsync(obj, args);
}
throw new Exception("Not static method");
}
/// <summary>
/// <para>使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。</para>
/// </summary>
/// <param name="instance">拥有符合委托签名的方法信息的实例</param>
/// <param name="args">如果方法没有入参,也需要传入一个空数组</param>
/// <returns>void方法自动返回null</returns>
/// <param name="instance">拥有符合委托签名的实例</param>
/// <param name="args">如果不需要入参,也需要传入一个空数组,而不能为 null</param>
/// <returns>void方法、setter自动返回null</returns>
public async Task<object> InvokeAsync(object instance, object[] args)
{
if (emitType == EmitType.MethodInvoke)
{
return await MethodInvoke(instance, args);
}
else if (emitType == EmitType.PropertyGetter || emitType == EmitType.FieldGetter)
{
return getter(instance);
}
else if (emitType == EmitType.PropertySetter || emitType == EmitType.FieldSetter)
{
setter(instance, args[0]);
return null;
}
else if (emitType == EmitType.CollectionGetter)
{
return collectionGetter(instance, args[0]);
}
else if (emitType == EmitType.CollectionSetter)
{
collectionSetter(instance, args[0], args[1]);
return null;
}
else
{
throw new NotSupportedException("当前委托类型不支持 InvokeAsync 方法。请使用其他方法调用。");
}
}
private async Task<object> MethodInvoke(object instance, object[] args)
{
if (args is null)
{
args = Array.Empty<object>();
}
if(_emitMethodInfo.IsStatic)
if (_emitMethodInfo.IsStatic)
{
instance = null;
}
object result = null;
if(funcType == 0)
if (methodType == EmitMethodType.Func)
{
result = func.Invoke(instance, args);
result = methodInvoke.Invoke(instance, args);
}
else if (funcType == 2)
else if (methodType == EmitMethodType.TaskHasResult)
{
result = await hasResultTask(instance, args);
}
else if (funcType == 1)
result = await methodHasResultTask(instance, args);
}
else if (methodType == EmitMethodType.Task)
{
await task(instance, args);
await methodTask(instance, args);
result = null;
}
else
@@ -148,5 +288,6 @@ namespace Serein.Library
}
return result;
}
}
}

View File

@@ -35,7 +35,7 @@ namespace Serein.Library
/// <summary>
/// <para>是否为显式参数(固定值/表达式)</para>
/// <para>如果为 true ,则使用UI输入的文本值作为入参数据。</para>
/// <para>如果为 true ,则使用输入的文本值作为入参数据。</para>
/// <para>如果为 false ,则在当前流程上下文中,根据 ArgDataSourceNodeGuid 查找到对应节点,并根据 ArgDataSourceNodeGuid 判断如何获取其返回的数据,以此作为入参数据。</para>
/// </summary>
[PropertyInfo(IsNotification = true)]
@@ -224,23 +224,14 @@ namespace Serein.Library
if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase))
return DataValue.ToConvert(DataType);
/* // 4. 枚举绑定类型
if (ExplicitType is not null && ExplicitType.IsEnum && DataType != ExplicitType)
{
var resultEnum = Enum.Parse(ExplicitType, DataValue);
var boundType = EnumHelper.GetBoundValue(ExplicitType, resultEnum, attr => attr.Value) as Type;
if (boundType != null)
return NodeModel.Env.IOC.CreateObject(boundType);
}*/
// 5. 来自其他节点
// 4. 来自其他节点
object inputParameter = null;
var env = NodeModel.Env;
if (ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
{
var prevNode = context.GetPreviousNode(NodeModel.Guid);
inputParameter = prevNode != null ? context.GetFlowData(prevNode)?.Value : null;
var prevNodeGuid = context.GetPreviousNode(NodeModel.Guid);
inputParameter = prevNodeGuid != null ? context.GetFlowData(prevNodeGuid)?.Value : null;
}
else
{
@@ -250,7 +241,7 @@ namespace Serein.Library
if (!env.TryGetNodeModel(ArgDataSourceNodeGuid, out var sourceNode))
throw new Exception($"[arg{Index}] 节点[{ArgDataSourceNodeGuid}]不存在");
if (sourceNode.IsPublic
if (sourceNode.IsPublic // 如果运行上一节点是[FlowCall]节点(则从该节点获取入参)
&& env.TryGetNodeModel(prevNodeGuid, out var prevNode)
&& prevNode.ControlType == NodeControlType.FlowCall
&& env.TryGetNodeModel(context.GetPreviousNode(NodeModel.Guid), out var sourceNodeTemp)
@@ -259,13 +250,13 @@ namespace Serein.Library
if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData)
inputParameter = context.GetFlowData(sourceNode.Guid)?.Value;
else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) // 立刻执行目标节点获取参数
inputParameter = (await sourceNode.ExecutingAsync(context, CancellationToken.None)).Value;
else
throw new Exception("无效的 ArgDataSourceType");
}
// 6. 表达式处理
// 5. 表达式处理
if (IsExplicitData && DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase))
{
var lower = DataValue.ToLowerInvariant();
@@ -275,7 +266,7 @@ namespace Serein.Library
}
}
// 7. 类型转换
// 6. 类型转换
if (!DataType.IsValueType && inputParameter is null)
throw new Exception($"[arg{Index}] 参数不能为null");

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
public static class HexExtensions
{
public static string ToHexString(this byte[] data, string separator = " ")
{
if (data == null) return string.Empty;
return BitConverter.ToString(data).Replace("-", separator);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
/// <summary>
/// Modbus 客户端通用接口 (TCP/RTU 通用)
/// </summary>
public interface IModbusClient : IDisposable
{
/// <summary>
/// 报文发送时
/// </summary>
Action<byte[]> OnTx { get; set; }
/// <summary>
/// 接收到报文时
/// </summary>
Action<byte[]> OnRx { get; set; }
/// <summary>
/// 读取线圈状态 (0x01)
/// </summary>
Task<bool[]> ReadCoils(ushort startAddress, ushort quantity);
/// <summary>
/// 读取离散输入状态 (0x02)
/// </summary>
Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity);
/// <summary>
/// 读取保持寄存器 (0x03)
/// </summary>
Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity);
/// <summary>
/// 读取输入寄存器 (0x04)
/// </summary>
Task<ushort[]> ReadInputRegisters(ushort startAddress, ushort quantity);
/// <summary>
/// 写单个线圈 (0x05)
/// </summary>
Task WriteSingleCoil(ushort address, bool value);
/// <summary>
/// 写单个寄存器 (0x06)
/// </summary>
Task WriteSingleRegister(ushort address, ushort value);
/// <summary>
/// 写多个线圈 (0x0F)
/// </summary>
Task WriteMultipleCoils(ushort startAddress, bool[] values);
/// <summary>
/// 写多个寄存器 (0x10)
/// </summary>
Task WriteMultipleRegisters(ushort startAddress, ushort[] values);
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
public static class ModbusClientFactory
{
private static readonly char[] separator = new[] { ':' };
/// <summary>
/// 创建 Modbus 客户端实例
/// </summary>
/// <param name="connectionString">
/// 连接字符串格式:
/// TCP示例"tcp:192.168.1.100:502"
/// UCP示例"ucp:192.168.1.100:502"
/// RTU示例"rtu:COM3:9600:1" 格式rtu:串口名:波特率:从站地址)
/// </param>
public static IModbusClient Create(string connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentException("connectionString 不能为空");
var parts = connectionString.Split(separator, StringSplitOptions.RemoveEmptyEntries);
//var parts = connectionString.Split(':',options: StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
throw new ArgumentException("connectionString 格式错误");
var protocol = parts[0].ToLower();
if (protocol == "tcp")
{
// tcp:host:port
if (parts.Length < 3)
throw new ArgumentException("TCP格式应为 tcp:host:port");
string host = parts[1];
if (!int.TryParse(parts[2], out int port))
port = 502; // 默认端口
return new ModbusTcpClient(host, port);
}
else if (protocol == "ucp")
{
// ucp:host:port
if (parts.Length < 3)
throw new ArgumentException("TCP格式应为 tcp:host:port");
string host = parts[1];
if (!int.TryParse(parts[2], out int port))
port = 502; // 默认端口
return new ModbusUdpClient(host, port);
}
else if (protocol == "rtu")
{
// rtu:portName:baudRate:slaveId
if (parts.Length < 4)
throw new ArgumentException("RTU格式应为 rtu:portName:baudRate:slaveId");
string portName = parts[1];
if (!int.TryParse(parts[2], out int baudRate))
baudRate = 9600;
if (!byte.TryParse(parts[3], out byte slaveId))
slaveId = 1;
return new ModbusRtuClient(portName, baudRate, slaveId: slaveId);
}
else
{
throw new NotSupportedException($"不支持的协议类型: {protocol}");
}
}
/// <summary>
/// 创建 Modbus TCP 客户端
/// </summary>
/// <param name="host">服务器地址</param>
/// <param name="port">端口默认502</param>
public static ModbusTcpClient CreateTcpClient(string host, int port = 502)
{
return new ModbusTcpClient(host, port);
}
/// <summary>
/// 创建 Modbus TCP 客户端
/// </summary>
/// <param name="host">服务器地址</param>
/// <param name="port">端口默认502</param>
public static ModbusUdpClient CreateUdpClient(string host, int port = 502)
{
return new ModbusUdpClient(host, port);
}
/// <summary>
/// 创建 Modbus RTU 客户端
/// </summary>
/// <param name="portName">串口名,比如 "COM3"</param>
/// <param name="baudRate">波特率默认9600</param>
/// <param name="parity">校验默认None</param>
/// <param name="dataBits">数据位默认8</param>
/// <param name="stopBits">停止位默认1</param>
/// <param name="slaveId">从站地址默认1</param>
public static ModbusRtuClient CreateRtuClient(string portName,
int baudRate = 9600,
Parity parity = Parity.None,
int dataBits = 8,
StopBits stopBits = StopBits.One,
byte slaveId = 1)
{
return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, slaveId);
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
public class ModbusException : Exception
{
public byte FunctionCode { get; }
public byte ExceptionCode { get; }
public ModbusException(byte functionCode, byte exceptionCode)
: base($"Modbus异常码=0x{functionCode:X2}0x{exceptionCode:X2}{GetExceptionMessage(exceptionCode)})")
{
FunctionCode = functionCode;
ExceptionCode = exceptionCode;
}
private static string GetExceptionMessage(byte code) => code switch
{
0x01 => "非法功能。确认功能码是否被目标设备支持;检查设备固件版本是否过低;修改主站请求为设备支持的功能码", // 功能码错误
0x02 => "非法数据地址。检查主站请求的寄存器地址和长度是否越界;确保设备配置的寄存器数量正确", // 数据地址错误
0x03 => "非法数据值。检查写入的数值是否在设备支持的范围内;核对协议文档中对应寄存器的取值要求", // 数据值错误
0x04 => "从站设备故障。检查设备运行状态和日志;尝试重启设备;排查硬件或内部程序错误", // 从设备故障
0x05 => "确认。主站需通过轮询或延时机制等待处理完成,再次查询结果", // 确认
0x06 => "从站设备忙。增加请求重试延时;避免高频率发送编程指令", // 从设备忙
0x08 => "存储奇偶性差错。尝试重新发送请求;如错误持续出现,检查存储器硬件或文件一致性", // 内存奇偶校验错误
0x0A => "不可用网关路径。检查网关配置和负载;确认目标设备的网络连接可用性", // 网关路径不可用
0x0B => "网关目标设备响应失败。检查目标设备是否在线;检查网关的路由配置与网络连接", // 网关目标设备未响应
_ => $"未知错误" // 未知错误
};
}
}

View File

@@ -0,0 +1,25 @@
using Serein.Library.Network.Modbus;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
public class ModbusRequest
{
/// <summary>
/// 功能码
/// </summary>
public ModbusFunctionCode FunctionCode { get; set; }
/// <summary>
/// PDU (Protocol Data Unit) 数据不包括从站地址和CRC
/// </summary>
public byte[] PDU { get; set; }
/// <summary>
/// 异步任务完成源,用于等待响应
/// </summary>
public TaskCompletionSource<byte[]> Completion { get; set; }
}
}

View File

@@ -0,0 +1,323 @@
using Serein.Library.Network.Modbus;
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
public class ModbusRtuClient : IModbusClient
{
public Action<byte[]> OnTx { get; set; }
public Action<byte[]> OnRx { get; set; }
private readonly SerialPort _serialPort;
private readonly SemaphoreSlim _requestLock = new SemaphoreSlim(1, 1);
private readonly byte _slaveId;
private readonly CancellationTokenSource _cts = new();
public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One, byte slaveId = 1)
{
_slaveId = slaveId;
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = 1000,
WriteTimeout = 1000
};
_serialPort.Open();
}
#region
public async Task<bool[]> ReadCoils(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var response = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
return ParseDiscreteBits(response, quantity);
}
public async Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var response = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
return ParseDiscreteBits(response, quantity);
}
public async Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var response = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
return ParseRegisters(response, quantity);
}
public async Task<ushort[]> ReadInputRegisters(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var response = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
return ParseRegisters(response, quantity);
}
public async Task WriteSingleCoil(ushort address, bool value)
{
var pdu = new byte[]
{
(byte)(address >> 8),
(byte)(address & 0xFF),
value ? (byte)0xFF : (byte)0x00,
0x00
};
await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
}
public async Task WriteSingleRegister(ushort address, ushort value)
{
var pdu = new byte[]
{
(byte)(address >> 8),
(byte)(address & 0xFF),
(byte)(value >> 8),
(byte)(value & 0xFF)
};
await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
}
public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
{
if (values == null || values.Length == 0)
throw new ArgumentException("values 不能为空");
int byteCount = (values.Length + 7) / 8; // 需要多少字节
byte[] coilData = new byte[byteCount];
for (int i = 0; i < values.Length; i++)
{
if (values[i])
coilData[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位
}
var pdu = new List<byte>
{
(byte)(startAddress >> 8), // 起始地址高字节
(byte)(startAddress & 0xFF), // 起始地址低字节
(byte)(values.Length >> 8), // 数量高字节
(byte)(values.Length & 0xFF), // 数量低字节
(byte)coilData.Length // 数据字节数
};
pdu.AddRange(coilData);
await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
}
public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
{
if (values == null || values.Length == 0)
throw new ArgumentException("values 不能为空");
var arrlen = 5 + values.Length * 2;
var pdu = new byte[arrlen];
pdu[0] = (byte)(startAddress >> 8); // 起始地址高字节
pdu[1] = (byte)(startAddress & 0xFF); // 起始地址低字节
pdu[2] = (byte)(values.Length >> 8); // 寄存器数量高字节
pdu[3] = (byte)(values.Length & 0xFF); // 寄存器数量低字节
pdu[4] = (byte)(values.Length * 2); // 数据字节数
// 添加寄存器数据(每个寄存器 2 字节:高字节在前)
var index = 5;
foreach(var val in values)
{
pdu[index++] = (byte)(val >> 8);
pdu[index++] = (byte)(val & 0xFF);
}
/* var pdu = new List<byte>
{
(byte)(startAddress >> 8), // 起始地址高字节
(byte)(startAddress & 0xFF), // 起始地址低字节
(byte)(values.Length >> 8), // 寄存器数量高字节
(byte)(values.Length & 0xFF), // 寄存器数量低字节
(byte)(values.Length * 2) // 数据字节数
};
foreach (var val in values)
{
pdu.Add((byte)(val >> 8));
pdu.Add((byte)(val & 0xFF));
}*/
await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu);
}
#endregion
#region
public async Task<byte[]> SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
{
await _requestLock.WaitAsync();
try
{
// 构造 RTU 帧
byte[] frame = BuildFrame(_slaveId, (byte)functionCode, pdu);
OnTx?.Invoke(frame); // 触发发送日志
await _serialPort.BaseStream.WriteAsync(frame, 0, frame.Length, _cts.Token);
await _serialPort.BaseStream.FlushAsync(_cts.Token);
// 接收响应
var response = await ReceiveResponseAsync();
OnRx?.Invoke(response); // 触发接收日志
// 检查功能码是否异常响应
if ((response[1] & 0x80) != 0)
{
byte exceptionCode = response[2];
throw new ModbusException(response[1], exceptionCode);
}
return response;
}
finally
{
_requestLock.Release();
}
}
/// <summary>
/// 接收响应
/// </summary>
private async Task<byte[]> ReceiveResponseAsync()
{
var buffer = new byte[256];
int offset = 0;
while (true)
{
int read = await _serialPort.BaseStream.ReadAsync(buffer, offset, buffer.Length - offset, _cts.Token);
offset += read;
// 最小RTU帧地址(1) + 功能码(1) + 数据(N) + CRC(2)
if (offset >= 5)
{
int frameLength = offset;
if (!ValidateCrc(buffer, 0, frameLength))
throw new IOException("CRC 校验失败");
byte[] response = new byte[frameLength - 2];
Array.Copy(buffer, 0, response, 0, frameLength - 2);
return response;
}
}
}
private byte[] BuildFrame(byte slaveAddr, byte functionCode, byte[] pdu)
{
var frame = new byte[2 + pdu.Length + 2]; // 地址 + 功能码 + PDU + CRC
frame[0] = slaveAddr;
frame[1] = functionCode;
Array.Copy(pdu, 0, frame, 2, pdu.Length);
ushort crc = Crc16(frame, 0, frame.Length - 2);
frame[frame.Length - 2] = (byte)(crc & 0xFF);
frame[frame.Length - 1] = (byte)(crc >> 8);
return frame;
}
private static bool ValidateCrc(byte[] buffer, int offset, int length)
{
ushort crcCalc = Crc16(buffer, offset, length - 2);
ushort crcRecv = (ushort)(buffer[length - 2] | (buffer[length - 1] << 8));
return crcCalc == crcRecv;
}
#endregion
#region PDU与解析
private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
{
byte[] buffer = new byte[4];
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节
return buffer;
}
private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
{
int byteCount = pdu[2]; // 第2字节是后续的字节数量
int dataIndex = 3; // 数据从第3字节开始0-based
var result = new bool[count];
for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
{
if (bitPos == 8)
{
bitPos = 0;
bytePos++;
}
result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
}
return result;
}
private ushort[] ParseRegisters(byte[] pdu, ushort count)
{
var result = new ushort[count];
int dataStart = 3; // 数据从第3字节开始
for (int i = 0; i < count; i++)
{
int offset = dataStart + i * 2;
result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
}
return result;
}
#endregion
#region CRC16
private static ushort Crc16(byte[] data, int offset, int length)
{
const ushort polynomial = 0xA001;
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
crc = (ushort)((crc >> 1) ^ polynomial);
else
crc >>= 1;
}
}
return crc;
}
#endregion
public void Dispose()
{
_cts.Cancel();
_serialPort?.Close();
}
}
}

View File

@@ -0,0 +1,19 @@
using Serein.Library.Network.Modbus;
namespace Serein.Library.Network.Modbus
{
/// <summary>
/// Modbus RTU 请求实体(串口模式下无效)
/// </summary>
public sealed class ModbusRtuRequest : ModbusRequest
{
/// <summary>
/// 从站地址1~247
/// </summary>
public byte SlaveAddress { get; set; }
}
}

View File

@@ -4,19 +4,21 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Channels;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
namespace Serein.Library.Network.Modbus
{
/// <summary>
/// Modbus TCP 客户端
/// </summary>
public class ModbusTcpClient : IDisposable
public class ModbusTcpClient : IModbusClient
{
public Action<byte[]> OnTx { get; set; }
public Action<byte[]> OnRx { get; set; }
/// <summary>
/// 消息通道
/// </summary>
@@ -264,10 +266,15 @@ namespace Serein.Library.Network.Modbus
/// <returns></returns>
private async Task ProcessQueueAsync()
{
var request = await _channel.Reader.ReadAsync();
byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
await _stream.WriteAsync(packet, 0, packet.Length);
await _stream.FlushAsync();
while (_tcpClient.Connected)
{
var request = await _channel.Reader.ReadAsync();
byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
OnTx?.Invoke(packet); // 触发发送日志
await _stream.WriteAsync(packet, 0, packet.Length);
await _stream.FlushAsync();
}
}
/// <summary>
@@ -275,6 +282,8 @@ namespace Serein.Library.Network.Modbus
/// </summary>
/// <param name="functionCode">功能码</param>
/// <param name="pdu">内容</param>
/// <param name="timeout">超时时间</param>
/// <param name="maxRetries">最大重发次数</param>
/// <returns></returns>
/// <exception cref="TimeoutException"></exception>
public Task<byte[]> SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
@@ -305,12 +314,8 @@ namespace Serein.Library.Network.Modbus
var buffer = new byte[1024];
while (true)
{
#if NET462
int len = await _stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
#else
int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
#endif
//int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
if (len == 0) return; // 连接关闭
if (len < 6)
@@ -320,6 +325,7 @@ namespace Serein.Library.Network.Modbus
}
ushort protocolId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(2, 2));
if (protocolId != 0x0000)
{
@@ -339,7 +345,22 @@ namespace Serein.Library.Network.Modbus
if (_pendingRequests.TryRemove(transactionId, out var tcs))
{
var responsePdu = new ReadOnlySpan<byte>(buffer, 6, dataLength).ToArray();
tcs.SetResult(responsePdu); // 如需 byte[] 则 ToArray
if (OnRx is not null)
{
var packet = new ReadOnlySpan<byte>(buffer, 0, 6 + dataLength).ToArray();
OnRx?.Invoke(packet); // 触发接收日志
}
// 检查是否异常响应
if ((responsePdu[1] & 0x80) != 0)
{
byte exceptionCode = responsePdu[2];
tcs.SetException(new ModbusException(responsePdu[1], exceptionCode));
}
else
{
tcs.SetResult(responsePdu);
}
}
else
{
@@ -400,4 +421,6 @@ namespace Serein.Library.Network.Modbus
_tcpClient?.Close();
}
}
}

View File

@@ -1,32 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Network.Modbus
{
/// <summary>
/// Modbus TCP 请求实体
/// </summary>
public class ModbusTcpRequest
public class ModbusTcpRequest : ModbusRequest
{
/// <summary>
/// 事务ID
/// </summary>
public ushort TransactionId { get; set; }
/// <summary>
/// 功能码
/// </summary>
public ModbusFunctionCode FunctionCode { get; set; }
/// <summary>
/// PDU 数据
/// </summary>
public byte[] PDU { get; set; }
/// <summary>
/// 请求的完成源,用于异步等待响应
/// </summary>
public TaskCompletionSource<byte[]> Completion { get; set; }
}
}
}

View File

@@ -0,0 +1,260 @@
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Threading;
namespace Serein.Library.Network.Modbus
{
public class ModbusUdpClient : IModbusClient
{
public Action<byte[]> OnTx { get; set; }
public Action<byte[]> OnRx { get; set; }
private readonly Channel<ModbusTcpRequest> _channel = Channel.CreateUnbounded<ModbusTcpRequest>();
private readonly UdpClient _udpClient;
private readonly IPEndPoint _remoteEndPoint;
private readonly ConcurrentDictionary<ushort, TaskCompletionSource<byte[]>> _pendingRequests = new();
private int _transactionId = 0;
public ModbusUdpClient(string host, int port = 502)
{
_remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
_udpClient = new UdpClient();
_udpClient.Connect(_remoteEndPoint);
_ = ProcessQueueAsync();
_ = ReceiveLoopAsync();
}
#region
public async Task<bool[]> ReadCoils(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
return ParseDiscreteBits(responsePdu, quantity);
}
public async Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
return ParseDiscreteBits(responsePdu, quantity);
}
public async Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
return ParseRegisters(responsePdu, quantity);
}
public async Task<ushort[]> ReadInputRegisters(ushort startAddress, ushort quantity)
{
var pdu = BuildReadPdu(startAddress, quantity);
var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
return ParseRegisters(responsePdu, quantity);
}
public async Task WriteSingleCoil(ushort address, bool value)
{
var pdu = new byte[]
{
(byte)(address >> 8),
(byte)(address & 0xFF),
value ? (byte)0xFF : (byte)0x00,
0x00
};
await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
}
public async Task WriteSingleRegister(ushort address, ushort value)
{
var pdu = new byte[]
{
(byte)(address >> 8),
(byte)(address & 0xFF),
(byte)(value >> 8),
(byte)(value & 0xFF)
};
await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
}
public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
{
int byteCount = (values.Length + 7) / 8;
byte[] data = new byte[byteCount];
for (int i = 0; i < values.Length; i++)
{
if (values[i])
data[i / 8] |= (byte)(1 << (i % 8));
}
var pdu = new List<byte>
{
(byte)(startAddress >> 8),
(byte)(startAddress & 0xFF),
(byte)(values.Length >> 8),
(byte)(values.Length & 0xFF),
(byte)data.Length
};
pdu.AddRange(data);
await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
}
public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
{
var pdu = new List<byte>
{
(byte)(startAddress >> 8),
(byte)(startAddress & 0xFF),
(byte)(values.Length >> 8),
(byte)(values.Length & 0xFF),
(byte)(values.Length * 2)
};
foreach (var val in values)
{
pdu.Add((byte)(val >> 8));
pdu.Add((byte)(val & 0xFF));
}
await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray());
}
#endregion
#region
public Task<byte[]> SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
{
int id = Interlocked.Increment(ref _transactionId);
var transactionId = (ushort)(id % ushort.MaxValue);
var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
var request = new ModbusTcpRequest
{
TransactionId = transactionId,
FunctionCode = functionCode,
PDU = pdu,
Completion = tcs
};
_pendingRequests[transactionId] = tcs;
_channel.Writer.TryWrite(request);
return tcs.Task;
}
private async Task ProcessQueueAsync()
{
while (true)
{
var request = await _channel.Reader.ReadAsync();
byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
OnTx?.Invoke(packet);
await _udpClient.SendAsync(packet, packet.Length);
}
}
private async Task ReceiveLoopAsync()
{
while (true)
{
UdpReceiveResult result = await _udpClient.ReceiveAsync();
var buffer = result.Buffer;
if (buffer.Length < 6) continue;
ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2));
if (_pendingRequests.TryRemove(transactionId, out var tcs))
{
OnRx?.Invoke(buffer);
var responsePdu = new ReadOnlySpan<byte>(buffer, 6, buffer.Length - 6).ToArray();
if ((responsePdu[1] & 0x80) != 0)
{
byte exceptionCode = responsePdu[2];
tcs.SetException(new ModbusException(responsePdu[1], exceptionCode));
}
else
{
tcs.SetResult(responsePdu);
}
}
}
}
private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData)
{
int pduLength = 1 + pduData.Length;
int totalLength = 7 + pduLength;
Span<byte> packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];
packet[0] = (byte)(transactionId >> 8);
packet[1] = (byte)(transactionId);
packet[2] = 0; packet[3] = 0;
ushort length = (ushort)(pduLength + 1);
packet[4] = (byte)(length >> 8);
packet[5] = (byte)(length);
packet[6] = unitId;
packet[7] = functionCode;
pduData.AsSpan().CopyTo(packet.Slice(8));
return packet.ToArray();
}
#endregion
private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
{
byte[] buffer = new byte[4];
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress);
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity);
return buffer;
}
private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
{
var result = new bool[count];
int byteCount = pdu[2];
int dataIndex = 3;
for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
{
if (bitPos == 8)
{
bitPos = 0;
bytePos++;
}
result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
}
return result;
}
private ushort[] ParseRegisters(byte[] pdu, ushort count)
{
var result = new ushort[count];
int dataStart = 3;
for (int i = 0; i < count; i++)
{
int offset = dataStart + i * 2;
result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
}
return result;
}
public void Dispose()
{
foreach (var tcs in _pendingRequests.Values)
tcs.TrySetCanceled();
_udpClient?.Dispose();
}
}
}

View File

@@ -45,9 +45,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />

View File

@@ -210,7 +210,310 @@ namespace Serein.Library.Utils
};
}
}
/// <summary>
/// 创建字段 Getter 委托Func&lt;object, object&gt;
/// </summary>
public static Func<object, object> CreateFieldGetter(FieldInfo fieldInfo)
{
if (fieldInfo == null)
throw new ArgumentNullException(nameof(fieldInfo));
var method = new DynamicMethod(
fieldInfo.Name + "_Get",
typeof(object),
new[] { typeof(object) },
fieldInfo.DeclaringType,
true);
ILGenerator il = method.GetILGenerator();
if (!fieldInfo.IsStatic)
{
// 加载实例
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType);
il.Emit(OpCodes.Ldfld, fieldInfo);
}
else
{
il.Emit(OpCodes.Ldsfld, fieldInfo);
}
// 如果是值类型,装箱
if (fieldInfo.FieldType.IsValueType)
il.Emit(OpCodes.Box, fieldInfo.FieldType);
il.Emit(OpCodes.Ret);
return (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
}
/// <summary>
/// 创建字段 Setter 委托Action&lt;object, object&gt;
/// </summary>
public static Action<object, object> CreateFieldSetter(FieldInfo fieldInfo)
{
if (fieldInfo == null)
throw new ArgumentNullException(nameof(fieldInfo));
if (fieldInfo.IsInitOnly)
throw new InvalidOperationException($"字段 {fieldInfo.Name} 是只读字段,无法设置值。");
var method = new DynamicMethod(
fieldInfo.Name + "_Set",
null,
new[] { typeof(object), typeof(object) },
fieldInfo.DeclaringType,
true);
ILGenerator il = method.GetILGenerator();
if (!fieldInfo.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType);
}
// 加载值
il.Emit(OpCodes.Ldarg_1);
if (fieldInfo.FieldType.IsValueType)
il.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
else
il.Emit(OpCodes.Castclass, fieldInfo.FieldType);
if (fieldInfo.IsStatic)
il.Emit(OpCodes.Stsfld, fieldInfo);
else
il.Emit(OpCodes.Stfld, fieldInfo);
il.Emit(OpCodes.Ret);
return (Action<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}
/// <summary>
/// 创建属性 Getter 委托Func&lt;object, object&gt;
/// </summary>
public static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
var getMethod = propertyInfo.GetGetMethod(true);
if (getMethod == null)
throw new InvalidOperationException($"属性 {propertyInfo.Name} 没有可用的 Getter。");
var method = new DynamicMethod(
propertyInfo.Name + "_Get",
typeof(object),
new[] { typeof(object) },
propertyInfo.DeclaringType,
true);
ILGenerator il = method.GetILGenerator();
if (!getMethod.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
il.EmitCall(OpCodes.Callvirt, getMethod, null);
}
else
{
il.EmitCall(OpCodes.Call, getMethod, null);
}
// 装箱
if (propertyInfo.PropertyType.IsValueType)
il.Emit(OpCodes.Box, propertyInfo.PropertyType);
il.Emit(OpCodes.Ret);
return (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
}
/// <summary>
/// 创建属性 Setter 委托Action&lt;object, object&gt;
/// </summary>
public static Action<object, object> CreatePropertySetter(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
var setMethod = propertyInfo.GetSetMethod(true);
if (setMethod == null)
throw new InvalidOperationException($"属性 {propertyInfo.Name} 没有可用的 Setter。");
var method = new DynamicMethod(
propertyInfo.Name + "_Set",
null,
new[] { typeof(object), typeof(object) },
propertyInfo.DeclaringType,
true);
ILGenerator il = method.GetILGenerator();
if (!setMethod.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
}
// 加载值
il.Emit(OpCodes.Ldarg_1);
if (propertyInfo.PropertyType.IsValueType)
il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
else
il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
if (setMethod.IsStatic)
il.EmitCall(OpCodes.Call, setMethod, null);
else
il.EmitCall(OpCodes.Callvirt, setMethod, null);
il.Emit(OpCodes.Ret);
return (Action<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}
/// <summary>
/// 创建集合赋值委托Action&lt;object, object, object&gt;
/// </summary>
/// <param name="collectionType"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
public static Action<object, object, object> CreateCollectionSetter(Type collectionType)
{
DynamicMethod dm = new DynamicMethod(
"SetCollectionValue",
null,
new[] { typeof(object), typeof(object), typeof(object) },
typeof(EmitHelper).Module,
true);
ILGenerator il = dm.GetILGenerator();
if (collectionType.IsArray)
{
// (object array, object index, object value) => ((T[])array)[(int)index] = (T)value;
var elementType = collectionType.GetElementType()!;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Unbox_Any, typeof(int)); // index
il.Emit(OpCodes.Ldarg_2);
if (elementType.IsValueType)
il.Emit(OpCodes.Unbox_Any, elementType);
else
il.Emit(OpCodes.Castclass, elementType);
il.Emit(OpCodes.Stelem, elementType); // 设置数组元素
il.Emit(OpCodes.Ret);
}
else
{
// 尝试获取 set_Item 方法
MethodInfo? setItem = collectionType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.Public);
if (setItem == null)
throw new NotSupportedException($"类型 {collectionType} 不支持 set_Item。");
var parameters = setItem.GetParameters();
var indexType = parameters[0].ParameterType;
var valueType = parameters[1].ParameterType;
// (object collection, object index, object value) => ((CollectionType)collection)[(IndexType)index] = (ValueType)value;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, collectionType);
il.Emit(OpCodes.Ldarg_1);
if (indexType.IsValueType)
il.Emit(OpCodes.Unbox_Any, indexType);
else
il.Emit(OpCodes.Castclass, indexType);
il.Emit(OpCodes.Ldarg_2);
if (valueType.IsValueType)
il.Emit(OpCodes.Unbox_Any, valueType);
else
il.Emit(OpCodes.Castclass, valueType);
il.Emit(OpCodes.Callvirt, setItem);
il.Emit(OpCodes.Ret);
}
return (Action<object, object, object>)dm.CreateDelegate(typeof(Action<object, object, object>));
}
/// <summary>
/// 创建集合获取委托Func&lt;object, object, object&gt;
/// </summary>
/// <param name="collectionType"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
public static Func<object, object, object> CreateCollectionGetter(Type collectionType)
{
DynamicMethod dm = new DynamicMethod(
"GetCollectionValue",
typeof(object),
new[] { typeof(object), typeof(object) },
typeof(EmitHelper).Module,
true);
ILGenerator il = dm.GetILGenerator();
if (collectionType.IsArray)
{
// (object array, object index) => ((T[])array)[(int)index]
var elementType = collectionType.GetElementType()!;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Unbox_Any, typeof(int)); // index
il.Emit(OpCodes.Ldelem, elementType); // 取值
if (elementType.IsValueType)
il.Emit(OpCodes.Box, elementType); // 装箱
il.Emit(OpCodes.Ret);
}
else
{
// 调用 get_Item 方法
MethodInfo? getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
if (getItem == null)
throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。");
var parameters = getItem.GetParameters();
var indexType = parameters[0].ParameterType;
var returnType = getItem.ReturnType;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, collectionType);
il.Emit(OpCodes.Ldarg_1);
if (indexType.IsValueType)
il.Emit(OpCodes.Unbox_Any, indexType);
else
il.Emit(OpCodes.Castclass, indexType);
il.Emit(OpCodes.Callvirt, getItem);
if (returnType.IsValueType)
il.Emit(OpCodes.Box, returnType);
il.Emit(OpCodes.Ret);
}
return (Func<object, object, object>)dm.CreateDelegate(typeof(Func<object, object, object>));
}
}
}

View File

@@ -36,6 +36,16 @@ namespace Serein.Library.Utils.SereinExpression
}
}
/// <summary>
/// 连接字符串数组的指定部分
/// </summary>
/// <param name="parts"></param>
/// <param name="startIndex"></param>
/// <param name="count"></param>
/// <param name="separator"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static string JoinStrings(string[] parts, int startIndex, int count, char separator)
{
if (parts == null)
@@ -51,14 +61,21 @@ namespace Serein.Library.Utils.SereinExpression
// 使用 string.Join 连接
return string.Join(separator.ToString(), subArray);
}
}
/// <summary>
/// 条件解析器生成IL进行判断
/// 格式: data.[propertyName] [operator] [value]
/// </summary>
public class SereinConditionParser
{
/// <summary>
/// 条件表达式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static bool To<T>(T data, string expression)
{
try
@@ -67,8 +84,8 @@ namespace Serein.Library.Utils.SereinExpression
{
return false;
}
var parse = ConditionParse(data, expression);
var result = parse.Evaluate(data);
var parse = ConditionParse(data, expression); // 解析条件
var result = parse.Evaluate(data); // 执行判断
return result;
}
@@ -79,15 +96,23 @@ namespace Serein.Library.Utils.SereinExpression
}
}
public static SereinConditionResolver ConditionParse(object data, string expression)
/// <summary>
/// 解析条件
/// </summary>
/// <param name="data"></param>
/// <param name="expression"></param>
/// <returns></returns>
private static SereinConditionResolver ConditionParse(object data, string expression)
{
//ReadOnlySpan<char> expSpan = expression.AsSpan();
if (expression[0] == '.') // 表达式前缀属于从上一个节点数据对象获取成员值
{
return ParseObjectExpression(data, expression);
return ParseObjectExpression(data, expression); // 对象表达式解析
}
else
{
return ParseSimpleExpression(data, expression);
return ParseSimpleExpression(data, expression); // 简单表达式解析
}
@@ -115,6 +140,7 @@ namespace Serein.Library.Utils.SereinExpression
return null;
}
/// <summary>
/// 获取对象指定名称的成员
/// </summary>
@@ -505,42 +531,7 @@ namespace Serein.Library.Utils.SereinExpression
}
}
//public static T ValueParse<T>(object value) where T : struct, IComparable<T>
//{
// return (T)ValueParse(typeof(T), value);
//}
//public static object ValueParse(Type type, object value)
//{
// string? valueStr = value.ToString();
// if (string.IsNullOrEmpty(valueStr))
// {
// throw new ArgumentException("value is null");
// }
// object result = type switch
// {
// Type t when t.IsEnum => Enum.Parse(type, valueStr),
// Type t when t == typeof(bool) => bool.Parse(valueStr),
// Type t when t == typeof(float) => float.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(decimal) => decimal.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(double) => double.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(sbyte) => sbyte.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(byte) => byte.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(short) => short.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(ushort) => ushort.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(int) => int.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(uint) => uint.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(long) => long.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(ulong) => ulong.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(nint) => nint.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(nuint) => nuint.Parse(valueStr, CultureInfo.InvariantCulture),
// _ => throw new ArgumentException("非预期值类型")
// };
// return result;
//}
/// <summary>
/// 数值操作类型
@@ -648,3 +639,41 @@ namespace Serein.Library.Utils.SereinExpression
}
}
//public static T ValueParse<T>(object value) where T : struct, IComparable<T>
//{
// return (T)ValueParse(typeof(T), value);
//}
//public static object ValueParse(Type type, object value)
//{
// string? valueStr = value.ToString();
// if (string.IsNullOrEmpty(valueStr))
// {
// throw new ArgumentException("value is null");
// }
// object result = type switch
// {
// Type t when t.IsEnum => Enum.Parse(type, valueStr),
// Type t when t == typeof(bool) => bool.Parse(valueStr),
// Type t when t == typeof(float) => float.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(decimal) => decimal.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(double) => double.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(sbyte) => sbyte.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(byte) => byte.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(short) => short.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(ushort) => ushort.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(int) => int.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(uint) => uint.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(long) => long.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(ulong) => ulong.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(nint) => nint.Parse(valueStr, CultureInfo.InvariantCulture),
// Type t when t == typeof(nuint) => nuint.Parse(valueStr, CultureInfo.InvariantCulture),
// _ => throw new ArgumentException("非预期值类型")
// };
// return result;
//}