周立功的CAN /FD实现
This commit is contained in:
@@ -88,10 +88,10 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
#endregion
|
||||
|
||||
#region 函数
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
public static extern IntPtr ZCAN_OpenDevice(uint device_type, uint device_index, uint reserved);
|
||||
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
public static extern IntPtr ZCAN_OpenDeviceByName(uint device_type, string name);
|
||||
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
|
||||
@@ -2,8 +2,10 @@ 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;
|
||||
@@ -21,6 +23,9 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
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;
|
||||
@@ -189,16 +194,189 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
|
||||
EnsureNativeDllExists("zlgcan.dll");
|
||||
|
||||
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_200U, deviceIndex, 0);
|
||||
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)
|
||||
{
|
||||
throw new InvalidOperationException("ZCAN_OpenDevice 失败,请确认驱动/设备连接/程序位数与 DLL 匹配。");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预加载指定路径的原生 DLL,尽量避免被 PATH 中的同名 DLL 干扰。
|
||||
/// </summary>
|
||||
/// <param name="dllFullPath">DLL 完整路径。</param>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造输出目录下某个 DLL 的诊断信息(架构/大小/版本)。
|
||||
/// </summary>
|
||||
/// <param name="baseDir">输出目录。</param>
|
||||
/// <param name="fileName">DLL 文件名。</param>
|
||||
/// <returns>诊断信息字符串;不存在返回 "xxx:not_found";异常返回 null。</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前进程中已加载模块的完整路径。
|
||||
/// </summary>
|
||||
/// <param name="moduleFileName">模块文件名(如 zlgcan.dll)。</param>
|
||||
/// <returns>完整路径;获取失败返回 null。</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开设备并初始化 CANFD 通道。
|
||||
/// </summary>
|
||||
@@ -1286,6 +1464,78 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试读取 PE 头判断 DLL 架构。
|
||||
/// </summary>
|
||||
/// <param name="dllFullPath">DLL 完整路径。</param>
|
||||
/// <returns>返回 "x86"/"x64"/"arm64"/"unknown(...)";读取失败返回 null。</returns>
|
||||
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<string, CanDbcModel> BuildDbcModelIndex(IEnumerable<CanDbcModel> models)
|
||||
|
||||
@@ -79,6 +79,33 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
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<string>();
|
||||
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)
|
||||
@@ -91,26 +118,42 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
}
|
||||
}
|
||||
|
||||
var fileInfo = new ZDBC.FileInfo
|
||||
var loadOk = false;
|
||||
foreach (var path in candidatePaths)
|
||||
{
|
||||
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)
|
||||
var fileInfo = new ZDBC.FileInfo
|
||||
{
|
||||
throw new InvalidOperationException("ZDBC_LoadFile 失败,请检查 DBC 文件格式。");
|
||||
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);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
if (!loadOk)
|
||||
{
|
||||
Marshal.FreeHGlobal(pFile);
|
||||
// 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();
|
||||
@@ -542,13 +585,128 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
|
||||
private static byte[] BuildFixedPathBytes(string path, int fixedLen)
|
||||
{
|
||||
var bytes = Encoding.ASCII.GetBytes(path);
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当 ZDBC_LoadFile 失败时的兜底加载策略:读取文件内容并调用 ZDBC_LoadContent。
|
||||
/// </summary>
|
||||
/// <param name="fullPath">DBC 完整路径。</param>
|
||||
/// <param name="merge">是否合并。</param>
|
||||
/// <returns>加载成功返回 true。</returns>
|
||||
private bool TryLoadContentFallback_NoLock(string fullPath, bool merge)
|
||||
{
|
||||
// zdbc.dll 入口为 ANSI 字符串,因此这里优先使用 ASCII(将非 ASCII 字符替换为 ?),
|
||||
// 避免中文注释/特殊字符导致原生库解析失败;同时需要兼容 UTF-16 BOM,否则用 ASCII 读会出现大量 \0 造成内容截断。
|
||||
|
||||
var encodings = new List<Encoding>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Windows 8.3 短路径。
|
||||
/// </summary>
|
||||
/// <param name="lpszLongPath">长路径。</param>
|
||||
/// <param name="lpszShortPath">短路径缓冲。</param>
|
||||
/// <param name="cchBuffer">缓冲区大小。</param>
|
||||
/// <returns>返回写入的字符数量;0 表示失败。</returns>
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern uint GetShortPathName(string lpszLongPath, StringBuilder lpszShortPath, uint cchBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将长路径转换为短路径(8.3),用于兼容部分原生库对中文/特殊字符路径处理不完整的问题。
|
||||
/// </summary>
|
||||
/// <param name="fullPath">长路径。</param>
|
||||
/// <returns>短路径;失败返回 null。</returns>
|
||||
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;
|
||||
@@ -556,7 +714,7 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
int len = Array.IndexOf(bytes, (byte)0);
|
||||
if (len < 0) len = bytes.Length;
|
||||
|
||||
return Encoding.ASCII.GetString(bytes, 0, len).Trim();
|
||||
return Encoding.Default.GetString(bytes, 0, len).Trim();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
|
||||
Reference in New Issue
Block a user