729 lines
26 KiB
C#
729 lines
26 KiB
C#
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>
|
||
/// 基于周立功 ZDBC(zdbc.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);
|
||
}
|
||
|
||
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)
|
||
{
|
||
_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();
|
||
}
|
||
}
|
||
|
||
/// <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=0,FT_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=0,FT_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.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;
|
||
|
||
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));
|
||
}
|
||
}
|
||
}
|
||
}
|