From df3da9d9cbf1481a43b063097950421fca1f511e Mon Sep 17 00:00:00 2001 From: Tyrone CT Date: Thu, 14 May 2026 18:26:22 +0800 Subject: [PATCH] =?UTF-8?q?Cladude=20code=20=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CapMachine.Wpf/App.xaml.cs | 18 ++++++ CapMachine.Wpf/CanDrive/ToomossCan.cs | 71 +++++++++++++--------- CapMachine.Wpf/CapMachine.Wpf.csproj | 1 + CapMachine.Wpf/Services/CanDriveService.cs | 65 ++++++++++++++------ 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/CapMachine.Wpf/App.xaml.cs b/CapMachine.Wpf/App.xaml.cs index e93833c..9409550 100644 --- a/CapMachine.Wpf/App.xaml.cs +++ b/CapMachine.Wpf/App.xaml.cs @@ -283,6 +283,24 @@ namespace CapMachine.Wpf TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; //非UI线程未捕获异常处理事件 (例如自己创建的一个子线程) AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); + + //首次抛出(含被吞掉的)异常也落日志,便于排查闪退场景下原生互操作异常 + AppDomain.CurrentDomain.FirstChanceException += (s, fce) => + { + try + { + var ex = fce.Exception; + //只关心严重异常,避免日志被刷爆 + if (ex is AccessViolationException + || ex is System.Runtime.InteropServices.SEHException + || ex is StackOverflowException + || ex is OutOfMemoryException) + { + LogService?.Error($"[FirstChance-FATAL] {ex.GetType().Name}: {ex.Message}\r\n{ex.StackTrace}"); + } + } + catch { /* 异常处理函数中绝不能再抛 */ } + }; } diff --git a/CapMachine.Wpf/CanDrive/ToomossCan.cs b/CapMachine.Wpf/CanDrive/ToomossCan.cs index f2197f9..3d06943 100644 --- a/CapMachine.Wpf/CanDrive/ToomossCan.cs +++ b/CapMachine.Wpf/CanDrive/ToomossCan.cs @@ -962,10 +962,30 @@ namespace CapMachine.Wpf.CanDrive // 执行发送CAN逻辑 { + //再次校验前置条件,运行中若设备/DBC被关闭则直接跳过本轮(避免传 0 句柄给原生导致 AV) + if (!OpenState || DBCHandle == 0 || CmdData == null || CmdData.Count == 0) + { + await Task.Delay(10, token); + continue; + } - var GroupMsg = CmdData.GroupBy(x => x.MsgName); - USB2CAN.CAN_MSG[] CanMsg = new USB2CAN.CAN_MSG[GroupMsg.Count()]; - for (int i = 0; i < GroupMsg.Count(); i++) + //本轮要发送的指令快照(避免遍历时 UI 线程并发修改) + List roundSnapshot; + lock (_canSendLock) + { + roundSnapshot = CmdData + .Where(x => !string.IsNullOrWhiteSpace(x.MsgName) && !string.IsNullOrWhiteSpace(x.SignalName)) + .ToList(); + } + if (roundSnapshot.Count == 0) + { + await Task.Delay(10, token); + continue; + } + + var GroupMsg = roundSnapshot.GroupBy(x => x.MsgName).ToList(); + USB2CAN.CAN_MSG[] CanMsg = new USB2CAN.CAN_MSG[GroupMsg.Count]; + for (int i = 0; i < GroupMsg.Count; i++) { CanMsg[i] = new USB2CAN.CAN_MSG(); CanMsg[i].Data = new Byte[64]; @@ -973,38 +993,33 @@ namespace CapMachine.Wpf.CanDrive // 发送构帧临时缓冲:每轮申请/释放,避免与其他线程共享同一指针导致并发问题 IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))); - int Index = 0; - //循环给MSG赋值数据 - foreach (var itemMsg in GroupMsg) + try { - foreach (var itemSignal in itemMsg) + int Index = 0; + //循环给MSG赋值数据;DBC 解析器在 .Net 侧不是线程安全的,必须串行化 + lock (_dbcParserLock) { - CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + foreach (var itemMsg in GroupMsg) + { + foreach (var itemSignal in itemMsg) + { + CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + } + CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); + CanMsg[Index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG)); + Index++; + } } - CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); - CanMsg[Index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG)); - Index++; } - - //通过DBC写入数据后生成CanMsg - //将信号值填入CAN消息里面 - - // 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏) - Marshal.FreeHGlobal(msgPtSend); + finally + { + // 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏) + Marshal.FreeHGlobal(msgPtSend); + } //发送CAN数据 int SendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, CanMsg, (uint)CanMsg.Length); - if (SendedNum >= 0) - { - //Console.WriteLine("Success send frames:{0}", SendedNum); - IsSendOk = true; - } - else - { - //Console.WriteLine("Send CAN data failed! {0}", SendedNum); - IsSendOk = false; - } - + IsSendOk = SendedNum >= 0; } diff --git a/CapMachine.Wpf/CapMachine.Wpf.csproj b/CapMachine.Wpf/CapMachine.Wpf.csproj index 87c66e3..0e7ca38 100644 --- a/CapMachine.Wpf/CapMachine.Wpf.csproj +++ b/CapMachine.Wpf/CapMachine.Wpf.csproj @@ -7,6 +7,7 @@ enable true AnyCPU + true diff --git a/CapMachine.Wpf/Services/CanDriveService.cs b/CapMachine.Wpf/Services/CanDriveService.cs index 547c9b1..9160f2b 100644 --- a/CapMachine.Wpf/Services/CanDriveService.cs +++ b/CapMachine.Wpf/Services/CanDriveService.cs @@ -210,31 +210,56 @@ namespace CapMachine.Wpf.Services /// - /// 循环发送数据到CAN + /// 循环发送数据到CAN /// public void CycleSendMsg() { - if (ToomossCanDrive.OpenState) + if (!ToomossCanDrive.OpenState) { - if (ToomossCanDrive.IsCycleSend == false) - { - if (CmdData.Count > 0) - { - ToomossCanDrive.IsCycleSend = true; - ToomossCanDrive.CmdData = CmdData; - ToomossCanDrive.StartPrecisionCycleSendMsg(); - } - else - { - System.Windows.MessageBox.Show("未发现配置的数据内容", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); - } - } - else - { - ToomossCanDrive.StopCycleSendMsg(); - } - + System.Windows.MessageBox.Show("CAN未打开,请先连接CAN", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + return; } + + //停止已在运行的循环 + if (ToomossCanDrive.IsCycleSend == true) + { + ToomossCanDrive.StopCycleSendMsg(); + return; + } + + //必要的前置校验:DBC 必须已解析成功,否则原生互操作直接 AccessViolation 闪退 + if (!ToomossCanDrive.DbcParserState || ToomossCanDrive.DBCHandle == 0) + { + System.Windows.MessageBox.Show("DBC尚未解析成功,无法循环发送", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + return; + } + + //过滤掉 MsgName / SignalName 为空的行(空名传给原生 DBC 解析器是 AV 常见诱因) + List cmdDataSnapshot; + lock (_cmdDataLock) + { + cmdDataSnapshot = CmdData + .Where(x => !string.IsNullOrWhiteSpace(x.MsgName) && !string.IsNullOrWhiteSpace(x.SignalName)) + .Select(x => new CanCmdData + { + ConfigName = x.ConfigName, + MsgName = x.MsgName, + SignalName = x.SignalName, + SignalCmdValue = x.SignalCmdValue + }) + .ToList(); + } + + if (cmdDataSnapshot.Count == 0) + { + System.Windows.MessageBox.Show("未发现可发送的数据(请检查写入配置的【消息名称】/【信号名称】是否为空)", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + return; + } + + ToomossCanDrive.IsCycleSend = true; + //传入的是已经过滤的快照副本,避免与 UI 线程共享同一引用 + ToomossCanDrive.CmdData = cmdDataSnapshot; + ToomossCanDrive.StartPrecisionCycleSendMsg(); }