using Common.Attributes; using Logger; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ZLGUSBCANFD { [ADPCommand] public class ZLGCANFD : IDisposable { // 硬件设备与通道句柄 private IntPtr _deviceHandle = IntPtr.Zero; private readonly IntPtr[] _channelHandles; // 每个通道分配独立的 DBC 引擎句柄与锁,彻底解决台架间DBC冲突与锁竞争 private readonly uint[] _dbcHandles; private readonly bool[] _isDbcLoadedArray; private readonly object[] _channelLocks; // 通道级细粒度锁 // 异步高性能接收线程控制 private volatile bool _isRunning = false; private readonly List _receiveThreads = new List(); // DBC 循环发送任务管理:Key = (通道号, 帧ID) private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), CancellationTokenSource> _cyclicSenders = new ConcurrentDictionary<(uint, uint), CancellationTokenSource>(); // 信号值持久化覆盖表:Key = (通道号, 帧ID),Value = 信号名 → 物理值 // 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值 private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), Dictionary> _signalOverrides = new ConcurrentDictionary<(uint, uint), Dictionary>(); // 动态硬件参数 private readonly uint _deviceType; // 76: USBCANFD-400U private readonly uint _deviceIndex; // 设备索引 private readonly int _maxChannels; // 动态通道数 // 通道波特率与终端电阻配置(构造时传入,所有通道共用) private readonly string _abitBaud; private readonly string _dbitBaud; private readonly bool _enableTerminalResistance; /// /// 当前 CAN 卡专属的 DBC 解析器,按通道隔离报文数据库。 /// public ZLGDBCParser DBCParser { get; } /// /// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发 /// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体 /// public event Action? OnDbcMessageDecoded; public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4, string abitBaud = "500000", string dbitBaud = "2000000", bool enableTerminalResistance = true) { _deviceType = deviceType; _deviceIndex = deviceIndex; _maxChannels = maxChannels; _abitBaud = abitBaud; _dbitBaud = dbitBaud; _enableTerminalResistance = enableTerminalResistance; // 初始化通道相关状态数组 _channelHandles = new IntPtr[_maxChannels]; _dbcHandles = new uint[_maxChannels]; _isDbcLoadedArray = new bool[_maxChannels]; _channelLocks = new object[_maxChannels]; DBCParser = new ZLGDBCParser(_maxChannels); for (int i = 0; i < _maxChannels; i++) { _channelHandles[i] = IntPtr.Zero; _dbcHandles[i] = 0; _isDbcLoadedArray[i] = false; _channelLocks[i] = new object(); } } #region 1. 硬件连接与通道初始化 /// /// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置) /// public virtual bool 打开设备() { if (_deviceHandle != IntPtr.Zero) return true; _deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_400U, _deviceIndex, 0); return _deviceHandle != IntPtr.Zero; } /// /// 针对特定通道进行参数初始化并启动(使用构造时传入的波特率与终端电阻配置) /// public virtual bool 初始化并启动通道(uint 通道号) { if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。"); if (通道号 >= _maxChannels) return false; lock (_channelLocks[通道号]) { // 如果已经启动过,先复位 if (_channelHandles[通道号] != IntPtr.Zero) { ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]); } // 1. 设置该通道专属的仲裁域与数据域波特率 if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", _abitBaud) != 1) return false; if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", _dbitBaud) != 1) return false; // 2. 设置该通道专属内部终端电阻状态 string resistanceStr = _enableTerminalResistance ? "1" : "0"; if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false; // 3. 规范配置通道结构体 ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG(); config.can_type = 1; // 1 代表 CANFD 模式 config.config.canfd.mode = 0; // 0 代表正常工作模式 IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config)); Marshal.StructureToPtr(config, pConfig, true); _channelHandles[通道号] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, 通道号, pConfig); Marshal.FreeHGlobal(pConfig); if (_channelHandles[通道号] == IntPtr.Zero) return false; // 4. 启动 CAN 通道 if (ZLGCAN.ZCAN_StartCAN(_channelHandles[通道号]) != 1) return false; // 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询) if (!_isRunning) _isRunning = true; int chnIdx = (int)通道号; Thread rxThread = new Thread(() => 接收轮询核心(_channelHandles[chnIdx], (uint)chnIdx)) { IsBackground = true, Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread" }; _receiveThreads.Add(rxThread); rxThread.Start(); return true; } } public virtual void 关闭CAN卡设备() { _isRunning = false; 停止所有循环发送(); Thread.Sleep(50); // 确保轮询线程安全退出 _receiveThreads.Clear(); // 动态复位所有通道并释放DBC //for (uint i = 0; i < _maxChannels; i++) //{ // lock (_channelLocks[i]) // { // if (_channelHandles[i] != IntPtr.Zero) // { // ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); // _channelHandles[i] = IntPtr.Zero; // } // 释放通道DBC(i); // } //} // 关闭设备主句柄 if (_deviceHandle != IntPtr.Zero) { ZLGCAN.ZCAN_CloseDevice(_deviceHandle); _deviceHandle = IntPtr.Zero; } } #endregion #region 2. DBC 矩阵文件操作 /// /// 指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵) /// public virtual bool 加载通道DBC文件(uint 通道号, string dbcFilePath) { if (通道号 >= _maxChannels) return false; lock (_channelLocks[通道号]) { try { if (_isDbcLoadedArray[通道号]) return true; //加载前先卸载 释放通道DBC(通道号); uint chDbcHandle = ZDBC.ZDBC_Init(); if (chDbcHandle == 0) return false; IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath); bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath); Marshal.FreeHGlobal(ptrPath); if (success) { _dbcHandles[通道号] = chDbcHandle; _isDbcLoadedArray[通道号] = true; DBCParser.ParseAndLoadToDatabase(chDbcHandle, (int)通道号); } else { ZDBC.ZDBC_Release(chDbcHandle); } return success; } catch { return false; } } } public virtual void 释放通道DBC(uint 通道号) { if (通道号 >= _maxChannels) return; lock (_channelLocks[通道号]) { if (_isDbcLoadedArray[通道号] && _dbcHandles[通道号] != 0) { ZDBC.ZDBC_Release(_dbcHandles[通道号]); _dbcHandles[通道号] = 0; _isDbcLoadedArray[通道号] = false; DBCParser.MsgDatabase[(int)通道号].Clear(); } } } #endregion #region 3. 轮询接收与实时自动化 DBC 解码 private void 接收轮询核心(IntPtr channelHandle, uint channelIndex) { const int bufferSize = 100; int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data)); int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data)); int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage)); IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize); IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize); IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize); try { while (_isRunning) { bool currentTurnHasData = false; // 1. 自动提取并解析经典 CAN 帧 uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0); if (canNum > 0) { uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5); for (int i = 0; i < actualRecv; i++) { IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize); // 只锁定当前通道的DBC句柄进行解码,高并发完全不卡顿 lock (_channelLocks[channelIndex]) { if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0)) { var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage)); OnDbcMessageDecoded?.Invoke(channelIndex, msg); } } } currentTurnHasData = true; } // 2. 自动提取并解析高速 CANFD 帧 uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1); if (canfdNum > 0) { uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5); for (int i = 0; i < actualRecvFd; i++) { IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize); lock (_channelLocks[channelIndex]) { if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1)) { var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage)); OnDbcMessageDecoded?.Invoke(channelIndex, msg); } } } currentTurnHasData = true; } if (!currentTurnHasData) { Thread.Sleep(2); } } } finally { Marshal.FreeHGlobal(ptrCanBuffer); Marshal.FreeHGlobal(ptrCanFDBuffer); Marshal.FreeHGlobal(ptrDbcMsg); } } #endregion #region 4. DBC 自动打包与智能报文发送 /// /// 发送一条 DBC 定义报文。 /// /// 通道号 /// DBC 中定义的帧 ID /// 信号名称 → 物理值;只设置字典中包含的信号,其余信号保持 DBC 默认值 /// 0 = 只发送一次;>0 = 按指定间隔循环发送(毫秒) /// 1 = CANFD,0 = CAN /// 是否成功启动/发送 public virtual bool 发送DBC定义报文( uint 通道号, uint 帧ID, Dictionary 信号物理值字典, int 循环间隔毫秒 = 0, int 是否使用CANFD = 1) { if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false; if (!_isDbcLoadedArray[通道号]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。"); if (循环间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环间隔毫秒), "循环间隔必须大于或等于 0。"); if (信号物理值字典 == null || 信号物理值字典.Count == 0) throw new ArgumentException("信号物理值字典不能为空。", nameof(信号物理值字典)); if (循环间隔毫秒 == 0) { // 单次发送:同步执行 lock (_channelLocks[通道号]) { return 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD); } } // 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环 停止循环发送(通道号, 帧ID); var cts = new CancellationTokenSource(); _cyclicSenders[(通道号, 帧ID)] = cts; Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { try { lock (_channelLocks[通道号]) { if (_channelHandles[通道号] == IntPtr.Zero) break; 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD); } await Task.Delay(循环间隔毫秒, cts.Token); } catch (OperationCanceledException) { break; } catch (Exception ex) { Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}"); break; } } // 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current, cts)) { _cyclicSenders.TryRemove((通道号, 帧ID), out _); } cts.Dispose(); }, cts.Token); return true; } /// /// 停止指定通道、指定帧 ID 的循环发送。 /// public virtual void 停止循环发送(uint 通道号, uint 帧ID) { if (_cyclicSenders.TryRemove((通道号, 帧ID), out var cts)) { cts.Cancel(); // 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token } } /// /// 停止所有 DBC 循环发送任务。 /// public virtual void 停止所有循环发送() { foreach (var kvp in _cyclicSenders) { kvp.Value.Cancel(); kvp.Value.Dispose(); } _cyclicSenders.Clear(); } /// /// 清除指定通道、指定帧 ID 的信号覆盖值,恢复为 DBC 初始值。 /// public virtual void 清除信号覆盖(uint 通道号, uint 帧ID) { if (_signalOverrides.TryRemove((通道号, 帧ID), out var dict)) { lock (dict) dict.Clear(); } } /// /// 清除指定通道所有帧的信号覆盖值。 /// public virtual void 清除通道信号覆盖(uint 通道号) { foreach (var key in _signalOverrides.Keys) { if (key.通道号 == 通道号 && _signalOverrides.TryRemove(key, out var dict)) { lock (dict) dict.Clear(); } } } /// /// 单次发送 DBC 报文(调用方已持有通道锁)。 /// private bool 发送DBC定义报文单次(uint 通道号, uint 帧ID, Dictionary 信号物理值字典, int 是否使用CANFD) { uint chDbcHandle = _dbcHandles[通道号]; IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage))); IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint)); Marshal.WriteInt32(ptrCount, 1); try { if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return false; var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage)); // 根据传入的物理值设置对应信号的原始值 for (int i = 0; i < msg.nSignalCount; i++) { var signal = msg.vSignals[i]; string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0'); if (信号物理值字典.TryGetValue(signalName, out double physicalValue)) { signal.nRawvalue = 物理值转原始值(signal, physicalValue); msg.vSignals[i] = signal; } } Marshal.StructureToPtr(msg, ptrDbcMsg, true); if (是否使用CANFD == 0) { IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame))); try { if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false; ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame)); canFrame.__pad |= 0x20; ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 2 }; IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData)); try { Marshal.StructureToPtr(txData, pTx, true); return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1; } finally { Marshal.FreeHGlobal(pTx); } } finally { Marshal.FreeHGlobal(ptrCanFrame); } } else { IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame))); try { if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false; ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame)); canfdFrame.flags |= 0x20; ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 2 }; IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData)); try { Marshal.StructureToPtr(txFdData, pTxFd, true); return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1; } finally { Marshal.FreeHGlobal(pTxFd); } } finally { Marshal.FreeHGlobal(ptrCanFDFrame); } } } finally { Marshal.FreeHGlobal(ptrDbcMsg); Marshal.FreeHGlobal(ptrCount); } } /// /// 不依赖通道锁的物理值到原始值转换。 /// private static ulong 物理值转原始值(ZDBC.DBCSignal 信号定义, double 实际物理值) { IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal))); IntPtr pValue = Marshal.AllocHGlobal(sizeof(double)); try { Marshal.StructureToPtr(信号定义, pSignal, true); Marshal.StructureToPtr(实际物理值, pValue, true); return ZDBC.ZDBC_CalcRawValue(pSignal, pValue); } finally { Marshal.FreeHGlobal(pSignal); Marshal.FreeHGlobal(pValue); } } #endregion #region 4.1 报文设置与发送 (支持智能进制转换) /// /// 设置并发送指定信号值(帧 ID 支持 string 兼容模式)。 /// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。 /// /// CAN/CANFD 通道索引 /// 对应报文的帧 ID(可以是 "26"、"0x1A"、"1A") /// DBC 中定义的英文信号名称(不区分大小写) /// 要写入的实际物理数值 /// 0 = 单次发送;>0 = 周期循环发送(单位毫秒) /// 操作是否成功 public virtual bool 设置报文(uint 通道号, string 帧IDStr, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0, bool 直接发送=false) { if (!TryParseFrameId(帧IDStr, out uint 帧ID)) { Console.WriteLine($"[ZLGCANFD] 设置报文失败: 无法解析的帧 ID '{帧IDStr}'"); return false; } if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false; if (!_isDbcLoadedArray[通道号]) return false; if (string.IsNullOrWhiteSpace(信号名称)) return false; // 将本次设置的信号值写入持久化覆盖表(累积,不会丢失之前设置的值) var overrides = _signalOverrides.GetOrAdd((通道号, 帧ID), _ => new Dictionary(StringComparer.OrdinalIgnoreCase)); lock (overrides) { overrides[信号名称] = 物理值; } // 构建发送字典:DBC 初始值 + 持久化覆盖表(已包含本次设置) var merged = 构建报文发送字典(通道号, 帧ID); if (直接发送) return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒); else return true; } /// /// 发送报文:以 DBC 中所有信号的初始值作为默认值发送(帧 ID 支持 string 兼容模式)。 /// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。 /// /// CAN/CANFD 通道索引 /// DBC 中定义的帧 ID(可以是 "26"、"0x1A"、"1A") /// 0 = 单次发送;>0 = 周期循环发送(毫秒) /// 1 = 以 CANFD 格式发送;0 = 经典 CAN 格式 public virtual bool 发送报文(uint 通道号, string 帧IDStr, int 循环发送间隔毫秒 = 0, int 是否使用CANFD = 1) { if (!TryParseFrameId(帧IDStr, out uint 帧ID)) { Console.WriteLine($"[ZLGCANFD] 发送报文失败: 无法解析的帧 ID '{帧IDStr}'"); return false; } if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false; if (!_isDbcLoadedArray[通道号]) return false; if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒)); var merged = 构建报文发送字典(通道号, 帧ID); return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒, 是否使用CANFD); } /// /// 发送自定义报文(原始字节,帧 ID 支持 string 兼容模式)。 /// /// CAN/CANFD 通道索引 /// 帧 ID(可以是 "26"、"0x1A"、"1A");扩展帧会自动置位 0x80000000 /// 原始帧数据 /// 数据长度;CANFD 仅允许 0-8,12,16,20,24,32,48,64 /// 是否为 29 位扩展帧 /// true = CANFD;false = 经典 CAN /// CANFD 是否开启波特率加速 public virtual bool 发送自定义报文( uint 通道号, string 帧IDStr, byte[] 原始数据, byte dlcLength, bool 是否是扩展帧 = false, bool 是否是CANFD = true, bool 开启波特率加速BRS = true) { if (!TryParseFrameId(帧IDStr, out uint 帧ID)) { Console.WriteLine($"[ZLGCANFD] 发送自定义报文失败: 无法解析的帧 ID '{帧IDStr}'"); return false; } if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false; if (原始数据 == null) return false; // 扩展帧需要把最高位标志位置起来 uint canId = 帧ID & 0x7FFFFFFF; if (是否是扩展帧) canId |= 0x80000000; lock (_channelLocks[通道号]) { if (是否是CANFD) { // CANFD 有效 DLC 校验 byte validFdDlc = 校验CANFD的Dlc(dlcLength); ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame { can_id = canId, len = validFdDlc, flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20), // 0x20 本地回显 data = new byte[64] }; Array.Copy(原始数据, 0, fdFrame.data, 0, Math.Min(原始数据.Length, 64)); ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 }; IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData)); try { Marshal.StructureToPtr(txFdData, pTxFd, true); return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1; } finally { Marshal.FreeHGlobal(pTxFd); } } else { ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame { can_id = canId, can_dlc = dlcLength > 8 ? (byte)8 : dlcLength, __pad = 0x20, // 发送回显 data = new byte[8] }; Array.Copy(原始数据, 0, standardFrame.data, 0, Math.Min(原始数据.Length, 8)); ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 }; IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData)); try { Marshal.StructureToPtr(txData, pTx, true); return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1; } finally { Marshal.FreeHGlobal(pTx); } } } } /// /// 直接发送 8 字节原始报文(经典 CAN 或 CANFD 均可)。 /// 无需加载 DBC,适合手动构造简单报文。 /// /// 通道号(0 ~ MaxChannels-1) /// 帧 ID(标准 11 位,扩展帧自动置位 0x80000000) /// 原始数据;超过 8 字节会自动截断,不足 8 字节自动补零 /// 是否为 29 位扩展帧 /// true = CANFD 格式;false = 经典 CAN 格式 /// CANFD 是否开启波特率加速(经典 CAN 忽略此参数) /// 是否发送成功 public virtual bool 发送原始报文( uint 通道号, uint 帧ID, byte[] data, bool 是否是扩展帧 = false, bool 是否是CANFD = true, bool 开启波特率加速BRS = true) { if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false; byte[] buffer = new byte[8]; if (data != null) { Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, 8)); } uint canId = 帧ID & 0x7FFFFFFF; if (是否是扩展帧) canId |= 0x80000000; lock (_channelLocks[通道号]) { if (是否是CANFD) { ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame { can_id = canId, len = 8, flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20), data = new byte[64] }; Array.Copy(buffer, 0, fdFrame.data, 0, 8); ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 }; IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData)); try { Marshal.StructureToPtr(txFdData, pTxFd, true); return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1; } finally { Marshal.FreeHGlobal(pTxFd); } } else { ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame { can_id = canId, can_dlc = 8, __pad = 0x20, data = new byte[8] }; Array.Copy(buffer, 0, standardFrame.data, 0, 8); ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 }; IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData)); try { Marshal.StructureToPtr(txData, pTx, true); return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1; } finally { Marshal.FreeHGlobal(pTx); } } } } /// /// 构建发送用信号字典:DBC 初始值 + 额外覆盖。 /// 调用方需自行保证通道已初始化且 DBC 已加载。 /// private Dictionary 构建报文发送字典(uint 通道号, uint 帧ID, Dictionary? 额外覆盖 = null) { uint chDbcHandle = _dbcHandles[通道号]; IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage))); try { lock (_channelLocks[通道号]) { if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return new Dictionary(StringComparer.OrdinalIgnoreCase); var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage)); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); // 1. DBC 初始值打底 for (int i = 0; i < msg.nSignalCount; i++) { var signal = msg.vSignals[i]; string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0'); if (!string.IsNullOrEmpty(signalName) && signal.initialValueValid != 0) { dict[signalName] = signal.initialValue; } } // 2. 叠加持久化覆盖表(之前 设置报文 累积的值) if (_signalOverrides.TryGetValue((通道号, 帧ID), out var persisted)) { lock (persisted) { foreach (var kvp in persisted) dict[kvp.Key] = kvp.Value; } } // 3. 叠加本次调用传入的额外覆盖(优先级最高) if (额外覆盖 != null) { foreach (var kvp in 额外覆盖) { dict[kvp.Key] = kvp.Value; } } return dict; } } finally { Marshal.FreeHGlobal(ptrDbcMsg); } } /// /// CANFD 有效 DLC 校验,非法值自动就近取到下一个合法 DLC。 /// private static byte 校验CANFD的Dlc(byte dlc) { if (dlc <= 8) return dlc; if (dlc <= 12) return 12; if (dlc <= 16) return 16; if (dlc <= 20) return 20; if (dlc <= 24) return 24; if (dlc <= 32) return 32; if (dlc <= 48) return 48; return 64; } #endregion #region 5. 辅助工具子系统 /// /// 计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算 /// /// /// 智能解析帧 ID 字符串,兼容 10 进制、16 进制(如 "0x1A", "1A", "26") /// /// 输入的帧 ID 字符串 /// 解析成功后的 uint 帧 ID /// 是否解析成功 [Browsable(false)] public static bool TryParseFrameId(string idStr, out uint frameId) { frameId = 0; if (string.IsNullOrWhiteSpace(idStr)) return false; idStr = idStr.Trim(); try { // 1. 处理带 0x 或 0X 的标准 16 进制 if (idStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { frameId = Convert.ToUInt32(idStr.Substring(2), 16); return true; } // 2. 尝试当做纯 10 进制解析(如 "26") if (uint.TryParse(idStr, out frameId)) { return true; } // 3. 如果不是纯数字,但符合 16 进制特征(如 "1A"、"ABC"),尝试按 16 进制解析 // 使用 System.Globalization.NumberStyles.HexNumber if (uint.TryParse(idStr, System.Globalization.NumberStyles.HexNumber, null, out frameId)) { return true; } } catch { return false; } return false; } [Browsable(false)] public ulong 物理值转原始寄存器值(uint 通道号, ZDBC.DBCSignal 信号定义, double 实际物理值) { if (通道号 >= _maxChannels) return 0; IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal))); IntPtr pValue = Marshal.AllocHGlobal(sizeof(double)); try { Marshal.StructureToPtr(信号定义, pSignal, true); Marshal.StructureToPtr(实际物理值, pValue, true); lock (_channelLocks[通道号]) { return ZDBC.ZDBC_CalcRawValue(pSignal, pValue); } } finally { Marshal.FreeHGlobal(pSignal); Marshal.FreeHGlobal(pValue); } } #endregion [Browsable(false)] public void Dispose() { 关闭CAN卡设备(); } } }