周立功开发过程1

This commit is contained in:
2026-02-02 21:22:01 +08:00
parent 4d16b474c6
commit 2e8ad1cffa
42 changed files with 11571 additions and 122 deletions

View File

@@ -792,7 +792,7 @@ namespace CapMachine.Wpf.CanDrive
}
// 同步值到CAN消息
DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(msgName), msgPtSend);
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(msgName), msgPtSend);
var canMsg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG));
// 直接发送此消息,无需通过队列
@@ -947,7 +947,7 @@ namespace CapMachine.Wpf.CanDrive
itemSignal.SignalCmdValue = random.Next(0, 100);
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
Index++;
}
@@ -1003,7 +1003,7 @@ namespace CapMachine.Wpf.CanDrive
{
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
//每个分组就是一个帧指令/消息数据
SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
//分配当前消息帧在集合中的位置信息到ListCANScheduleConfig中方便实时更新时定位帧的位置
@@ -1120,99 +1120,6 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
private int CycleUpdateIndex = 0;
/// <summary>
/// 循环更新调度表的指令数据
/// 定时更新数据到调度表中
/// </summary>
public void StartCycleUpdateCmd()
{
CycleUpdateCmdTask = Task.Run(async () =>
{
while (IsCycleSend)
{
await Task.Delay(UpdateCycle);
try
{
//通过DBC进行对消息赋值
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
CycleUpdateIndex = 0;
//循环给MSG赋值数据顺序是固定的跟初始时设置是一样的
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
SchCanMsg[CycleUpdateIndex] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
CycleUpdateIndex++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
Marshal.FreeHGlobal(msgPtSend);
//CAN_UpdateSchedule 官网解释
// ---MsgTabIndex CAN调度表索引号
// ---MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面
// ---pCanMsg 需要更新的CAN帧指针
// ---MsgNum pCanMsgTab里面包含的有效帧数
//CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号
//因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg
//默认1号调度表一个更新所有的帧数据
var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CANFD.CANFD_SUCCESS)
{
IsSendOk = true;
Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)} ");
}
else
{
IsSendOk = false;
Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)}");
//return;
}
//一个报文帧一个报文帧进行更新数据
////配置信息 默认启用1号调度器,MsgTabIndex=0
//foreach (var itemMsgSchConfig in ListCANScheduleConfig)
//{
// //USB2CANFD.CAN_MSG[] SchCanMsg1=new CAN_MSG[1];
// //SchCanMsg1[0] = SchCanMsg[itemMsgSchConfig.MsgIndex];
// // MsgTabIndex CAN调度表索引号 ;MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面, ;
// // pCanMsg 需要更新的CAN帧指针,消息数据 ; MsgNum pCanMsgTab里面包含的有效帧数一个调度表对应一个帧/消息即为1 (byte)(itemMsgSchConfig.MsgIndex+0)
// var ret = USB2CANFD.CAN_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(itemMsgSchConfig.MsgIndex), SchCanMsg, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
// if (ret == USB2CANFD.CAN_SUCCESS)
// {
// Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)itemMsgSchConfig.SchTabIndex} -- MsgIndex{(byte)(itemMsgSchConfig.MsgIndex)} ");
// }
// else
// {
// Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex{(byte)itemMsgSchConfig.SchTabIndex} -- MsgIndex{(byte)(itemMsgSchConfig.MsgIndex)}");
// //return;
// }
//}
}
catch (Exception ex)
{
IsSendOk = false;
LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}");
}
}
IsSendOk = false;
});
}
/// <summary>
/// 加载要发送的数据
/// 一般是激活后才注册事件
@@ -1257,7 +1164,7 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpdateSchDataByCmdDataChanged()
public void UpdateSchDataByCmdDataChanged()
{
try
{
@@ -1284,7 +1191,7 @@ namespace CapMachine.Wpf.CanDrive
var SetSignalValue = DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
//monitorValueLog.UpdateValue1(SetSignalValue);
}
var SyncValueToCanMsg = DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
var SyncValueToCanMsg = DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
//monitorValueLog.UpdateValue2(SyncValueToCanMsg);
SchCanMsg[CycleUpdateIndex] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
CycleUpdateIndex++;
@@ -1309,7 +1216,7 @@ namespace CapMachine.Wpf.CanDrive
if (ret == USB2CANFD.CANFD_SUCCESS)
{
IsSendOk = true;
//Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)} ");
Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)}-- SchCanMsg.Count(){(SchCanMsg.Count())} ");
//monitorValueLog.UpdateValue3(ret);
}
else
@@ -1469,14 +1376,14 @@ namespace CapMachine.Wpf.CanDrive
else if (CanNum == 0)
{
IsReviceOk = false;
Console.WriteLine("No CAN data!");
//Console.WriteLine("No CAN data!");
}
else
{
IsReviceOk = false;
Console.WriteLine("Get CAN data error!");
}
Console.WriteLine("");
//Console.WriteLine("");
//将CAN消息数据填充到信号里面
DBCParserByFD.DBC_SyncCANFDMsgToValue(DBCHandle, msgPtRead, CanNum);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
using System;
namespace CapMachine.Wpf.CanDrive.ZlgCan
{
/// <summary>
/// ZLG CANFD 通道初始化参数。
/// </summary>
public sealed class ZlgCanFdChannelOptions
{
/// <summary>
/// 仲裁域波特率单位bps。例如 500000。
/// </summary>
public uint ArbitrationBaudRate { get; set; } = 500000;
/// <summary>
/// 数据域波特率单位bps。例如 2000000。
/// </summary>
public uint DataBaudRate { get; set; } = 2000000;
/// <summary>
/// 终端电阻。
/// </summary>
public bool EnableInternalResistance { get; set; } = true;
/// <summary>
/// 仅监听模式。
/// </summary>
public bool ListenOnly { get; set; } = false;
/// <summary>
/// 是否启用总线利用率上报。
/// </summary>
public bool EnableBusUsage { get; set; } = false;
/// <summary>
/// 总线利用率上报周期单位ms
/// </summary>
public int BusUsagePeriodMs { get; set; } = 500;
/// <summary>
/// 是否启用设备层“合并接收”ZCAN_ReceiveData
/// </summary>
public bool EnableMergeReceive { get; set; } = false;
/// <summary>
/// 合并接收缓冲区最大帧数量。
/// </summary>
public int MergeReceiveBufferFrames { get; set; } = 100;
}
/// <summary>
/// ZLG LIN 通道初始化参数。
/// </summary>
public sealed class ZlgLinChannelOptions
{
/// <summary>
/// LIN 模式true=主节点false=从节点。
/// </summary>
public bool IsMaster { get; set; } = true;
/// <summary>
/// 校验模式。
/// 1-经典校验2-增强校验3-自动。
/// </summary>
public byte ChecksumMode { get; set; } = 3;
/// <summary>
/// 最大数据长度8~64
/// </summary>
public byte MaxLength { get; set; } = 8;
/// <summary>
/// 波特率1000~20000
/// </summary>
public uint BaudRate { get; set; } = 19200;
/// <summary>
/// LIN 接收轮询等待时间ms
/// </summary>
public int ReceiveWaitMs { get; set; } = 10;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
using System;
namespace CapMachine.Wpf.CanDrive.ZlgCan
{
/// <summary>
/// ZLG CAN ID 帮助类。
/// </summary>
public static class ZlgCanIdHelper
{
/// <summary>
/// 生成 ZLG/SocketCAN 风格的 can_id包含扩展帧/远程帧/错误帧标志位)。
/// </summary>
/// <param name="id">CAN 标识符(标准帧 11bit0~0x7FF扩展帧 29bit0~0x1FFFFFFF。</param>
/// <param name="isExtended">是否为扩展帧。</param>
/// <param name="isRemote">是否为远程帧RTR。</param>
/// <param name="isError">是否为错误帧。</param>
/// <returns>包含标志位的 can_id。</returns>
public static uint MakeCanId(uint id, bool isExtended, bool isRemote, bool isError)
{
// 兼容官方样例写法bit31 扩展帧bit30 RTRbit29 ERR
// 注意:该函数不做强约束校验(例如扩展帧 29bit上层可按需要增加校验。
uint canId = id & 0x1FFFFFFF;
if (isExtended)
{
canId |= 1u << 31;
}
if (isRemote)
{
canId |= 1u << 30;
}
if (isError)
{
canId |= 1u << 29;
}
return canId;
}
/// <summary>
/// 从 can_id 中提取 29bit 标识符。
/// </summary>
/// <param name="canId">包含标志位的 can_id。</param>
/// <returns>29bit 标识符。</returns>
public static uint GetArbitrationId(uint canId)
{
return canId & 0x1FFFFFFF;
}
/// <summary>
/// 判断 can_id 是否为扩展帧。
/// </summary>
/// <param name="canId">包含标志位的 can_id。</param>
/// <returns>是否扩展帧。</returns>
public static bool IsExtended(uint canId)
{
return (canId & (1u << 31)) != 0;
}
}
}

View File

@@ -0,0 +1,570 @@
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));
}
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Runtime.InteropServices;
namespace CapMachine.Wpf.CanDrive.ZlgCan
{
/// <summary>
/// 供 DBC 编解码/Marshal 使用的原生 CAN 帧结构(与 zlgcan can_frame 二进制布局兼容)。
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ZlgNativeCanFrame
{
public uint can_id;
public byte can_dlc;
public byte __pad;
public byte __res0;
public byte __res1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] data;
/// <summary>
/// 创建 CAN 帧。
/// </summary>
/// <param name="canId">can_id包含扩展帧等标志位。</param>
/// <param name="payload">数据0~8。</param>
/// <param name="requestTxEcho">是否请求发送回显。</param>
/// <returns>原生 CAN 帧。</returns>
public static ZlgNativeCanFrame Create(uint canId, byte[] payload, bool requestTxEcho)
{
var len = Math.Min(8, payload?.Length ?? 0);
var frame = new ZlgNativeCanFrame
{
can_id = canId,
can_dlc = (byte)len,
__pad = 0,
__res0 = 0,
__res1 = 0,
data = new byte[8]
};
if (len > 0 && payload != null)
{
Array.Copy(payload, frame.data, len);
}
if (requestTxEcho)
{
frame.__pad |= 0x20;
}
return frame;
}
}
/// <summary>
/// 供 DBC 编解码/Marshal 使用的原生 CANFD 帧结构(与 zlgcan canfd_frame 二进制布局兼容)。
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ZlgNativeCanFdFrame
{
public uint can_id;
public byte len;
public byte flags;
public byte __res0;
public byte __res1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] data;
/// <summary>
/// 创建 CANFD 帧。
/// </summary>
/// <param name="canId">can_id包含扩展帧等标志位。</param>
/// <param name="payload">数据0~64。</param>
/// <param name="requestTxEcho">是否请求发送回显。</param>
/// <returns>原生 CANFD 帧。</returns>
public static ZlgNativeCanFdFrame Create(uint canId, byte[] payload, bool requestTxEcho)
{
var len = Math.Min(64, payload?.Length ?? 0);
var frame = new ZlgNativeCanFdFrame
{
can_id = canId,
len = (byte)len,
flags = 0,
__res0 = 0,
__res1 = 0,
data = new byte[64]
};
if (len > 0 && payload != null)
{
Array.Copy(payload, frame.data, len);
}
if (requestTxEcho)
{
frame.flags |= 0x20;
}
return frame;
}
}
}