Thursday, February 8, 2007

Exception callstack in Delphi

One of the great thing about .Net is getting the callstack with minimum effort.
Unlike .Net, Delphi developers must rely on other methods to locate the location of an exception.
However, there is a way to get the call stack on Delphi exception.
You can download Jiri Hajek's unit for this purpose, which also includes logging the exceptions to a file.
For this to work you also need to enable the "Stack frames" and "TD32 debug info" parameters in the project settings.
Here is something I wrote based on Jiri's code, which directs the callstack output to another method, allowing actions other than file write:

unit ExceptionLog;

interface

uses Classes, MemCheck;

type
TOnException = procedure(CallStack : String) of object;

ExceptionHandler = class
private
FOnException: TOnException;
Function GetOnException : TOnException;
Procedure SetOnException(value: TOnException);
procedure Init;
Constructor Create;
public
Class Function GetInstance : ExceptionHandler;
Property OnException: TOnException read GetOnException write SetOnException;
end;

implementation
var

oldRTLUnwindProc : procedure; stdcall;
OInstance : ExceptionHandler;

{ ExceptionHandler }
constructor ExceptionHandler.Create;

begin
FOnException := nil;
end;

Class Function ExceptionHandler.GetInstance: ExceptionHandler;
begin
if OInstance = nil then OInstance := ExceptionHandler.Create;
result := OInstance;
end;

function ExceptionHandler.GetOnException: TOnException;
begin
Result := FOnException;
end;

procedure ExceptionHandler.SetOnException(value: TOnException);
begin
FOnException := value;
end;

procedure HandleOnException; stdcall;
var CS: TCallStack;
begin
FillCallStack(CS, 4);
Try
ExceptionHandler.GetInstance.OnException(CallStackTextualRepresentation(CS, ''));
Except
End;
asm
mov esp, ebp
pop ebp
jmp oldRTLUnwindProc
end; //asm
end; //HandleOnException

procedure ExceptionHandler.Init;
begin
oldRTLUnwindProc := RTLUnwindProc;
RTLUnwindProc := @HandleOnException;
end;

initialization
ExceptionHandler.GetInstance.Init;

end.

Usage:

  1. Add this unit to the "uses" section of your main unit
  2. Create a method with the "TOnException" (below) signature
  3. Connect the method to this class (Example: "ExceptionHandler.GetInstance.OnException := MyMethod")

No comments: