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); } var fileSize = 0L; try { fileSize = new FileInfo(dbcPath).Length; } catch { } var fullPath = dbcPath; try { fullPath = Path.GetFullPath(dbcPath); } catch { } var shortPath = TryGetShortPath(fullPath); var candidatePaths = new List(); if (!string.IsNullOrWhiteSpace(fullPath)) candidatePaths.Add(fullPath); if (!string.IsNullOrWhiteSpace(shortPath) && !string.Equals(shortPath, fullPath, StringComparison.OrdinalIgnoreCase)) { candidatePaths.Add(shortPath); } 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 loadOk = false; foreach (var path in candidatePaths) { var fileInfo = new ZDBC.FileInfo { strFilePath = BuildFixedPathBytes(path, 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); loadOk = ZDBC.ZDBC_LoadFile(_dbcHandle, pFile); if (loadOk) { break; } } finally { Marshal.FreeHGlobal(pFile); } } if (!loadOk) { // fallback:直接按内容加载,绕开路径编码问题;同时尝试多种编码以提升兼容性。 loadOk = TryLoadContentFallback_NoLock(fullPath, merge); } if (!loadOk) { var msg = $"ZDBC_LoadFile 失败。DbcPath={fullPath},ShortPath={shortPath ?? string.Empty},Size={fileSize}。请检查 DBC 文件格式,或将 DBC 放到纯英文路径后重试。"; throw new InvalidOperationException(msg); } 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.Default.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; } /// /// 当 ZDBC_LoadFile 失败时的兜底加载策略:读取文件内容并调用 ZDBC_LoadContent。 /// /// DBC 完整路径。 /// 是否合并。 /// 加载成功返回 true。 private bool TryLoadContentFallback_NoLock(string fullPath, bool merge) { // zdbc.dll 入口为 ANSI 字符串,因此这里优先使用 ASCII(将非 ASCII 字符替换为 ?), // 避免中文注释/特殊字符导致原生库解析失败;同时需要兼容 UTF-16 BOM,否则用 ASCII 读会出现大量 \0 造成内容截断。 var encodings = new List(); try { var bytes = File.ReadAllBytes(fullPath); if (bytes.Length >= 2) { // UTF-16 LE BOM: FF FE if (bytes[0] == 0xFF && bytes[1] == 0xFE) { encodings.Add(Encoding.Unicode); } // UTF-16 BE BOM: FE FF else if (bytes[0] == 0xFE && bytes[1] == 0xFF) { encodings.Add(Encoding.BigEndianUnicode); } } if (bytes.Length >= 3) { // UTF-8 BOM: EF BB BF if (bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) { encodings.Add(Encoding.UTF8); } } } catch { } // 常规兜底编码序列 encodings.Add(new UTF8Encoding(false, true)); encodings.Add(Encoding.UTF8); encodings.Add(Encoding.Default); encodings.Add(Encoding.ASCII); foreach (var enc in encodings.Distinct()) { IntPtr pContent = IntPtr.Zero; try { var content = File.ReadAllText(fullPath, enc); pContent = Marshal.StringToHGlobalAnsi(content); var ok = ZDBC.ZDBC_LoadContent(_dbcHandle, pContent, merge ? 1u : 0u); if (ok) { return true; } } catch { } finally { if (pContent != IntPtr.Zero) { Marshal.FreeHGlobal(pContent); } } } return false; } /// /// 获取 Windows 8.3 短路径。 /// /// 长路径。 /// 短路径缓冲。 /// 缓冲区大小。 /// 返回写入的字符数量;0 表示失败。 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern uint GetShortPathName(string lpszLongPath, StringBuilder lpszShortPath, uint cchBuffer); /// /// 尝试将长路径转换为短路径(8.3),用于兼容部分原生库对中文/特殊字符路径处理不完整的问题。 /// /// 长路径。 /// 短路径;失败返回 null。 private static string? TryGetShortPath(string fullPath) { try { if (string.IsNullOrWhiteSpace(fullPath)) { return null; } var sb = new StringBuilder(1024); var ret = GetShortPathName(fullPath, sb, (uint)sb.Capacity); if (ret == 0) { return null; } return sb.ToString(); } catch { return null; } } 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.Default.GetString(bytes, 0, len).Trim(); } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(ZlgDbcDatabase)); } } } }