How to pass call stack information to an exception using EurekaLog? - delphi

I have a threaded application and for some purpose I want to pass call stack information of a catched exception to a new custom exception:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, CallStackOfExceptionEAsString);
end;
end;
What is the best way to do this, preferably using EurekaLog? I am using Delphi 2006 btw.

EurekaLog exposes several event handlers like OnExceptionNotify.
You can implement these in your code. For example: procedure EurekaLogExceptionNotify(
EurekaExceptionRecord: TEurekaExceptionRecord; var Handled: Boolean);
Here you can see a TEurekaExceptionRecord which is defined in ExceptionLog.pas. But you maybe just own the non-source version which works just fine.
The record has a EurekaExceptionRecord.CallStack list. This proprietary list can be converted to TStringsusing the CallStackToStrings method which is also defined in the ExceptionLog unit.
Here is an example where I write the CallStack into a StringList.
CallStackList := TStringList.Create;
try
CallStackToStrings(EurekaExceptionRecord.CallStack, CallStackList);
LogMessage := 'An unhandled exception occured. Here is the CallStack.' + #13#10
+ CallStackList.Text;
finally
CallStackList.Free;
end;
At least from this starting point you should be able to investigate the exposed functions, records etc.. All information is accessible.

EurekaLog provides a function GetLastExceptionCallStack() (defined in unit ExceptionLog.pas).
Using this I have written the following function (based on example code here):
function GetLastEurekalogCallStackAsString(): string;
{$IFDEF EUREKALOG}
var
Stack: TEurekaStackList;
Str: TStringList;
{$ENDIF}
begin
{$IFDEF EUREKALOG}
Stack := GetLastExceptionCallStack();
try
Str := TStringList.Create;
try
CallStackToStrings(Stack, Str);
Result := Str.Text;
finally
FreeAndNil(Str);
end;
finally
FreeAndNil(Stack);
end;
{$ELSE}
Result := '';
{$ENDIF}
end;
So you can write:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, GetLastEurekalogCallStackAsString());
end;
end;

EurekaLog 7 has Chained Exception support, which is specifically designed for this task. Just enable it in options (it is enabled by default) and use:
try
// here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
Exception.RaiseOuterException(EMyException.Create(E.Message));
// for old IDEs:
// raise EMyException.Create(E.Message);
end;
end;

Related

Delphi Tokyo exception prevents setting function result

Found that exception handling in Delphi Tokyo behaves a little different than in previous Delphi versions.
function FuncTest: integer;
begin
Result := 1;
try
raise Exception.Create('Error Message');
finally
Result := 2;
end;
end;
function Test:integer;
begin
Result:=0;
try
Result:=FuncTest;
finally
ShowMessage(Result.ToString);
end;
end;
In earlier Delphi versions the message box shows here "2", Tokyo - "0".
Is this a Tokyo bug or the exceptions should not be handled like this?
The Tokyo behaviour is correct. A function that raises an exception does not return a value. You have hitherto been relying on implementation detail.
Consider this code:
Result:=FuncTest;
This executes as follows:
FuncTest is called.
Result is assigned.
Now, because step 1 raises an exception, step 2 does not execute.
If anything, I would say that the behaviour you report from earlier versions is dubious. In this function:
function Test:integer;
begin
Result:=0;
try
Result:=FuncTest;
finally
ShowMessage(Result.ToString);
end;
end;
The statement Result:=FuncTest raises an exception and so Result should not be modified by that statement. Another way to think of it is that the function is called but the assignment is not executed.
One of the problems with the Delphi ABI is that function return values are sometimes implemented as implicit var parameters. Which means that the assignment may or may not happen. To demonstrate:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TRec1 = record
X1: NativeInt;
end;
TRec2 = record
X1: NativeInt;
X2: NativeInt;
end;
function GetRec1: TRec1;
begin
Result.X1 := 1;
raise Exception.Create('');
end;
function GetRec2: TRec2;
begin
Result.X1 := 1;
raise Exception.Create('');
end;
procedure Main;
var
Rec1: TRec1;
Rec2: TRec2;
begin
Rec1 := Default(TRec1);
Writeln(Rec1.X1);
try
Rec1 := GetRec1;
except
end;
Writeln(Rec1.X1);
Rec2 := Default(TRec2);
Writeln(Rec2.X1);
try
Rec2 := GetRec2;
except
end;
Writeln(Rec2.X1);
end;
begin
Main;
Readln;
end.
This outputs:
0
0
0
1
Which is rather disappointing. It should not be possible to modify the caller's variable, but the use of an implicit var parameter rather than a value return allows this leakage. In my view this is a serious flaw in the design of the Delphi ABI, a flaw that you will not find in most other languages.
In your code, there is no var parameter because the return type is transferred in a register. In which case any Delphi version that outputs 2 is broken.
Fundamentally though, your code is mistaken in its expectations. If a function raises an exception then you must assume that the return value is ill-defined.
Finally, your code outputs 0 in XE3 and XE7, so I wonder how far back you need to go to see a value of 2.
Consider this:
function FuncTest: integer;
begin
Result := 1;
try
try
raise Exception.Create('Error Message');
except
{ do nothing }
end
finally
Result := 2;
end;
end;
Don't think that "finally" handles an exception locally, rather than "returning an exception". To handle it locally requires a LOCAL try-except clause.

