using CapMachine.Wpf.CanDrive; using CapMachine.Wpf.Services; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace CapMachine.Wpf.CanDrive.ZlgCan { /// /// 基于周立功 ZDBC(zdbc.dll)的 DBC 数据库封装。 /// /// /// 设计要点: /// - zdbc.dll 为原生依赖,必须放置在程序输出目录(AppContext.BaseDirectory)。 /// - 本类封装 DBC 文件加载、消息/信号枚举、以及基于 DBCMessage 的编码/解码能力。 /// - 运行稳定性:实现 IDisposable,确保 ZDBC_Release 与相关非托管内存释放。 /// public sealed class ZlgDbcDatabase : IDisposable { private readonly ILogService _log; private readonly object _sync = new object(); private uint _dbcHandle; private bool _disposed; // 复用非托管缓冲,避免高频 Allocate/Free private IntPtr _msgPtr = IntPtr.Zero; private IntPtr _countPtr = IntPtr.Zero; private IntPtr _canFramePtr = IntPtr.Zero; private IntPtr _canFdFramePtr = IntPtr.Zero; private readonly Dictionary _messageByName = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _messageIdByName = new Dictionary(StringComparer.Ordinal); /// /// 构造函数。 /// /// 日志服务。 public ZlgDbcDatabase(ILogService logService) { _log = logService; } /// /// 是否已加载。 /// public bool IsLoaded { get { return _dbcHandle != 0 && _dbcHandle != ZDBC.INVALID_DBC_HANDLE; } } /// /// 加载 DBC 文件。 /// /// DBC 文件路径。 /// 是否启用异步解析(库内部线程)。 /// 是否合并到当前数据库(支持加载多个文件)。 /// 协议类型:J1939=0,其他=1。 public void Load(string dbcPath, bool enableAsyncAnalyse = true, bool merge = false, byte protocolType = ZDBC.PROTOCOL_OTHER) { ThrowIfDisposed(); if (string.IsNullOrWhiteSpace(dbcPath)) { throw new ArgumentException("dbcPath 不能为空。", nameof(dbcPath)); } EnsureNativeDllExists("zdbc.dll"); if (!File.Exists(dbcPath)) { throw new FileNotFoundException("DBC 文件不存在。", dbcPath); } lock (_sync) { if (_dbcHandle == 0 || _dbcHandle == ZDBC.INVALID_DBC_HANDLE) { _dbcHandle = ZDBC.ZDBC_Init(0, enableAsyncAnalyse ? (byte)1 : (byte)0); if (_dbcHandle == ZDBC.INVALID_DBC_HANDLE) { _dbcHandle = 0; throw new InvalidOperationException("ZDBC_Init 初始化失败。"); } } var fileInfo = new ZDBC.FileInfo { strFilePath = BuildFixedPathBytes(dbcPath, ZDBC._MAX_FILE_PATH_ + 1), type = protocolType, merge = (byte)(merge ? 1 : 0) }; IntPtr pFile = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.FileInfo))); try { Marshal.StructureToPtr(fileInfo, pFile, false); var ok = ZDBC.ZDBC_LoadFile(_dbcHandle, pFile); if (!ok) { throw new InvalidOperationException("ZDBC_LoadFile 失败,请检查 DBC 文件格式。"); } } finally { Marshal.FreeHGlobal(pFile); } RefreshMessageCache_NoLock(); } } /// /// 枚举 DBC 内全部 Message/Signal 元信息,并转换为 UI 使用的 CanDbcModel 集合。 /// /// 信号集合。 public ObservableCollection BuildCanDbcModels() { ThrowIfDisposed(); if (!IsLoaded) { return new ObservableCollection(); } lock (_sync) { var list = new List(); foreach (var kv in _messageByName) { var msg = kv.Value; var msgName = ByteArrayToAsciiString(msg.strName); var msgIdStr = "0x" + msg.nID.ToString("X8"); var signals = msg.vSignals; if (signals == null || signals.Length == 0) { continue; } var signalCount = (int)Math.Min(msg.nSignalCount, (uint)signals.Length); for (int i = 0; i < signalCount; i++) { var s = signals[i]; var sigName = ByteArrayToAsciiString(s.strName); if (string.IsNullOrWhiteSpace(sigName)) { continue; } list.Add(new CanDbcModel { MsgName = msgName, MsgId = msgIdStr, SignalName = sigName, SignalDesc = ByteArrayToAsciiString(s.strComment), SignalUnit = ByteArrayToAsciiString(s.unit), SignalRtValue = "--", Publisher = string.Empty }); } } return new ObservableCollection(list); } } /// /// 根据 MsgName 获取 Message ID。 /// /// 消息名称。 /// Message ID。 public uint GetMessageIdByName(string msgName) { ThrowIfDisposed(); if (string.IsNullOrWhiteSpace(msgName)) return 0; lock (_sync) { return _messageIdByName.TryGetValue(msgName, out var id) ? id : 0; } } /// /// 根据 MsgName + 信号值集合编码为 CAN/CANFD 原始帧。 /// /// 消息名称。 /// 信号值(SignalName -> 实际值)。 /// 帧类型:FT_CAN=0,FT_CANFD=1。 /// 编码后的 can_id 与数据。 public (uint CanId, byte[] Data, int DataLen) EncodeToRawFrame(string msgName, IDictionary signals, byte frameType) { ThrowIfDisposed(); if (!IsLoaded) { throw new InvalidOperationException("DBC 未加载。"); } if (string.IsNullOrWhiteSpace(msgName)) { throw new ArgumentException("msgName 不能为空。", nameof(msgName)); } lock (_sync) { if (!_messageByName.TryGetValue(msgName, out var msg)) { throw new InvalidOperationException($"DBC 中未找到消息:{msgName}"); } // 在副本上修改信号,避免污染缓存 var workingMsg = msg; if (workingMsg.vSignals == null) { workingMsg.vSignals = new ZDBC.DBCSignal[ZDBC._DBC_SIGNAL_MAX_COUNT_]; } if (signals != null) { foreach (var kv in signals) { SetSignalActualValue(ref workingMsg, kv.Key, kv.Value); } } EnsureMarshalBuffers_NoLock(); // 写入 msg 到非托管 Marshal.StructureToPtr(workingMsg, _msgPtr, false); // nCount 输入输出参数,准备 1 帧 Marshal.WriteInt32(_countPtr, 1); if (frameType == ZDBC.FT_CAN) { var ok = ZDBC.ZDBC_Encode(_dbcHandle, _canFramePtr, _countPtr, _msgPtr, frameType); if (!ok) { throw new InvalidOperationException("ZDBC_Encode(CAN) 失败。"); } var frame = (ZlgNativeCanFrame)Marshal.PtrToStructure(_canFramePtr, typeof(ZlgNativeCanFrame)); var len = Math.Min(8, (int)frame.can_dlc); var data = new byte[len]; if (len > 0 && frame.data != null) { Array.Copy(frame.data, data, len); } return (frame.can_id, data, len); } if (frameType == ZDBC.FT_CANFD) { var ok = ZDBC.ZDBC_Encode(_dbcHandle, _canFdFramePtr, _countPtr, _msgPtr, frameType); if (!ok) { throw new InvalidOperationException("ZDBC_Encode(CANFD) 失败。"); } var frame = (ZlgNativeCanFdFrame)Marshal.PtrToStructure(_canFdFramePtr, typeof(ZlgNativeCanFdFrame)); var len = Math.Min(64, (int)frame.len); var data = new byte[len]; if (len > 0 && frame.data != null) { Array.Copy(frame.data, data, len); } return (frame.can_id, data, len); } throw new ArgumentOutOfRangeException(nameof(frameType), "frameType 仅支持 FT_CAN=0 或 FT_CANFD=1。"); } } /// /// 将原始 CAN/CANFD 帧解码为 DBCMessage,并返回消息名称与信号实际值。 /// /// can_id。 /// payload。 /// 帧类型:FT_CAN=0,FT_CANFD=1。 /// 解码结果。 public (string MsgName, Dictionary Signals) DecodeRawFrame(uint canId, byte[] data, byte frameType) { ThrowIfDisposed(); if (!IsLoaded) { throw new InvalidOperationException("DBC 未加载。"); } lock (_sync) { EnsureMarshalBuffers_NoLock(); if (frameType == ZDBC.FT_CAN) { var frame = ZlgNativeCanFrame.Create(canId, data ?? Array.Empty(), requestTxEcho: false); Marshal.StructureToPtr(frame, _canFramePtr, false); var ok = ZDBC.ZDBC_Decode(_dbcHandle, _msgPtr, _canFramePtr, 1, frameType); if (!ok) { return (string.Empty, new Dictionary()); } var msg = Marshal.PtrToStructure(_msgPtr); return ExtractActualSignals(msg); } if (frameType == ZDBC.FT_CANFD) { var frame = ZlgNativeCanFdFrame.Create(canId, data ?? Array.Empty(), requestTxEcho: false); Marshal.StructureToPtr(frame, _canFdFramePtr, false); var ok = ZDBC.ZDBC_Decode(_dbcHandle, _msgPtr, _canFdFramePtr, 1, frameType); if (!ok) { return (string.Empty, new Dictionary()); } var msg = Marshal.PtrToStructure(_msgPtr); return ExtractActualSignals(msg); } throw new ArgumentOutOfRangeException(nameof(frameType), "frameType 仅支持 FT_CAN=0 或 FT_CANFD=1。"); } } /// public void Dispose() { if (_disposed) return; _disposed = true; lock (_sync) { try { if (_dbcHandle != 0 && _dbcHandle != ZDBC.INVALID_DBC_HANDLE) { ZDBC.ZDBC_Release(_dbcHandle); } } catch (Exception ex) { _log.Warn($"ZDBC_Release 异常:{ex.Message}"); } finally { _dbcHandle = 0; } FreeMarshalBuffers_NoLock(); _messageByName.Clear(); _messageIdByName.Clear(); } } private void RefreshMessageCache_NoLock() { _messageByName.Clear(); _messageIdByName.Clear(); if (!IsLoaded) { return; } EnsureMarshalBuffers_NoLock(); // 遍历消息 var msg = new ZDBC.DBCMessage { vSignals = new ZDBC.DBCSignal[ZDBC._DBC_SIGNAL_MAX_COUNT_], strName = new byte[ZDBC._DBC_NAME_LENGTH_ + 1], strComment = new byte[ZDBC._DBC_COMMENT_MAX_LENGTH_ + 1] }; Marshal.StructureToPtr(msg, _msgPtr, false); var ok = ZDBC.ZDBC_GetFirstMessage(_dbcHandle, _msgPtr); while (ok) { var m = Marshal.PtrToStructure(_msgPtr); var name = ByteArrayToAsciiString(m.strName); if (!string.IsNullOrWhiteSpace(name)) { _messageByName[name] = m; _messageIdByName[name] = m.nID; } Marshal.StructureToPtr(msg, _msgPtr, false); ok = ZDBC.ZDBC_GetNextMessage(_dbcHandle, _msgPtr); } } private void EnsureMarshalBuffers_NoLock() { if (_msgPtr == IntPtr.Zero) { _msgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage))); } if (_countPtr == IntPtr.Zero) { _countPtr = Marshal.AllocHGlobal(sizeof(int)); } if (_canFramePtr == IntPtr.Zero) { _canFramePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZlgNativeCanFrame))); } if (_canFdFramePtr == IntPtr.Zero) { _canFdFramePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZlgNativeCanFdFrame))); } } private void FreeMarshalBuffers_NoLock() { if (_canFdFramePtr != IntPtr.Zero) { Marshal.FreeHGlobal(_canFdFramePtr); _canFdFramePtr = IntPtr.Zero; } if (_canFramePtr != IntPtr.Zero) { Marshal.FreeHGlobal(_canFramePtr); _canFramePtr = IntPtr.Zero; } if (_countPtr != IntPtr.Zero) { Marshal.FreeHGlobal(_countPtr); _countPtr = IntPtr.Zero; } if (_msgPtr != IntPtr.Zero) { Marshal.FreeHGlobal(_msgPtr); _msgPtr = IntPtr.Zero; } } private static void EnsureNativeDllExists(string dllName) { var baseDir = AppContext.BaseDirectory; var full = Path.Combine(baseDir, dllName); if (!File.Exists(full)) { throw new FileNotFoundException($"未找到 {dllName},请将其复制到程序输出目录:{baseDir}", full); } } private void SetSignalActualValue(ref ZDBC.DBCMessage msg, string signalName, double actualValue) { if (msg.vSignals == null || msg.vSignals.Length == 0) { return; } var count = (int)Math.Min(msg.nSignalCount, (uint)msg.vSignals.Length); for (int i = 0; i < count; i++) { var s = msg.vSignals[i]; var name = ByteArrayToAsciiString(s.strName); if (!string.Equals(name, signalName, StringComparison.Ordinal)) { continue; } // 使用 ZDBC_CalcRawValue 做实际值->原始值转换 IntPtr pSgl = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal))); IntPtr pVal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(double))); try { Marshal.StructureToPtr(s, pSgl, false); Marshal.StructureToPtr(actualValue, pVal, false); var raw = ZDBC.ZDBC_CalcRawValue(pSgl, pVal); msg.vSignals[i].nRawvalue = raw; } finally { Marshal.FreeHGlobal(pVal); Marshal.FreeHGlobal(pSgl); } return; } } private (string MsgName, Dictionary Signals) ExtractActualSignals(ZDBC.DBCMessage msg) { var msgName = ByteArrayToAsciiString(msg.strName); var dict = new Dictionary(StringComparer.Ordinal); if (msg.vSignals == null) { return (msgName, dict); } var count = (int)Math.Min(msg.nSignalCount, (uint)msg.vSignals.Length); for (int i = 0; i < count; i++) { var s = msg.vSignals[i]; var name = ByteArrayToAsciiString(s.strName); if (string.IsNullOrWhiteSpace(name)) { continue; } // 使用 ZDBC_CalcActualValue 做原始值->实际值转换 IntPtr pSgl = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal))); IntPtr pRaw = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ulong))); try { Marshal.StructureToPtr(s, pSgl, false); Marshal.StructureToPtr(s.nRawvalue, pRaw, false); var actual = ZDBC.ZDBC_CalcActualValue(pSgl, pRaw); dict[name] = actual; } finally { Marshal.FreeHGlobal(pRaw); Marshal.FreeHGlobal(pSgl); } } return (msgName, dict); } private static byte[] BuildFixedPathBytes(string path, int fixedLen) { var bytes = Encoding.ASCII.GetBytes(path); var dst = new byte[fixedLen]; Array.Clear(dst, 0, dst.Length); Array.Copy(bytes, 0, dst, 0, Math.Min(bytes.Length, fixedLen - 1)); return dst; } private static string ByteArrayToAsciiString(byte[]? bytes) { if (bytes == null || bytes.Length == 0) return string.Empty; int len = Array.IndexOf(bytes, (byte)0); if (len < 0) len = bytes.Length; return Encoding.ASCII.GetString(bytes, 0, len).Trim(); } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(ZlgDbcDatabase)); } } } }