1

问题摘要: 中的某些代码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 usesMain.pas

现在这里是崩溃的一步一步:

  1. 执行进入UartComm.OnGetIdRES()
  2. ERangeError当我尝试将Length动态数组设置为-7

    SetLength(InfoBytes, InfoLength);

  3. 我们进入LogExceptions.HookGlobalException()。此时IDE中显示的调用堆栈是这样的(我省略了内存地址):

    ->  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 .... }
    
  4. 一旦我们从HookGlobalException调试器中出来,就会抛出一个对话框:

    引发异常类 ERangeError 并带有消息“范围检查错误”

如果我按“继续”程序仍然冻结工作。如果没有调试器,程序也会在此时冻结。

  1. 如果我单击“Break”并继续使用调试器,执行会一直通过堆栈进入VaComm.TVaCommEventThread.DoEvent并执行该行:

    Application.HandleException(Self);
    

之后它什么也不做(我用调试器进入这个例程,程序永远“运行”)。

即使我不使用 JCL 库作为钩子,而是指向Application.OnException一些空例程,也会发生完全相同的事情。

为什么异常被钩子捕获,然后在钩子返回时重新引发?如何抑制异常以使程序不会崩溃但继续运行?

更新:我有 3 个伟大的发现:

  • JCL 钩子实际上捕获了所有异常,无论是已处理的还是未处理的。这就是为什么GlobalExceptHook()在异常通过调用堆栈之前。
  • Application.OnException被分配在代码中的其他地方重新分配。
  • Application.HandleException执行OnException(但是当我尝试进入时调试器没有告诉我)并且那里有一条线试图关闭 COM 端口。这是让我的程序的 GUI 冻结的那一行。

当我弄清楚一切时,我会写一个答案。

4

2 回答 2

0

I don't understand exactly what you mean by 'carry on'. If you mean carry on from where the exception was raised that makes no sense. That is the point of exceptions, to filter up the stack until you reach a point where you can build in a recovery mechanism and safely resume. This means you need to find a sensible point or points to enable resumption of your program and at that point use a try...except...end block. Generally exceptions behave just the way you want except that the the log is not written, which is where Application.OnException comes in, and the message is not what you would like, which is where you would need appropriate try... except...end blocks. If your program really crashes, i.e. terminates or freezes, that is more to do with the nature of what caused the exception than exception handling per se and no amount of fudging will hide this.

于 2015-06-18T14:05:33.337 回答
0

问题是UartComm.TfrmUartComm.UartVaComm1RxChar事件触发了。当此例程中引发未处理的异常时,执行会通过调用堆栈直到到达Application.OnException.

在里面OnException我试图用 . 关闭 COM 端口VaComm1.Close()。一部分Close()是调用停止VaComm1线程并WaitFor()完成线程。但请记住,UartVaComm1RxChar永远不会回来!从未完成!所以这WaitFor()是永远的等待。

解决方案是启用TTimer内部OnException并在此计时器内移动VaComm1.Close()例程。程序完成处理引发的异常并返回执行“主”循环,事件完成。现在 TTimer 触发并关闭 COM 端口。

更多细节在这里

于 2015-07-15T11:14:25.363 回答