问题摘要:UartComm.OnGetIdRES()
中的某些代码会引发ERangeError
,这会导致我的程序崩溃。
这个错误不是问题,重要的是为什么我的应用程序全局异常挂钩捕获异常,但我的程序仍然崩溃。
我希望钩子捕获所有未处理的异常并抑制它们;程序应该继续运行。
以下是负责全局异常挂钩的单元:
unit LogExceptions;
interface
uses
Windows, SysUtils, Classes, JclDebug, JclHookExcept;
procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);
implementation
uses Main;
procedure HookGlobalException(ExceptObj: TObject; ExceptAddr: Pointer;
OSException: Boolean);
var
Trace: TStringList;
DlgErrMsg: String;
begin
{ Write stack trace to `error.log`. }
Trace := TStringList.Create;
try
Trace.Add(
Format('{ Original Exception - %s }', [Exception(ExceptObj).Message]));
JclLastExceptStackListToStrings(Trace, False, True, True, False);
Trace.Add('{ _______End of the exception stact trace block_______ }');
Trace.Add(' ');
Trace.LineBreak := sLineBreak;
LogExceptions.AppendToLog(Trace.Text, lflError);
{ Show an dialog to the user to let them know an error occured. }
DlgErrMsg := Trace[0] + sLineBreak +
Trace[1] + sLineBreak +
sLineBreak +
'An error has occured. Please check "error.log" for the full stack trace.';
frmMain.ShowErrDlg(DlgErrMsg);
finally
Trace.Free;
end;
end;
procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);
{ .... irrelevant code ....}
initialization
Include(JclStackTrackingOptions, stTraceAllExceptions);
Include(JclStackTrackingOptions, stRawMode);
// Initialize Exception tracking
JclStartExceptionTracking;
JclAddExceptNotifier(HookGlobalException, npFirstChain);
JclHookExceptions;
finalization
JclUnhookExceptions;
JclStopExceptionTracking;
end.
(如果它在这里有帮助,可以链接到JclDebug.pas
和JclHookExcept.pas
)
我要做的就是将LogExceptions
添加到interface uses
的{{1}}列表中。
现在,这是崩溃的一步一步:
Main.pas
UartComm.OnGetIdRES()
设置为ERangeError
时,会引发 Length
:
-7
我们输入SetLength(InfoBytes, InfoLength);
。此时IDE中显示的调用堆栈是这样的(我遗漏了内存地址):
LogExceptions.HookGlobalException()
一旦我们从-> LogExceptions.HookGlobalException
:TNotifierItem.DoNotify
:DoExceptNotify
:HookedRaiseException
:DynArraySetLength
:DynArraySetLength
:@DynArraySetLength
UartComm.TfrmUartComm.OnSpecificRES // This method runs `OnGetIdRES()`
UartComm.TfrmUartComm.OnSpecificPktRX
UartComm.TfrmUartComm.DisplayUartFrame
UartComm.TfrmUartComm.UartVaComm1RxChar
VaComm.TVaCustommComm.HandleDataEvent
VaComm.TVaCommEventThread.DoEvent
{ ... }
{ ... Some low-level calls here .... }
出来,调试器会抛出一个对话框:
引发了异常类ERangeError,其中包含消息'范围检查错误'
如果我按下"继续"程序仍然是冻结的工作。如果没有调试器,程序也会在此时冻结。
如果我点击" Break"并继续使用调试器,执行从堆栈一直进入HookGlobalException
并执行该行:
VaComm.TVaCommEventThread.DoEvent
之后它什么都不做(我使用调试器进入这个例程,程序是"运行"永远)。
即使我没有使用JCL库作为挂钩,而是将Application.HandleException(Self);
指向某个空例程,也会发生同样的事情。
为什么钩子会抓住异常,然后在钩子返回时重新引发?如何抑制异常以使程序不会崩溃但仍在运行?
更新:我做了3个伟大的发现:
Application.OnException
通过调用堆栈的原因。GlobalExceptHook()
已在代码中的其他位置重新分配。Application.OnException
已执行Application.HandleException
(但调试器没有告诉我当我试图进入时)并且那里有一条线试图关闭COM端口。这就是让我的程序的GUI冻结的界限。当我弄明白时,我会写一个答案。
答案 0 :(得分:0)
我不明白你的意思是'继续'。如果你的意思是从提出异常的地方继续进行,那是没有意义的。这是例外的一点,要过滤堆栈,直到达到可以构建恢复机制并安全恢复的程度。这意味着你需要找到一个合理的点或点来恢复你的程序,并在那时使用try ... except ... end block。一般情况下异常的行为只是你想要的方式,除了没有写入日志,这是Application.OnException的来源,而消息不是你想要的,这是你需要适当的尝试......除了.. 。块。如果你的程序真的崩溃,即终止或冻结,那更多的是导致异常的本质,而不是异常处理本身,没有多少捏造会隐藏这个。
答案 1 :(得分:0)
问题是UartComm.TfrmUartComm.UartVaComm1RxChar
是事件触发的。如果在此例程中引发了未处理的异常,则执行将通过调用堆栈落到Application.OnException
。
在OnException
内部,我尝试使用VaComm1.Close()
关闭COM端口。 Close()
的一部分是停止VaComm1
线程和WaitFor()
线程完成的调用。但请记住,UartVaComm1RxChar
永远不会回来!从未完成!所以这个WaitFor()
正在等待。
解决方案是在TTimer
内启用OnException
并在此Timer中移动VaComm1.Close()
例程。程序完成处理引发的异常并返回执行" main"循环,事件结束。现在,TTimer会触发并关闭COM端口。
更多详情here。