起源:
用习惯了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文件生成数据:
参考资料: