Files
CapMachine/CapMachine.Wpf/CanDrive/ZlgCan/ZlgDbcDatabase.cs
2026-02-02 21:22:01 +08:00

571 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 基于周立功 ZDBCzdbc.dll的 DBC 数据库封装。
/// </summary>
/// <remarks>
/// 设计要点:
/// - zdbc.dll 为原生依赖必须放置在程序输出目录AppContext.BaseDirectory
/// - 本类封装 DBC 文件加载、消息/信号枚举、以及基于 DBCMessage 的编码/解码能力。
/// - 运行稳定性:实现 IDisposable确保 ZDBC_Release 与相关非托管内存释放。
/// </remarks>
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<string, ZDBC.DBCMessage> _messageByName = new Dictionary<string, ZDBC.DBCMessage>(StringComparer.Ordinal);
private readonly Dictionary<string, uint> _messageIdByName = new Dictionary<string, uint>(StringComparer.Ordinal);
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="logService">日志服务。</param>
public ZlgDbcDatabase(ILogService logService)
{
_log = logService;
}
/// <summary>
/// 是否已加载。
/// </summary>
public bool IsLoaded
{
get
{
return _dbcHandle != 0 && _dbcHandle != ZDBC.INVALID_DBC_HANDLE;
}
}
/// <summary>
/// 加载 DBC 文件。
/// </summary>
/// <param name="dbcPath">DBC 文件路径。</param>
/// <param name="enableAsyncAnalyse">是否启用异步解析(库内部线程)。</param>
/// <param name="merge">是否合并到当前数据库(支持加载多个文件)。</param>
/// <param name="protocolType">协议类型J1939=0其他=1。</param>
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();
}
}
/// <summary>
/// 枚举 DBC 内全部 Message/Signal 元信息,并转换为 UI 使用的 CanDbcModel 集合。
/// </summary>
/// <returns>信号集合。</returns>
public ObservableCollection<CanDbcModel> BuildCanDbcModels()
{
ThrowIfDisposed();
if (!IsLoaded)
{
return new ObservableCollection<CanDbcModel>();
}
lock (_sync)
{
var list = new List<CanDbcModel>();
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<CanDbcModel>(list);
}
}
/// <summary>
/// 根据 MsgName 获取 Message ID。
/// </summary>
/// <param name="msgName">消息名称。</param>
/// <returns>Message ID。</returns>
public uint GetMessageIdByName(string msgName)
{
ThrowIfDisposed();
if (string.IsNullOrWhiteSpace(msgName)) return 0;
lock (_sync)
{
return _messageIdByName.TryGetValue(msgName, out var id) ? id : 0;
}
}
/// <summary>
/// 根据 MsgName + 信号值集合编码为 CAN/CANFD 原始帧。
/// </summary>
/// <param name="msgName">消息名称。</param>
/// <param name="signals">信号值SignalName -> 实际值)。</param>
/// <param name="frameType">帧类型FT_CAN=0FT_CANFD=1。</param>
/// <returns>编码后的 can_id 与数据。</returns>
public (uint CanId, byte[] Data, int DataLen) EncodeToRawFrame(string msgName, IDictionary<string, double> 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。");
}
}
/// <summary>
/// 将原始 CAN/CANFD 帧解码为 DBCMessage并返回消息名称与信号实际值。
/// </summary>
/// <param name="canId">can_id。</param>
/// <param name="data">payload。</param>
/// <param name="frameType">帧类型FT_CAN=0FT_CANFD=1。</param>
/// <returns>解码结果。</returns>
public (string MsgName, Dictionary<string, double> 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<byte>(), requestTxEcho: false);
Marshal.StructureToPtr(frame, _canFramePtr, false);
var ok = ZDBC.ZDBC_Decode(_dbcHandle, _msgPtr, _canFramePtr, 1, frameType);
if (!ok)
{
return (string.Empty, new Dictionary<string, double>());
}
var msg = Marshal.PtrToStructure<ZDBC.DBCMessage>(_msgPtr);
return ExtractActualSignals(msg);
}
if (frameType == ZDBC.FT_CANFD)
{
var frame = ZlgNativeCanFdFrame.Create(canId, data ?? Array.Empty<byte>(), requestTxEcho: false);
Marshal.StructureToPtr(frame, _canFdFramePtr, false);
var ok = ZDBC.ZDBC_Decode(_dbcHandle, _msgPtr, _canFdFramePtr, 1, frameType);
if (!ok)
{
return (string.Empty, new Dictionary<string, double>());
}
var msg = Marshal.PtrToStructure<ZDBC.DBCMessage>(_msgPtr);
return ExtractActualSignals(msg);
}
throw new ArgumentOutOfRangeException(nameof(frameType), "frameType 仅支持 FT_CAN=0 或 FT_CANFD=1。");
}
}
/// <inheritdoc />
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<ZDBC.DBCMessage>(_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<string, double> Signals) ExtractActualSignals(ZDBC.DBCMessage msg)
{
var msgName = ByteArrayToAsciiString(msg.strName);
var dict = new Dictionary<string, double>(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));
}
}
}
}