Delphi:Exception输出堆栈信息

起源:

用习惯了c#之Exception的StackTrace,在程序出异常crash时候能够以其定位出问题的模块及行号,用回Delphi 2009,发现没有这东西。

显然,在编译环境日新月异的今天,是不科学的。分析Delphi的Exception,发现些线索:StackTrace。

应该有戏!

继续下去,验证输出这个StackTrace,它是空的,里面没有预想要的内容。再深究去,发现并没想象那么容易,它扯到不少蛋。

1、Exception类

Delphi 2009中,StackTrace是如此定义的,与它一起的还有几个var:

  Exception = class(TObject)
  private
    ...
  protected
    ...
  public
    ...
    property StackTrace: string read GetStackTrace;
    property StackInfo: Pointer read FStackInfo;
{$IFDEF MSWINDOWS}
  class var
    // Hook this function to return an opaque data structure that contains stack information
    // for the given exception information record. This function will be called when the
    // exception is about to be raised or if this is an external exception such as an
    // Access Violation, called soon after the object is created.
    GetExceptionStackInfoProc: function (P: PExceptionRecord): Pointer;
    // This function is called to return a string representation of the above opaque
    // data structure
    GetStackInfoStringProc: function (Info: Pointer): string;
    // This function is called when the destructor is called to clean up any data associated
    // with the given opaque data structure.
    CleanUpStackInfoProc: procedure (Info: Pointer);
    // Use this function to raise an exception instance from within an exception handler and
    // you want to "acquire" the active exception and chain it to the new exception and preserve
    // the context. This will cause the FInnerException field to get set with the exception
    // in currently in play.
    // You should only call this procedure from within an except block where the this new
    // exception is expected to be handled elsewhere.
    class procedure RaiseOuterException(E: Exception); static;
    // Provide another method that does the same thing as RaiseOuterException, but uses the
    // C++ vernacular of "throw"
    class procedure ThrowOuterException(E: Exception); static;
{$ENDIF}
  end;

明明白白的说,我东西给你了,你要用,自己想办法。你编译环境都不能实现,让我想什么办法?去它主页上看看:

蛋疼之极,欲用StackTrace,它居然要求我们用第三方的解决方案。如此不负责任!

好吧,上JEDI。

2、JclDebug

StackOverflow是个技术的绝佳去处,曾在其上受益不少,再去找找,果然有所得。

有个家伙封装了实现方法,借鉴下来,代码如下:

unit StackTrace;

interface

uses
  SysUtils, Classes, JclDebug;

implementation

function GetExceptionStackInfoProc(P: PExceptionRecord): Pointer;
var
  LLines: TStringList;
  LText: String;
  LResult: PChar;
begin
  LLines := TStringList.Create;
  try
    JclLastExceptStackListToStrings(LLines, True, True, True, True);
    LText := LLines.Text;
    LResult := StrAlloc(Length(LText));
    StrCopy(LResult, PChar(LText));
    Result := LResult;
  finally
    LLines.Free;
  end;
end;

function GetStackInfoStringProc(Info: Pointer): string;
begin
  Result := string(PChar(Info));
end;

procedure CleanUpStackInfoProc(Info: Pointer);
begin
  StrDispose(PChar(Info));
end;

initialization
// Start the Jcl exception tracking and register our Exception
// stack trace provider.
if JclStartExceptionTracking then
begin
  Exception.GetExceptionStackInfoProc := GetExceptionStackInfoProc;
  Exception.GetStackInfoStringProc := GetStackInfoStringProc;
  Exception.CleanUpStackInfoProc := CleanUpStackInfoProc;
end;

finalization
// Stop Jcl exception tracking and unregister our provider.
if JclExceptionTrackingActive then
begin
  Exception.GetExceptionStackInfoProc := nil;
  Exception.GetStackInfoStringProc := nil;
  Exception.CleanUpStackInfoProc := nil;
  JclStopExceptionTracking;
end;

end.

很是优美。但单步去跟,发现GetExceptionStackInfoProc中,其返回值为空。

作者也发现了,所幸另个人解决这个问题,替换其函数如下:

function GetExceptionStackInfoProc(P: PExceptionRecord): Pointer;
var
  LLines: TStringList;
  LText: String;
  LResult: PChar;
  jcl_sil: TJclStackInfoList;
begin
  LLines := TStringList.Create;
  try
    jcl_sil := TJclStackInfoList.Create(True, 7, p.ExceptAddr, False, nil, nil);
    try
      jcl_sil.AddToStrings(LLines, true, true, true, true);
    finally
      FreeAndNil(jcl_sil);
    end;
    LText := LLines.Text;
    LResult := StrAlloc(Length(LText));
    StrCopy(LResult, PChar(LText));
    Result := LResult;
  finally
    LLines.Free;
  end;
end;

验证OK,解决问题!

这样,可以挂接Application.OnException,自个处理程序的Crash异常了。

注意事项:用JEDI求堆栈信息,须设置Delphi工程属性之Linking->Map file值为Detailed,它是基于解析map文件生成数据:

 

参考资料:

System.SysUtils.Exception.StackTrace

JEDI Code Library

Delphi - Trying to get StackTrace for an exception

原文地址:https://www.cnblogs.com/crwy/p/8561019.html