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 viola开发者_运维问答te
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.
精彩评论