Who is responsible for error checking and handling?

Who is responsible for error checking and handling?
I don't have any of the expensive component libraries such as DevExpress or TMS Components etc so I cannot look at source to get an idea of how most components manage error handling.
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
Here is an example using a few scenarios:
Note these are directly from the components source (made up for example purposes)
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
or
procedure TMyComponent.AddFromFile(FileName: string);
begin
if FileExists(FileName) then
begin
FBitmap.LoadFromFile(FileName);
end
else
raise Exception.Create(FileName + ' does not exist.');
end;
And these last two are using an instance of the component at runtime:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
or
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists('D:\Test.bmp') then
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end
else
raise Exception.Create('D:\Test.bmp does not exist.');
end;
I guess it comes down to who should error check and handle what? Is the component developer responsible for handling these types of checking or the user of the component?
As I am writing this I believe both component developer and user should handle such checking but I am unsure, so I am looking for what the general consensus amongst developers is.
Thanks.
To answer your specific queestion:
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
The general rule about exception handling is that you should only catch exceptions you know how to handle, and let others propagate to higher code that may know how to handle it. If an exception is raised inside of your component, the component needs to decide whether to:
handle that particular exception internally and gracefully move on to other things without notifying the caller at all.
re-throw the exception (maybe with tweaks made to it), or re-throw a whole new exception, to allow the caller to identify and handle that specific failure, if desired.
ignore the exception (don't catch it at all) and just let it propagate as-is.
If an API used by your component returns an error code instead of raising an exception, the component needs to decide how to handle that as well. Whether to ignore the error and move on, or raise an exception to make it more apparent.
In your particular example, I prefer the following approach:
type
EMyComponentAddError = class(Exception)
private
FFileName: String;
begin
constructor CreateWithFileName(const AFileName: string);
property FileName: string read FFileName;
end;
constructor EMyComponentAddError.CreateWithFileName(const AFileName: string);
begin
inherited CreateFmt('Unable to add file: %s', [AFileName]);
FFileName := AFileName;
end;
procedure TMyComponent.AddFromFile(FileName: string);
begin
try
FBitmap.LoadFromFile(FileName);
except
Exception.RaiseOuterException(EMyComponentAddError.CreateWithFileName(FileName));
end;
end;
This allows your component to recognize that an error occurred, act on it as needed, and still report component-specific information to the caller without losing the original error that caused the actual failure. If the caller is interested in the details, it can catch the exception, look at its InnerException property, access custom properties if present, etc.
For example:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
Let's assume MyComponent1.AddFromFile('D:\Test.bmp'); fails. The default exception handler will catch it and display a popup message that says:
Unable to add file: D:\Test.bmp
Useful, but little details, as it could have failed for any number of reasons. Maybe the file could not be opened, but why? Non-existant vs no permission? Maybe the file was opened but corrupted? Maybe memory could not be allocated? And so on.
The caller could catch it and display more useful info, if so desired (it is not required - the component provides the info, the caller decides whether to use it or not):
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
ShowMessage('There was a problem adding a file:'+sLineBreak+E.FileName+sLineBreak+sLineBreak+E.InnerException.Message);
Sysutils.Abort;
end;
end;
end;
Or:
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
raise Exception.CreateFmt('There was a problem adding a file:'#10'%s'#10#10'%s', [E.FileName, E.InnerException.Message]);
end;
end;
end;
Either of which would display:
There was a problem adding a file:
D:\Test.bmp
The file was not found
As David said we only need this
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This will check that
there is an existing file
in this file is a valid bitmap
Now it depends on the application, how important is this for the application. If this TForm1 is the Application.MainForm, every exception you did not catch inside the creation process will terminate the application. This is sometimes a valid behavior.
Very important, the application cannot run without
procedure TForm1.Form1Create(Sender:TObject);
begin
MyComponent.AddFromFile( 'D:\Test.bmp' );
end;
or wrap the exception for a user-friendly message
procedure TForm1.Form1Create(Sender:TObject);
begin
try
MyComponent.AddFromFile( 'D:\Test.bmp' );
except
on E: Exception do
raise Exception.Create( 'Sorry, I cannot run, because of: ' + E.Message );
end;
end;
Very important, but we have a fallback to handle this, maybe
procedure TForm1.Form1Create(Sender:TObject);
var
LBitmapFiles : TStringList;
LBitmapIdx : Integer;
LBitmapLoaded : Boolean;
LErrorStore : TStringList;
begin
LBitmapFiles := nil;
LErrorStore := nil;
try
LBitmapFiles := TStringList.Create;
LErrorStore := TStringList.Create;
LBitmapFiles.Add( 'D:\Test.bmp' );
LBitmapFiles.Add( 'D:\Fallback.bmp' );
LBitmapLoaded := False;
while not LBitmapLoaded and ( LBitmapIdx < LBitmapFiles.Count ) do
try
MyComponent.AddFromFile( LBitmapFiles[LBitmapIdx] );
LBitmapLoaded := True;
except
on E: Exception do
begin
LErrorStore.Add( LBitmapFiles[LBitmapIdx] + ': ' + E.Message );
Inc( LBitmapIdx );
end;
end;
if not LBitmapLoaded then
raise Exception.Create( 'Sorry, I cannot run, because of: ' + LErrorStore.Text );
finally
LErrorStore.Free;
LBitmapFiles.Free;
end;
end;
There are other fallbacks possible and this also depends on the application (f.i. set a dummy bitmap to the component) to get the application to work properly.
Not important, if we have no image ... we have no image, who cares
procedure TForm1.Form1Create(Sender:TObject);
const
CBitmapFile = 'D:\Test.bmp';
begin
// check, if there is a file
if FileExists( CBitmapFile ) then
try
MyComponent.AddFromFile( CBitmapFile );
except
on E: Exception do
begin
// Maybe log the exception
SomeLogger.Log( E );
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
end
else
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
Component
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This is all you need. If the bitmap object cannot load the file, for whatever reason, it will raise an exception. Let that exception propagate to the consumer of the code.
There's really no point trying to test whether or not the file exists. What if the file exists and it is not a bitmap file? What if the file exists, is a bitmap file, but the disk has a duff sector and the file read fails? If you attempt to check for all error conditions, you will just be repeating the checks that the LoadFromFile method already does.
Some error conditions cannot possibly be checked from the outside. An error that only becomes apparent part way through reading the file cannot reasonably be checked from the outside.
One very common consequence of over-zealous, duplicate error checking is that you end up with code that produces errors in scenarios where there should be none. If you get your error checking wrong you could end up reporting an error that would not have occurred had you let the underlying code run.
Consumer
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
At this point the decision is more difficult. I would typically expect the following question to be the driver of the decision:
Is it an expected, and reasonable event, for the file not to be present?
If the answer to that question is yes, then you should consider handling the exception in the FormCreate method. Again, testing FileExists() catches just one failure mode, albeit a common one. Perhaps you should use a try/except block to catch the error.
If the answer to the question is no, let the error propagate.
That said, you should also consider whether or not you want an exception to be thrown from your form's OnCreate event handler. That may be perfectly reasonable, but it is certainly conceivable that you will not wish to do this.

Try/Except/Finally not working properly in Delphi XE

I'm having this funny issue with Delphi XE where I create a try/except/finally statement and when the application generate an exception the except block is never called it jump straight to the finally block, I tried few things like invert the try/except/finally to try/finally/except, try to change the try blocks to different places, clean the code and recompile in case was a Delphi issue but noting seems to work.
What I'm trying to accomplish here is to show a dialog message to the user and after clean up the code in case of a crash.
procedure CallbackExport(Sender: TObject);
var
SaveDlg: TSaveDialog;
FileName: string;
begin
SaveDlg := TSaveDialog.Create (nil);
try
try
SaveDlg.Title := 'Export';
SaveDlg.InitialDir := GetSystemPath(CSIDL_DESKTOP);
SaveDlg.Options := [ofOverwritePrompt, ofEnableSizing];
case (Sender as TMenuItem).Tag of
cnExcel: begin
SaveDlg.Filter := 'Excel File (*.xls)|*.xls';
end;
cnHtml: begin
SaveDlg.Filter := 'HTML File (*.html)|*.html';
end;
cnTxt: begin
SaveDlg.Filter := 'Text File (*.txt)|*.txt';
end;
cnCsv: begin
SaveDlg.Filter := 'Comma Seperated File (*.csv)';
end;
cnXml: begin
SaveDlg.Filter := 'XML file (*.xml)|*.xml';
end;
end;
if not SaveDlg.Execute(self.Handle) then
Exit;
FileName := SaveDlg.FileName;
case (Sender as TMenuItem).Tag of
cnExcel: begin
ExportGridToExcel(FileName, tvdGrid);
end;
cnHtml: begin
ExportGridToHTML(FileName, tvdGrid);
end;
cnTxt: begin
ExportGridToText(FileName, tvdGrid);
end;
cnCsv: begin
ExportGridToText(FileName, tvdGrid, true, true, ',', '', '', 'CSV');
end;
cnXml: begin
ExportGridToXML(FileName, tvdGrid);
end;
end;
except
on e: exception do
begin
ShowMessage('An error occurred while saving the file ' + FileName + #13#10 + 'With a message: ' + E.Message);
StvdAudit.tvdAudit('Error saving file, reason: ' + E.Message);
end;
end;
finally
SaveDlg.Free;
end;
end
If an exception is raised inside the try/except, and not handled by code further down the call stack, it will be caught by your exception handler.
You are claiming the ExportGridToXXX is raising an exception that is not caught by the exception handler in your code. But that claim cannot be true. Either no exception is raised, or ExportGridToXXX already handles the exception.
On the more general subject of exception handling, the general policy should be not to handle them if at all possible. You should only handle them in case you need to stop the exception propagating, and need to deal with the exception at this point in the code. Normally, particularly in a UI program, you would simply let the exception be handled by the top-level exception handler.
As well as that point, you code swallows all exceptions, irrespective of their type. That's bad practice. Supposing that you do want to handle exceptions raised by ExportGridToXXX, you should only handle the exception classes that are expected. For instance, you might encounter EAccessViolation for which your app's policy is to terminate. But since you swallowed it, treating it in the same handler used to trap sharing violations, you cannot apply that policy. Always be discerning in your handling of exceptions.
Do the Export(smth) functions reside in a separate DLL? Then your application's Exception class is not the same as external DLL's Exception class.
Your exception handler is swallowing the exception, try re-raising instead:
on e: exception do
begin
StvdAudit.tvdAudit('Error saving file, reason: ' + E.Message);
raise exception.create('An error occurred while saving the file ' + FileName + #13#10 + 'With a message: ' + E.Message);
end;

How to get the EXCEPTION_POINTERS during an EExternal exception?

How do i get the EXCEPTION_POINTERS, i.e. both:
PEXCEPTION_RECORD and
PCONTEXT
data during an EExternal exception?
Background
When Windows throws an exception, it passes a PEXCEPTION_POINTERS; a pointer to the exception information:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
When Delphi throws me an EExternal exception, it only contains half that information, the PEXCEPTION_RECORD only:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
How, during an EExternal exception, do i get both?
Example Usage
i am trying to write a Minidump using MiniDumpWriteDump function from Delphi.
The function has a few optional parameters:
function MiniDumpWriteDump(
hProcess: THandle; //A handle to the process for which the information is to be generated.
ProcessID: DWORD; //The identifier of the process for which the information is to be generated.
hFile: THandle; //A handle to the file in which the information is to be written.
DumpType: MINIDUMP_TYPE; //The type of information to be generated.
{in, optional}ExceptionParam: PMinidumpExceptionInformation; //A pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be generated.
{in, optional}UserStreamParam: PMinidumpUserStreamInformation;
{in, optional}CallbackParam: PMinidumpCallbackInformation): Boolean;
At a basic level i can omit the three optional parameters:
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
nil, //PMinidumpExceptionInformation
nil,
nil);
and it succeeds. The downside is that the minidump is missing the exception information. That information is (optionally) passed using the 4th miniExceptionInfo parameter:
TMinidumpExceptionInformation = record
ThreadId: DWORD;
ExceptionPointers: PExceptionPointers;
ClientPointers: BOOL;
end;
PMinidumpExceptionInformation = ^TMinidumpExceptionInformation;
This is good, except i need a way to get at the EXCEPTION_POINTERS that is supplied by Windows when an exception happens.
The TExceptionPointers structure contains two members:
EXCEPTION_POINTERS = record
ExceptionRecord : PExceptionRecord;
ContextRecord : PContext;
end;
i know that Delphi's EExternal exception is the base of all "Windows" exceptions, and it contains the needed PExceptionRecord:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
But it doesn't contain the associated ContextRecord.
Isn't PEXCEPTION_RECORD good enough?
If i try to pass the EXCEPTION_POINTERS to MiniDumpWriteDump, leaving ContextRecord nil:
procedure TDataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
ei.ExceptionRecord := EExternal(E).ExceptionRecord;
ei.ContextRecord := nil;
GenerateDump(#ei);
end;
...
end;
function GenerateDump(exceptionInfo: PExceptionPointers): Boolean;
var
miniEI: TMinidumpExceptionInformation;
begin
...
miniEI.ThreadID := GetCurrentThreadID();
miniEI.ExceptionPointers := exceptionInfo;
miniEI.ClientPointers := True;
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
#miniEI, //PMinidumpExceptionInformation
nil,
nil);
end;
Then the function fails with error 0x8007021B
Only part of a ReadProcessMemory or WriteProcessMemory request was completed
What about SetUnhandledExceptionFilter?
Why don't you just use SetUnhandledExceptionFilter and get the pointer you need?
SetUnhandledExceptionFilter(#DebugHelpExceptionFilter);
function DebugHelpExceptionFilter(const ExceptionInfo: TExceptionPointers): Longint; stdcall;
begin
GenerateDump(#ExceptionInfo);
Result := 1; //1 = EXCEPTION_EXECUTE_HANDLER
end;
Problem with that is that the unfiltered exception handler only kicks in if the exception is unfiltered. Because this is Delphi, and because because i handle the exception:
procedure DataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
//If it's EXCEPTION_IN_PAGE_ERROR then we have to terminate *now*
if EExternal(E).ExceptionRecord.ExceptionCode = EXCEPTION_IN_PAGE_ERROR then
begin
ExitProcess(1);
Exit;
end;
//Write minidump
...
end;
{$IFDEF SaveExceptionsToDatabase}
SaveExceptionToDatabase(Sender, E);
{$ENDIF}
{$IFDEF ShowExceptionForm}
ShowExceptionForm(Sender, E);
{$ENDIF}
end;
The application doesn't, nor do i want it to, terminate with a WER fault.
How do i get the EXCEPTION_POINTERS during an EExternal?
Note: You can ignore everything from Background on. It's unnecessarily filler designed to make me look smarter.
Pre-emptive snarky Heffernan comment: You should stop using Delphi 5.
Bonus Reading
MSDN: Crash Dump Analysis (Windows)
(detailed example of how to call MiniDumpWriteDump)
CodeProject: Post-Mortem Debugging Your Application with Minidumps and Visual Studio .NET
(General talk about the concepts, virtues, and how to generate and use minidumps)
Stackoverflow: How to create minidump for my process when it crashes?
(initial introduction to the world of mini dumps)
Stackoverflow: Can one prevent Microsoft Error Reporting for a single app?
(setting up the unfiltered handler in Delphi)
Since the Delphi RTL doesn't expose the context pointer directly but only extracts the exception pointer and does so in the bowels of System, the solution is going to be somewhat specific to the version of Delphi you are using.
It's been a while since I've had Delphi 5 installed, but I do have Delphi 2007 and I believe that the concepts between Delphi 5 and Delphi 2007 have remained largely unchanged as far as this goes.
With that in mind, here's an example of how it can be done for Delphi 2007:
program Sample;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils;
var
SaveGetExceptionObject : function(P: PExceptionRecord):Exception;
// we show just the content of the general purpose registers in this example
procedure DumpContext(Context: PContext);
begin
writeln('eip:', IntToHex(Context.Eip, 8));
writeln('eax:', IntToHex(Context.Eax, 8));
writeln('ebx:', IntToHex(Context.Ebx, 8));
writeln('ecx:', IntToHex(Context.Ecx, 8));
writeln('edx:', IntToHex(Context.Edx, 8));
writeln('esi:', IntToHex(Context.Esi, 8));
writeln('edi:', IntToHex(Context.Edi, 8));
writeln('ebp:', IntToHex(Context.Ebp, 8));
writeln('esp:', IntToHex(Context.Esp, 8));
end;
// Below, we redirect the ExceptObjProc ptr to point to here
// When control reaches here we locate the context ptr on
// stack, call the dump procedure, and then call the original ptr
function HookGetExceptionObject(P: PExceptionRecord):Exception;
var
Context: PContext;
begin
asm
// This +44 value is likely to differ on a Delphi 5 setup, but probably
// not by a lot. To figure out what value you should use, set a
// break-point here, then look in the stack in the CPU window for the
// P argument value on stack, and the Context pointer should be 8 bytes
// (2 entries) above that on stack.
// Note also that the 44 is sensitive to compiler switches, calling
// conventions, and so on.
mov eax, [esp+44]
mov Context, eax
end;
DumpContext(Context);
Result := SaveGetExceptionObject(P);
end;
var
dvd, dvs, res: double; // used to force a div-by-zero error
begin
dvd := 1; dvs := 0;
SaveGetExceptionObject := ExceptObjProc;
ExceptObjProc := #HookGetExceptionObject;
try
asm
// this is just for register context verification
// - don't do this in production
mov esi, $BADF00D5;
end;
// cause a crash
res := dvd / dvs;
writeln(res);
except
on E:Exception do begin
Writeln(E.Classname, ': ', E.Message);
Readln;
end;
end;
end.

How should I re-raise a Delphi exception after logging it?

Do you know a way to trap, log, and re-raise exception in Delphi code?
A simple example:
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
begin
MyHandleException(E);
end;
end;
end;
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
// raise AException; - this will access violate
end;
So I need to re-raise it in the except block but I was wondering if there is a better way to write my own method to handle and (on specific conditions) to re-raise exceptions.
If you want to re-raise the exception only under certain conditions, write
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
begin
if MyHandleException(E) then
raise;
end;
end;
end;
function TForm3.MyHandleException(AException: Exception): boolean;
begin
ShowMessage(AException.Message);
result := true/false;
end;
Following on from Craig Young's post, I've used something along the lines of the following code successfully. You can preserve the original exception location by using the "at" identifier with the ExceptAddr function. The original exception class type and information is also preserved.
procedure MyHandleException(AMethod: string);
var
e: Exception;
begin
e := Exception(AcquireExceptionObject);
e.Message := e.Message + ' raised in ' + AMethod;
raise e at ExceptAddr;
end;
try
...
except
MyHandleException('MyMethod');
end;
The following will work, but is of course not ideal for 2 reasons:
The exception is raised from a different place in the call stack.
You don't get an exact copy of the exception - especially those classes that add attributes. I.e. you'll have to explicitly copy the attributes you need.
Copying custom attributes can get messy due to required type checking.
.
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
raise ExceptClass(AException.ClassType).Create(AException.Message);
end;
The benefits are that you preserve the original exception class, and message (and any other attributes you wish to copy).
Ideally you'd want to call System._RaiseAgain, but alas that is a 'compiler-magic' routine and can only be called by raise;.
You could try to use (system.pas):
function AcquireExceptionObject: Pointer;
AcquireExceptionObject returns a pointer to the current exception object and prevents the exception object from being deallocated when the current exception handler exits.
Note: AcquireExceptionObject increments the exception object's reference count. Make sure that the reference count is decremented when the exception object is no longer needed. This happens automatically if you use the exception object to re-raise the exception. In all other cases, every call to AcquireExceptionObject must have a matching call to ReleaseExceptionObject. AcquireExceptionObject/ReleaseExceptionObject sequences can be nested.
You should be able to just use the Raise command by itself to re-raise the exception:
begin
MyHandleException(E);
Raise;
end;
Old topic but, what about this solution?
procedure MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
AcquireExceptionObject;
raise AException;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
MyHandleException(E);
end;
end;
It's based on the first code posted by Eduardo.
This way was working for me!
procedure RaiseExceptionFmt(const AFormat: string;
const AArgs: array of const);
begin
raise Exception.CreateFmt(AFormat, AArgs) at ExceptAddr;
end;
I rewrote my method, and now the raise will be the same before.
procedure RaiseInternalExceptionFmt(const AFormat: string;
const AArgs: array of const);
var
LExceptionPtr: Pointer;
begin
LExceptionPtr := AcquireExceptionObject();
try
Exception(LExceptionPtr).Message := Format(AFormat, AArgs);
raise Exception(LExceptionPtr) at ExceptAddr;
finally
ReleaseExceptionObject();
end;
end;
You could acquire the exception object before calling your handler and keep the handler itself one liner. However, you still have a lot of "Try/Except/Do/End" burden.
Procedure MyExceptionHandler(AException: Exception);
Begin
Log(AException); // assuming it accepts an exception
ShowMessage(AException.Message);
raise AException; // the ref count will be leveled if you always raise it
End;
Procedure TForm3.Button1Click(Sender: TObject);
Begin
Try
Foo;
Except On E:Exception Do
MyExceptionHandler(Exception(AcquireExceptionObject));
End;
End;
However, if what you only want to do is to get rid of repetitive error handling code in event handlers, you could try this:
Procedure TForm3.ShowException(AProc : TProc);
Begin
Try
AProc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
Reducing your event handler code to this:
Procedure TForm3.Button1Click(Sender: TObject);
Begin
ShowException(Procedure Begin // anon method
Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
You can also make it work for functions, using parametrized functions:
Function TForm3.ShowException<T>(AFunc : TFunc<T>) : T;
Begin
Try
Result := AFunc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
And making ShowException return a value (acting as a passthru):
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
V := ShowException<Integer>(Function : Integer Begin // anon method
Result := Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
Or even making the anon procedure touch directly the outer scope variable(s):
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
ShowException(Procedure Begin // anon method
V := Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
There are some limitations on the interaction of variables from inside the body of the anonymous function and the ones defined in the outer scope, but for simple cases like these, you will be more than fine.

Resources