using CapMachine.Wpf.CanDrive;
using CapMachine.Wpf.Services;
using Prism.Mvvm;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CapMachine.Wpf.CanDrive.ZlgCan
{
///
/// 周立功 USBCANFD-200U 工程化封装(CAN/CANFD/LIN)。
///
public sealed class ZlgCanFd200uDriver : BindableBase, IDisposable
{
private readonly object _sync = new object();
private readonly ILogService _log;
private static IntPtr _preloadedZlgcanHandle = IntPtr.Zero;
private static string? _preloadedZlgcanError;
private IntPtr _deviceHandle = IntPtr.Zero;
private readonly IntPtr[] _canChannelHandles = new IntPtr[2];
private IntPtr _linChannelHandle = IntPtr.Zero;
private CancellationTokenSource? _recvCts;
private Task? _recvTask;
private volatile bool _disposed;
private readonly object _dbcSync = new object();
private ZlgDbcDatabase? _dbc;
private ObservableCollection? _dbcModels;
private Dictionary? _dbcModelIndex;
private List? _cmdData;
private int _cmdSendChannelIndex;
private byte _cmdSendFrameType;
///
/// 构造函数。
///
/// 日志服务。
public ZlgCanFd200uDriver(ILogService logService)
{
_log = logService;
}
///
/// 安全关闭设备与通道(需在 _sync 锁内调用)。
///
///
/// - 该方法会尽力调用 Reset/Close,并在异常时记录日志且继续执行释放流程;
/// - 该方法不会停止接收线程,调用方应优先 StopReceiveLoop。
///
private bool _openState;
///
/// 设备打开状态。
///
public bool OpenState
{
get { return _openState; }
private set { _openState = value; RaisePropertyChanged(); }
}
private bool _isReceiving;
///
/// 是否正在接收。
///
public bool IsReceiving
{
get { return _isReceiving; }
private set { _isReceiving = value; RaisePropertyChanged(); }
}
private bool _isSendOk;
///
/// 最近是否发生过“发送成功”(用于 UI 指示灯)。
///
public bool IsSendOk
{
get { return _isSendOk; }
private set { _isSendOk = value; RaisePropertyChanged(); }
}
private bool _isReviceOk;
///
/// 最近是否发生过“接收成功”(用于 UI 指示灯)。
///
public bool IsReviceOk
{
get { return _isReviceOk; }
private set { _isReviceOk = value; RaisePropertyChanged(); }
}
private int _sendOkToken;
private int _reviceOkToken;
private void MarkSendOk(int holdMs = 800)
{
var token = Interlocked.Increment(ref _sendOkToken);
IsSendOk = true;
Task.Run(async () =>
{
await Task.Delay(Math.Max(50, holdMs));
if (token == _sendOkToken)
{
IsSendOk = false;
}
});
}
private void MarkReviceOk(int holdMs = 800)
{
var token = Interlocked.Increment(ref _reviceOkToken);
IsReviceOk = true;
Task.Run(async () =>
{
await Task.Delay(Math.Max(50, holdMs));
if (token == _reviceOkToken)
{
IsReviceOk = false;
}
});
}
///
/// CAN/CANFD 原始帧接收事件。
///
public event Action? CanFrameReceived;
///
/// LIN 原始帧接收事件。
///
public event Action? LinFrameReceived;
private bool _dbcDecodeEnabled;
///
/// 是否启用 DBC 解码更新(接收帧触发)。
///
public bool DbcDecodeEnabled
{
get { return _dbcDecodeEnabled; }
set { _dbcDecodeEnabled = value; RaisePropertyChanged(); }
}
private bool _isCycleSend;
///
/// 是否启用“事件驱动发送”。
///
///
/// 与现有 ToomossCan/ToomossCanFD 行为对齐:只有 IsCycleSend=true 且 SchEnable=true 时,
/// 才会在 CmdDataChanged 事件中触发增量下发。
///
public bool IsCycleSend
{
get { return _isCycleSend; }
set { _isCycleSend = value; RaisePropertyChanged(); }
}
private bool _schEnable;
///
/// 发送使能(与 UI 的调度表使能语义对齐)。
///
public bool SchEnable
{
get { return _schEnable; }
set { _schEnable = value; RaisePropertyChanged(); }
}
///
/// 打开设备(不初始化 CAN/LIN 通道)。
///
/// 设备索引(通常 0)。
public void OpenDevice(uint deviceIndex)
{
ThrowIfDisposed();
lock (_sync)
{
if (_deviceHandle != IntPtr.Zero)
{
OpenState = true;
return;
}
EnsureNativeDllExists("zlgcan.dll");
var deviceType = ZLGCAN.ZCAN_USBCANFD_200U;
try
{
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(deviceType, deviceIndex, 0);
}
catch (Exception ex)
{
var baseDir = AppContext.BaseDirectory;
var dllFullPath = Path.Combine(baseDir, "zlgcan.dll");
var msg = $"ZCAN_OpenDevice 调用异常。deviceType={deviceType}, deviceIndex={deviceIndex}, " +
$"Is64BitProcess={Environment.Is64BitProcess}, ProcessArch={RuntimeInformation.ProcessArchitecture}, " +
$"DllPath={dllFullPath}, BaseDir={baseDir}。异常:{ex.Message}";
_log.Error(msg);
throw new InvalidOperationException(msg, ex);
}
if (_deviceHandle == IntPtr.Zero)
{
var lastError = Marshal.GetLastWin32Error();
var lastErrorMsg = string.Empty;
try
{
lastErrorMsg = new Win32Exception(lastError).Message;
}
catch
{
}
var baseDir = AppContext.BaseDirectory;
var dllFullPath = Path.Combine(baseDir, "zlgcan.dll");
var loadedModulePath = GetLoadedModulePath("zlgcan.dll") ?? string.Empty;
var depDiag = string.Join(" | ", new[]
{
BuildDllDiag(baseDir, "zlgcan.dll"),
BuildDllDiag(baseDir, "USB2XXX.dll"),
BuildDllDiag(baseDir, "libusb-1.0.dll"),
BuildDllDiag(baseDir, "zdbc.dll"),
BuildDllDiag(baseDir, "kerneldlls\\USBCANFD.dll"),
BuildDllDiag(baseDir, "kerneldlls\\CANDevCore.dll"),
BuildDllDiag(baseDir, "kerneldlls\\CANDevice.dll"),
BuildDllDiag(baseDir, "kerneldlls\\CANFDCOM.dll"),
BuildDllDiag(baseDir, "kerneldlls\\CANFDNET.dll"),
BuildDllDiag(baseDir, "kerneldlls\\ZPSCANFD.dll"),
BuildDllDiag(baseDir, "kerneldlls\\USBCANFD800U.dll"),
BuildDllDiag(baseDir, "kerneldlls\\USBCAN.dll"),
BuildDllDiag(baseDir, "kerneldlls\\usbcan.dll"),
}.Where(s => !string.IsNullOrWhiteSpace(s)));
string dllVer = string.Empty;
try
{
if (File.Exists(dllFullPath))
{
var vi = FileVersionInfo.GetVersionInfo(dllFullPath);
dllVer = vi?.FileVersion ?? string.Empty;
}
}
catch
{
}
var msg = $"ZCAN_OpenDevice 失败。deviceType={deviceType}, deviceIndex={deviceIndex}。" +
$"Win32LastError={lastError}(0x{lastError:X}){(string.IsNullOrWhiteSpace(lastErrorMsg) ? string.Empty : $"({lastErrorMsg})")}。" +
$"Is64BitProcess={Environment.Is64BitProcess}, ProcessArch={RuntimeInformation.ProcessArchitecture}。" +
$"DllPath={dllFullPath}, DllVersion={dllVer}, LoadedModulePath={loadedModulePath}, BaseDir={baseDir}。" +
$"PreloadError={_preloadedZlgcanError ?? string.Empty}。" +
$"DirDllDiag={depDiag}。" +
"请确认:1) 已安装ZLG驱动;2) 设备已连接且未被其它程序占用;3) 程序位数与 zlgcan.dll 匹配;4) 设备类型/索引正确。";
_log.Error(msg);
throw new InvalidOperationException(msg);
}
OpenState = true;
}
}
///
/// 预加载指定路径的原生 DLL,尽量避免被 PATH 中的同名 DLL 干扰。
///
/// DLL 完整路径。
private static void TryPreloadNativeDll(string dllFullPath)
{
try
{
if (_preloadedZlgcanHandle != IntPtr.Zero)
{
return;
}
if (!string.IsNullOrWhiteSpace(_preloadedZlgcanError))
{
return;
}
_preloadedZlgcanHandle = NativeLibrary.Load(dllFullPath);
}
catch (Exception ex)
{
_preloadedZlgcanError = $"{ex.GetType().Name}: {ex.Message}";
}
}
///
/// 构造输出目录下某个 DLL 的诊断信息(架构/大小/版本)。
///
/// 输出目录。
/// DLL 文件名。
/// 诊断信息字符串;不存在返回 "xxx:not_found";异常返回 null。
private static string? BuildDllDiag(string baseDir, string fileName)
{
try
{
if (string.IsNullOrWhiteSpace(baseDir) || string.IsNullOrWhiteSpace(fileName))
{
return null;
}
var full = Path.Combine(baseDir, fileName);
if (!File.Exists(full))
{
return $"{fileName}:not_found";
}
var arch = TryGetPeArchitecture(full) ?? string.Empty;
long size = 0;
try
{
size = new FileInfo(full).Length;
}
catch
{
}
var ver = string.Empty;
try
{
var vi = FileVersionInfo.GetVersionInfo(full);
ver = vi?.FileVersion ?? string.Empty;
}
catch
{
}
return $"{fileName}:arch={arch},size={size},ver={ver}";
}
catch
{
return null;
}
}
///
/// 获取当前进程中已加载模块的完整路径。
///
/// 模块文件名(如 zlgcan.dll)。
/// 完整路径;获取失败返回 null。
private static string? GetLoadedModulePath(string moduleFileName)
{
try
{
if (string.IsNullOrWhiteSpace(moduleFileName))
{
return null;
}
using var p = Process.GetCurrentProcess();
foreach (ProcessModule m in p.Modules)
{
if (string.Equals(m.ModuleName, moduleFileName, StringComparison.OrdinalIgnoreCase))
{
return m.FileName;
}
}
return null;
}
catch
{
return null;
}
}
///
/// 打开设备并初始化 CANFD 通道。
///
/// 设备索引(通常 0)。
/// 通道0配置。
/// 通道1配置(可为 null 表示不初始化)。
public void OpenAndInitCan(uint deviceIndex, ZlgCanFdChannelOptions channel0, ZlgCanFdChannelOptions? channel1 = null)
{
ThrowIfDisposed();
lock (_sync)
{
if (_deviceHandle == IntPtr.Zero)
{
OpenDevice(deviceIndex);
}
try
{
InitCanChannelInternal(0, channel0);
_canChannelHandles[0] = StartCanChannelInternal(0);
if (channel1 != null)
{
InitCanChannelInternal(1, channel1);
_canChannelHandles[1] = StartCanChannelInternal(1);
}
// 合并接收:这是设备级能力,按通道0的配置为准(两通道同时开启/关闭)
var mergeEnable = channel0.EnableMergeReceive;
var mergeRet = ZLGCAN.ZCAN_SetValue(_deviceHandle, "0/set_device_recv_merge", mergeEnable ? "1" : "0");
if (mergeRet != 1)
{
_log.Warn("设置合并接收失败(0/set_device_recv_merge)。将继续运行,但接收模式可能不符合预期。");
}
}
catch
{
SafeClose_NoLock();
throw;
}
}
}
///
/// 初始化并启动 LIN 通道。
///
/// LIN 通道索引(通常 0)。
/// LIN 初始化参数。
public void OpenAndInitLin(uint linIndex, ZlgLinChannelOptions options)
{
ThrowIfDisposed();
lock (_sync)
{
if (_deviceHandle == IntPtr.Zero)
{
OpenDevice(0);
}
if (_linChannelHandle != IntPtr.Zero)
{
throw new InvalidOperationException("LIN 通道已初始化。");
}
var cfg = new ZLGCAN.ZCAN_LIN_INIT_CONFIG
{
linMode = (byte)(options.IsMaster ? 1 : 0),
chkSumMode = options.ChecksumMode,
maxLength = options.MaxLength,
reserved = 0,
libBaud = options.BaudRate
};
IntPtr pCfg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_INIT_CONFIG)));
try
{
Marshal.StructureToPtr(cfg, pCfg, false);
_linChannelHandle = ZLGCAN.ZCAN_InitLIN(_deviceHandle, linIndex, pCfg);
}
finally
{
Marshal.FreeHGlobal(pCfg);
}
if (_linChannelHandle == IntPtr.Zero)
{
throw new InvalidOperationException("ZCAN_InitLIN 失败。");
}
var ret = ZLGCAN.ZCAN_StartLIN(_linChannelHandle);
if (ret != 1)
{
throw new InvalidOperationException("ZCAN_StartLIN 失败。");
}
}
}
///
/// 启动后台接收线程。
///
/// 是否启用合并接收(ZCAN_ReceiveData)。建议与通道初始化时的 EnableMergeReceive 保持一致。
/// 接收缓冲最大帧数(合并接收固定数组大小,过小会丢帧)。
public void StartReceiveLoop(bool mergeReceive, int bufferFrames = 100)
{
ThrowIfDisposed();
lock (_sync)
{
if (_deviceHandle == IntPtr.Zero)
{
throw new InvalidOperationException("设备未打开。");
}
if (_recvTask != null)
{
throw new InvalidOperationException("接收线程已启动。");
}
if (bufferFrames <= 0) bufferFrames = 1;
_recvCts = new CancellationTokenSource();
var token = _recvCts.Token;
_recvTask = Task.Run(() =>
{
IsReceiving = true;
try
{
if (mergeReceive)
{
ReceiveLoop_Merge(token, bufferFrames);
}
else
{
ReceiveLoop_PerChannel(token, bufferFrames);
}
}
catch (Exception ex)
{
_log.Error($"ZLG 接收线程异常退出:{ex.Message}");
}
finally
{
IsReceiving = false;
}
}, token);
}
}
///
/// 加载 DBC 文件,并生成 UI 绑定用的信号集合。
///
/// DBC 文件路径。
/// 是否启用异步解析。
/// 是否合并加载。
/// 协议类型。
/// CanDbcModel 集合。
public ObservableCollection StartDbc(string dbcPath, bool enableAsyncAnalyse = true, bool merge = false, byte protocolType = ZDBC.PROTOCOL_OTHER)
{
ThrowIfDisposed();
lock (_dbcSync)
{
_dbc?.Dispose();
_dbc = new ZlgDbcDatabase(_log);
_dbc.Load(dbcPath, enableAsyncAnalyse, merge, protocolType);
_dbcModels = _dbc.BuildCanDbcModels();
_dbcModelIndex = BuildDbcModelIndex(_dbcModels);
DbcDecodeEnabled = true;
return _dbcModels;
}
}
///
/// 设置并订阅要发送的指令集合(事件驱动)。
///
/// 指令集合。
/// 发送通道(0/1)。
/// 帧类型:ZDBC.FT_CAN 或 ZDBC.FT_CANFD。
public void LoadCmdDataToDrive(List cmdData, int channelIndex, byte frameType)
{
ThrowIfDisposed();
lock (_dbcSync)
{
if (_cmdData != null)
{
foreach (var old in _cmdData)
{
old.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler;
}
}
_cmdData = cmdData;
_cmdSendChannelIndex = channelIndex;
_cmdSendFrameType = frameType;
if (_cmdData != null)
{
foreach (var item in _cmdData)
{
item.CanCmdDataChangedHandler += CmdData_CanCmdDataChangedHandler;
}
}
}
}
///
/// 事件驱动回调:某个 MsgName 的信号值变更时,仅增量编码并下发该消息。
///
/// 发送方。
/// 发生变化的消息名称。
private void CmdData_CanCmdDataChangedHandler(object? sender, string msgName)
{
try
{
if (!IsCycleSend) return;
if (!SchEnable) return;
SendOneMsgByCmdData(msgName, _cmdSendChannelIndex, _cmdSendFrameType);
}
catch (Exception ex)
{
_log.Warn($"事件驱动发送异常:{ex.Message}");
}
}
///
/// 根据 MsgName,从当前 CmdData 中取出信号值,编码并发送一帧。
///
/// 消息名称。
/// 发送通道。
/// 帧类型:ZDBC.FT_CAN 或 ZDBC.FT_CANFD。
public void SendOneMsgByCmdData(string msgName, int channelIndex, byte frameType)
{
ThrowIfDisposed();
ZlgDbcDatabase? dbc;
List? cmdData;
lock (_dbcSync)
{
dbc = _dbc;
cmdData = _cmdData;
}
if (dbc == null || !dbc.IsLoaded)
{
return;
}
if (cmdData == null || cmdData.Count == 0)
{
return;
}
if (string.IsNullOrWhiteSpace(msgName))
{
return;
}
var dict = new Dictionary(StringComparer.Ordinal);
foreach (var item in cmdData)
{
if (!string.Equals(item.MsgName, msgName, StringComparison.Ordinal))
{
continue;
}
if (string.IsNullOrWhiteSpace(item.SignalName))
{
continue;
}
dict[item.SignalName] = item.SignalCmdValue;
}
if (dict.Count == 0)
{
return;
}
var (canId, data, dataLen) = dbc.EncodeToRawFrame(msgName, dict, frameType);
if (frameType == ZDBC.FT_CAN)
{
SendCan(channelIndex, canId, data, requestTxEcho: true);
return;
}
if (frameType == ZDBC.FT_CANFD)
{
SendCanFd(channelIndex, canId, data, requestTxEcho: true);
return;
}
throw new ArgumentOutOfRangeException(nameof(frameType), "frameType 仅支持 ZDBC.FT_CAN=0 或 ZDBC.FT_CANFD=1。");
}
///
/// 停止后台接收线程。
///
public void StopReceiveLoop()
{
lock (_sync)
{
if (_recvCts == null || _recvTask == null)
{
return;
}
try
{
_recvCts.Cancel();
_recvTask.Wait(TimeSpan.FromSeconds(2));
}
catch (Exception ex)
{
_log.Warn($"停止接收线程等待超时或异常:{ex.Message}");
}
finally
{
_recvCts.Dispose();
_recvCts = null;
_recvTask = null;
}
}
}
///
/// 发送 CAN 报文。
///
/// 通道索引(0/1)。
/// 包含扩展帧标志位的 can_id(可用 ZlgCanIdHelper.MakeCanId 生成)。
/// 数据(0~8字节)。
/// 是否请求发送回显。
/// 发送方式,0=正常发送,1=单次发送,2=自发自收,3=单次自发自收。
public uint SendCan(int channelIndex, uint canId, byte[] data, bool requestTxEcho = false, uint transmitType = 0)
{
ThrowIfDisposed();
var chn = GetCanChannelHandleOrThrow(channelIndex);
var frame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = (byte)Math.Min(8, data?.Length ?? 0),
__pad = 0,
__res0 = 0,
__res1 = 0,
data = new byte[8]
};
if (data != null && data.Length > 0)
{
Array.Copy(data, frame.data, Math.Min(8, data.Length));
}
if (requestTxEcho)
{
frame.__pad |= 0x20;
}
var tx = new ZLGCAN.ZCAN_Transmit_Data
{
frame = frame,
transmit_type = transmitType
};
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data)));
try
{
Marshal.StructureToPtr(tx, pTx, false);
var ret = ZLGCAN.ZCAN_Transmit(chn, pTx, 1);
if (ret > 0)
{
MarkSendOk();
}
return ret;
}
finally
{
Marshal.FreeHGlobal(pTx);
}
}
///
/// 发送 CANFD 报文。
///
/// 通道索引(0/1)。
/// 包含扩展帧标志位的 can_id(可用 ZlgCanIdHelper.MakeCanId 生成)。
/// 数据(0~64字节)。
/// 是否请求发送回显。
/// 发送方式,0=正常发送,1=单次发送,2=自发自收,3=单次自发自收。
public uint SendCanFd(int channelIndex, uint canId, byte[] data, bool requestTxEcho = false, uint transmitType = 0)
{
ThrowIfDisposed();
var chn = GetCanChannelHandleOrThrow(channelIndex);
var len = (byte)Math.Min(64, data?.Length ?? 0);
var frame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = len,
flags = 0,
__res0 = 0,
__res1 = 0,
data = new byte[64]
};
if (data != null && data.Length > 0)
{
Array.Copy(data, frame.data, Math.Min(64, data.Length));
}
if (requestTxEcho)
{
frame.flags |= 0x20;
}
var tx = new ZLGCAN.ZCAN_TransmitFD_Data
{
frame = frame,
transmit_type = transmitType
};
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data)));
try
{
Marshal.StructureToPtr(tx, pTx, false);
var ret = ZLGCAN.ZCAN_TransmitFD(chn, pTx, 1);
if (ret > 0)
{
MarkSendOk();
}
return ret;
}
finally
{
Marshal.FreeHGlobal(pTx);
}
}
///
/// 设置设备定时发送(CAN)。
///
/// 通道索引。
/// 定时任务索引。
/// 是否使能。
/// 周期(ms)。
/// can_id。
/// 数据(0~8)。
public void ConfigureAutoSendCan(int channelIndex, ushort taskIndex, bool enable, uint intervalMs, uint canId, byte[] data)
{
ThrowIfDisposed();
if (_deviceHandle == IntPtr.Zero)
{
throw new InvalidOperationException("设备未打开。");
}
var frame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = (byte)Math.Min(8, data?.Length ?? 0),
__pad = 0x20, // 默认发送回显
__res0 = 0,
__res1 = 0,
data = new byte[8]
};
if (data != null && data.Length > 0)
{
Array.Copy(data, frame.data, Math.Min(8, data.Length));
}
var obj = new ZLGCAN.ZCAN_AUTO_TRANSMIT_OBJ
{
enable = (ushort)(enable ? 1 : 0),
index = taskIndex,
interval = intervalMs,
obj = new ZLGCAN.ZCAN_Transmit_Data { frame = frame, transmit_type = 0 }
};
var path = string.Format("{0}/auto_send", channelIndex);
var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, ref obj);
if (ret != 1)
{
throw new InvalidOperationException($"配置定时发送失败:{path}");
}
}
///
/// 启动通道定时发送任务(apply_auto_send)。
///
/// 通道索引。
public void ApplyAutoSend(int channelIndex)
{
ThrowIfDisposed();
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("设备未打开。");
var path = string.Format("{0}/apply_auto_send", channelIndex);
var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "0");
if (ret != 1)
{
throw new InvalidOperationException($"启动定时发送失败:{path}");
}
}
///
/// 清空通道定时发送任务(clear_auto_send)。
///
/// 通道索引。
public void ClearAutoSend(int channelIndex)
{
ThrowIfDisposed();
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("设备未打开。");
var path = string.Format("{0}/clear_auto_send", channelIndex);
var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "0");
if (ret != 1)
{
throw new InvalidOperationException($"清空定时发送失败:{path}");
}
}
///
/// 配置 LIN 发布表。
///
/// 发布配置集合。
public void SetLinPublish(IEnumerable publishCfg)
{
ThrowIfDisposed();
if (_linChannelHandle == IntPtr.Zero) throw new InvalidOperationException("LIN 未初始化。");
var list = publishCfg?.ToList() ?? new List();
if (list.Count == 0)
{
return;
}
int size = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_PUBLISH_CFG));
IntPtr pArr = Marshal.AllocHGlobal(size * list.Count);
try
{
for (int i = 0; i < list.Count; i++)
{
var item = list[i];
if (item.data == null || item.data.Length != 8)
{
item.data = new byte[8];
}
if (item.reserved == null || item.reserved.Length != 5)
{
item.reserved = new byte[5];
}
Marshal.StructureToPtr(item, IntPtr.Add(pArr, i * size), false);
}
var ret = ZLGCAN.ZCAN_SetLINPublish(_linChannelHandle, pArr, (uint)list.Count);
if (ret != 1)
{
throw new InvalidOperationException("ZCAN_SetLINPublish 失败。");
}
}
finally
{
Marshal.FreeHGlobal(pArr);
}
}
///
/// 配置 LIN 订阅表。
///
/// 订阅配置集合。
public void SetLinSubscribe(IEnumerable subscribeCfg)
{
ThrowIfDisposed();
if (_linChannelHandle == IntPtr.Zero) throw new InvalidOperationException("LIN 未初始化。");
var list = subscribeCfg?.ToList() ?? new List();
if (list.Count == 0)
{
return;
}
int size = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_LIN_SUBSCIBE_CFG));
IntPtr pArr = Marshal.AllocHGlobal(size * list.Count);
try
{
for (int i = 0; i < list.Count; i++)
{
var item = list[i];
if (item.reserved == null || item.reserved.Length != 5)
{
item.reserved = new byte[5];
}
Marshal.StructureToPtr(item, IntPtr.Add(pArr, i * size), false);
}
var ret = ZLGCAN.ZCAN_SetLINSubscribe(_linChannelHandle, pArr, (uint)list.Count);
if (ret != 1)
{
throw new InvalidOperationException("ZCAN_SetLINSubscribe 失败。");
}
}
finally
{
Marshal.FreeHGlobal(pArr);
}
}
///
/// 关闭设备。
///
public void Close()
{
lock (_sync)
{
StopReceiveLoop();
SafeClose_NoLock();
}
lock (_dbcSync)
{
if (_cmdData != null)
{
foreach (var item in _cmdData)
{
item.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler;
}
}
_cmdData = null;
_dbcModels = null;
_dbcModelIndex = null;
_dbc?.Dispose();
_dbc = null;
}
}
///
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
Close();
}
catch
{
// ignored
}
}
private void SafeClose_NoLock()
{
try
{
if (_linChannelHandle != IntPtr.Zero)
{
var ret = ZLGCAN.ZCAN_ResetLIN(_linChannelHandle);
if (ret != 1)
{
_log.Warn("ZCAN_ResetLIN 失败。");
}
}
}
catch (Exception ex)
{
_log.Warn($"关闭 LIN 异常:{ex.Message}");
}
finally
{
_linChannelHandle = IntPtr.Zero;
}
for (int i = 0; i < _canChannelHandles.Length; i++)
{
try
{
if (_canChannelHandles[i] != IntPtr.Zero)
{
var ret = ZLGCAN.ZCAN_ResetCAN(_canChannelHandles[i]);
if (ret != 1)
{
_log.Warn($"ZCAN_ResetCAN 失败,通道{i}。");
}
}
}
catch (Exception ex)
{
_log.Warn($"关闭 CAN 通道{i}异常:{ex.Message}");
}
finally
{
_canChannelHandles[i] = IntPtr.Zero;
}
}
try
{
if (_deviceHandle != IntPtr.Zero)
{
var ret = ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
if (ret != 1)
{
_log.Warn("ZCAN_CloseDevice 失败。");
}
}
}
catch (Exception ex)
{
_log.Warn($"关闭设备异常:{ex.Message}");
}
finally
{
_deviceHandle = IntPtr.Zero;
OpenState = false;
}
}
///
/// 初始化指定 CANFD 通道(仅设置参数并 InitCAN,不负责 StartCAN)。
///
/// 通道索引。
/// 通道初始化参数。
private void InitCanChannelInternal(int chnIdx, ZlgCanFdChannelOptions options)
{
var path = string.Format("{0}/canfd_abit_baud_rate", chnIdx);
var ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.ArbitrationBaudRate.ToString());
if (ret != 1)
{
throw new InvalidOperationException($"设置仲裁域波特率失败:{path}");
}
path = string.Format("{0}/canfd_dbit_baud_rate", chnIdx);
ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.DataBaudRate.ToString());
if (ret != 1)
{
throw new InvalidOperationException($"设置数据域波特率失败:{path}");
}
path = string.Format("{0}/initenal_resistance", chnIdx);
ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.EnableInternalResistance ? "1" : "0");
if (ret != 1)
{
throw new InvalidOperationException($"设置终端电阻失败:{path}");
}
if (options.EnableBusUsage)
{
path = string.Format("{0}/set_bus_usage_enable", chnIdx);
ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, "1");
if (ret != 1)
{
_log.Warn($"启用总线利用率失败:{path}");
}
path = string.Format("{0}/set_bus_usage_period", chnIdx);
ret = ZLGCAN.ZCAN_SetValue(_deviceHandle, path, options.BusUsagePeriodMs.ToString());
if (ret != 1)
{
_log.Warn($"设置总线利用率周期失败:{path}");
}
}
var initCfg = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG
{
can_type = 1,
config = new ZLGCAN._ZCAN_CHANNEL_INIT_CONFIG
{
canfd = new ZLGCAN._ZCAN_CHANNEL_CANFD_INIT_CONFIG
{
acc_code = 0,
acc_mask = 0xFFFFFFFF,
abit_timing = 0,
dbit_timing = 0,
brp = 0,
filter = 0,
mode = (byte)(options.ListenOnly ? 1 : 0),
pad = 0,
reserved = 0
}
}
};
var chnHandle = ZLGCAN.ZCAN_InitCAN(_deviceHandle, (uint)chnIdx, ref initCfg);
if (chnHandle == IntPtr.Zero)
{
throw new InvalidOperationException($"初始化 CANFD 通道失败:{chnIdx}");
}
_canChannelHandles[chnIdx] = chnHandle;
}
///
/// 启动指定 CAN 通道。
///
/// 通道索引。
/// 通道句柄。
private IntPtr StartCanChannelInternal(int chnIdx)
{
var h = _canChannelHandles[chnIdx];
if (h == IntPtr.Zero)
{
throw new InvalidOperationException("CAN 通道句柄为空。");
}
var ret = ZLGCAN.ZCAN_StartCAN(h);
if (ret != 1)
{
throw new InvalidOperationException($"启动 CAN 通道失败:{chnIdx}");
}
return h;
}
///
/// 获取 CAN 通道句柄(不存在则抛异常)。
///
/// 通道索引。
/// 通道句柄。
private IntPtr GetCanChannelHandleOrThrow(int channelIndex)
{
if (channelIndex < 0 || channelIndex >= _canChannelHandles.Length)
{
throw new ArgumentOutOfRangeException(nameof(channelIndex));
}
var h = _canChannelHandles[channelIndex];
if (h == IntPtr.Zero)
{
throw new InvalidOperationException($"CAN 通道{channelIndex}未初始化。");
}
return h;
}
///
/// 普通接收模式:按通道轮询 CAN/CANFD 缓冲区读取。
///
/// 取消令牌。
/// 每次接收的最大帧数。
private void ReceiveLoop_PerChannel(CancellationToken token, int bufferFrames)
{
const int WaitMs = 10;
int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
IntPtr pCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferFrames);
IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferFrames);
try
{
while (!token.IsCancellationRequested)
{
for (int ch = 0; ch < _canChannelHandles.Length; ch++)
{
var handle = _canChannelHandles[ch];
if (handle == IntPtr.Zero) continue;
uint canNum = ZLGCAN.ZCAN_GetReceiveNum(handle, 0);
if (canNum > 0)
{
uint actual = ZLGCAN.ZCAN_Receive(handle, pCanBuffer, (uint)bufferFrames, WaitMs);
for (int i = 0; i < actual; i++)
{
IntPtr pCur = IntPtr.Add(pCanBuffer, i * canStructSize);
var rec = Marshal.PtrToStructure(pCur);
RaiseCanFrame(ch, rec.timestamp, false, rec.frame.can_id, rec.frame.data, rec.frame.can_dlc, (rec.frame.__pad & 0x20) == 0x20);
}
}
uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(handle, 1);
if (canfdNum > 0)
{
uint actual = ZLGCAN.ZCAN_ReceiveFD(handle, pCanfdBuffer, (uint)bufferFrames, WaitMs);
for (int i = 0; i < actual; i++)
{
IntPtr pCur = IntPtr.Add(pCanfdBuffer, i * canfdStructSize);
var rec = Marshal.PtrToStructure(pCur);
RaiseCanFrame(ch, rec.timestamp, true, rec.frame.can_id, rec.frame.data, rec.frame.len, (rec.frame.flags & 0x20) == 0x20);
}
}
ZLGCAN.ZCAN_CHANNEL_ERR_INFO err = new ZLGCAN.ZCAN_CHANNEL_ERR_INFO
{
error_code = 0,
passive_ErrData = new byte[3],
arLost_ErrData = 0
};
try
{
ZLGCAN.ZCAN_ReadChannelErrInfo(handle, ref err);
if (err.error_code != 0)
{
_log.Warn($"CAN通道{ch}错误码:0x{err.error_code:X}");
}
}
catch
{
// ignore
}
}
Thread.Sleep(10);
}
}
finally
{
Marshal.FreeHGlobal(pCanfdBuffer);
Marshal.FreeHGlobal(pCanBuffer);
}
}
///
/// 合并接收模式:通过设备级 API(ZCAN_ReceiveData)统一接收 CAN/CANFD/LIN。
///
/// 取消令牌。
/// 接收缓存容量。
private void ReceiveLoop_Merge(CancellationToken token, int bufferFrames)
{
int dataObjSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANDataObj));
int canfdSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANCANFDData));
int linSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANLINData));
IntPtr pDataObjs = Marshal.AllocHGlobal(dataObjSize * bufferFrames);
IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdSize);
IntPtr pLinBuffer = Marshal.AllocHGlobal(linSize);
try
{
while (!token.IsCancellationRequested)
{
uint recvNum = ZLGCAN.ZCAN_GetReceiveNum(_deviceHandle, 2);
if (recvNum == 0)
{
Thread.Sleep(10);
continue;
}
uint actualRecv = ZLGCAN.ZCAN_ReceiveData(_deviceHandle, pDataObjs, (uint)bufferFrames, 10);
if (actualRecv == 0)
{
continue;
}
for (int i = 0; i < actualRecv; i++)
{
IntPtr pCur = IntPtr.Add(pDataObjs, i * dataObjSize);
var obj = Marshal.PtrToStructure(pCur);
switch (obj.dataType)
{
case 1:
Marshal.Copy(obj.data, 0, pCanfdBuffer, canfdSize);
var canfdData = Marshal.PtrToStructure(pCanfdBuffer);
bool isFd = (canfdData.flag & 1) == 1;
bool isTx = (canfdData.flag & (1 << 9)) != 0;
RaiseCanFrame(obj.chnl, canfdData.timeStamp, isFd, canfdData.frame.can_id, canfdData.frame.data, canfdData.frame.len, isTx);
break;
case 4:
if (obj.data == null || obj.data.Length < linSize)
{
break;
}
Marshal.Copy(obj.data, 0, pLinBuffer, linSize);
var linData = Marshal.PtrToStructure(pLinBuffer);
RaiseLinFrame(obj.chnl, linData.rxData.timeStamp, linData.pid.rawVal, linData.rxData.data, linData.rxData.datalen, linData.rxData.dir);
break;
}
}
Thread.Sleep(1);
}
}
finally
{
Marshal.FreeHGlobal(pLinBuffer);
Marshal.FreeHGlobal(pCanfdBuffer);
Marshal.FreeHGlobal(pDataObjs);
}
}
///
/// 将接收到的 CAN/CANFD 帧转换为托管对象并触发事件。
///
/// 通道号。
/// 时间戳(us)。
/// 是否 CANFD。
/// can_id(含标志位)。
/// 原始数据缓冲区。
/// 数据长度。
/// 是否为发送回显(Tx)。
private void RaiseCanFrame(int channel, ulong timestamp, bool isCanFd, uint canId, byte[] data, byte dlc, bool isTx)
{
try
{
var len = Math.Min(isCanFd ? 64 : 8, Math.Min((int)dlc, data?.Length ?? 0));
var bytes = new byte[len];
if (len > 0 && data != null)
{
Array.Copy(data, bytes, len);
}
CanFrameReceived?.Invoke(new ZlgCanRxFrame(channel, isCanFd, canId, bytes, timestamp, isTx));
if (isTx)
{
MarkSendOk();
}
else
{
MarkReviceOk();
}
if (DbcDecodeEnabled)
{
TryDecodeAndUpdateModels(canId, bytes, isCanFd ? (byte)ZDBC.FT_CANFD : (byte)ZDBC.FT_CAN);
}
}
catch (Exception ex)
{
_log.Warn($"派发 CAN 帧事件异常:{ex.Message}");
}
}
///
/// 将接收到的 LIN 帧转换为托管对象并触发事件。
///
/// 通道号。
/// 时间戳(us)。
/// PID。
/// 原始数据缓冲区。
/// 数据长度。
/// 方向(由设备回传)。
private void RaiseLinFrame(int channel, ulong timestamp, byte pid, byte[] data, byte datalen, byte dir)
{
try
{
var len = Math.Min(8, Math.Min((int)datalen, data?.Length ?? 0));
var bytes = new byte[len];
if (len > 0 && data != null)
{
Array.Copy(data, bytes, len);
}
LinFrameReceived?.Invoke(new ZlgLinRxFrame(channel, pid, bytes, timestamp, dir));
}
catch (Exception ex)
{
_log.Warn($"派发 LIN 帧事件异常:{ex.Message}");
}
}
///
/// 校验指定原生 DLL 是否存在于程序输出目录(AppContext.BaseDirectory)。
///
/// DLL 文件名。
/// 找不到 DLL。
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);
}
TryPreloadNativeDll(full);
if (_preloadedZlgcanHandle == IntPtr.Zero && !string.IsNullOrWhiteSpace(_preloadedZlgcanError))
{
throw new InvalidOperationException($"预加载 {dllName} 失败:{_preloadedZlgcanError}。DLL 路径:{full}");
}
var dllArch = TryGetPeArchitecture(full);
if (!string.IsNullOrWhiteSpace(dllArch))
{
var procArch = Environment.Is64BitProcess ? "x64" : "x86";
if (Environment.Is64BitProcess && !string.Equals(dllArch, "x64", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"{dllName} 位数不匹配:当前进程为 {procArch},但 DLL 架构为 {dllArch}。请替换为 x64 版本 DLL,或将程序改为 x86 运行。DLL 路径:{full}");
}
if (!Environment.Is64BitProcess && !string.Equals(dllArch, "x86", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"{dllName} 位数不匹配:当前进程为 {procArch},但 DLL 架构为 {dllArch}。请替换为 x86 版本 DLL,或将程序改为 x64 运行。DLL 路径:{full}");
}
}
}
///
/// 尝试读取 PE 头判断 DLL 架构。
///
/// DLL 完整路径。
/// 返回 "x86"/"x64"/"arm64"/"unknown(...)";读取失败返回 null。
private static string? TryGetPeArchitecture(string dllFullPath)
{
try
{
using var fs = new FileStream(dllFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var br = new BinaryReader(fs);
if (fs.Length < 0x40)
{
return null;
}
var mz = br.ReadUInt16();
if (mz != 0x5A4D)
{
return null;
}
fs.Position = 0x3C;
var peOffset = br.ReadInt32();
if (peOffset <= 0 || peOffset > fs.Length - 6)
{
return null;
}
fs.Position = peOffset;
var peSig = br.ReadUInt32();
if (peSig != 0x00004550)
{
return null;
}
var machine = br.ReadUInt16();
return machine switch
{
0x014c => "x86",
0x8664 => "x64",
0xAA64 => "arm64",
_ => $"unknown(0x{machine:X})"
};
}
catch
{
return null;
}
}
private static Dictionary BuildDbcModelIndex(IEnumerable models)
{
var dict = new Dictionary(StringComparer.Ordinal);
foreach (var m in models)
{
if (string.IsNullOrWhiteSpace(m.MsgName) || string.IsNullOrWhiteSpace(m.SignalName))
{
continue;
}
dict[BuildMsgSigKey(m.MsgName, m.SignalName)] = m;
}
return dict;
}
private static string BuildMsgSigKey(string msgName, string signalName)
{
return msgName + "\u0000" + signalName;
}
private void TryDecodeAndUpdateModels(uint canId, byte[] payload, byte frameType)
{
ZlgDbcDatabase? dbc;
Dictionary? index;
lock (_dbcSync)
{
dbc = _dbc;
index = _dbcModelIndex;
}
if (dbc == null || !dbc.IsLoaded || index == null)
{
return;
}
try
{
var (msgName, signals) = dbc.DecodeRawFrame(canId, payload, frameType);
if (string.IsNullOrWhiteSpace(msgName) || signals == null || signals.Count == 0)
{
return;
}
foreach (var kv in signals)
{
var key = BuildMsgSigKey(msgName, kv.Key);
if (index.TryGetValue(key, out var model))
{
model.SignalRtValue = kv.Value.ToString();
}
}
}
catch (Exception ex)
{
_log.Warn($"DBC 解码异常:{ex.Message}");
}
}
///
/// 若对象已释放则抛异常。
///
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ZlgCanFd200uDriver));
}
}
}
///
/// CAN/CANFD 接收帧。
///
public readonly struct ZlgCanRxFrame
{
///
/// 构造 CAN/CANFD 接收帧。
///
/// 通道号。
/// 是否 CANFD。
/// can_id(含标志位)。
/// 数据(已截断为实际长度)。
/// 时间戳(us)。
/// 是否为发送回显。
public ZlgCanRxFrame(int channel, bool isCanFd, uint canId, byte[] data, ulong timestampUs, bool isTx)
{
Channel = channel;
IsCanFd = isCanFd;
CanId = canId;
Data = data;
TimestampUs = timestampUs;
IsTx = isTx;
}
public int Channel { get; }
public bool IsCanFd { get; }
public uint CanId { get; }
public byte[] Data { get; }
public ulong TimestampUs { get; }
public bool IsTx { get; }
}
///
/// LIN 接收帧。
///
public readonly struct ZlgLinRxFrame
{
///
/// 构造 LIN 接收帧。
///
/// 通道号。
/// PID。
/// 数据(已截断为实际长度)。
/// 时间戳(us)。
/// 方向(由设备回传)。
public ZlgLinRxFrame(int channel, byte pid, byte[] data, ulong timestampUs, byte dir)
{
Channel = channel;
Pid = pid;
Data = data;
TimestampUs = timestampUs;
Dir = dir;
}
public int Channel { get; }
public byte Pid { get; }
public byte[] Data { get; }
public ulong TimestampUs { get; }
///
/// 方向:由设备回传,0/1 具体含义以 ZLG 文档为准。
///
public byte Dir { get; }
}
}