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

8

27

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.

Klos answered 27/5, 2010 at 17:8 Comment(5)
Just re-raise it the usual way. It keeps your code self-documenting and doesn't mess up the stack trace.Roddie
yep I definitely want to preserve the stack!Klos
If it is logging of exceptions you are after, you should take a look at madExcept, EurekaLog and/or Jedi. Each have better handling of exception logging you could ever dream of doing yourself.Butadiene
Yea I know the tools, I prefer EurekaLog, but it is not to me to use and how to use those tool with clients .. so I'm doing what I can :)Klos
Exceptions are reference counted. The triggering exception is decremented when the EXCEPT block exits. RAISE by itself w/o an exception instance increments the reference count. RAISE followed by an exception instance (e.g. raise E; ) will not, hence the AV noted. Exception.Create() will result in an exception with ref count of 1 and, if done in an exception block, is not subject to the ref count decrement when the EXCEPT block exits. Write code where raise alone can be called, create a new exception, or use the AcquireExceptionObject() to increment the ref count, as per the below solutions.Acrosstheboard
S
37

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;
Symbolism answered 27/5, 2010 at 17:29 Comment(2)
this is good idea and I had it in mind but still I waned to know is there other code-less way to do it in a separate function. Thanks.Klos
while this is the most correct way of solving this problem, there are situations where you can't modify the calling method (maybe it's someone else's code, e.g. an event handler of a component - I have exactly this scenario) so, for me, Craig's and especially Wes' solutions are the real answer to this question.Macruran
S
12

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;
Sorilda answered 3/5, 2012 at 5:15 Comment(0)
E
8

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;.

Eupepsia answered 6/4, 2011 at 11:50 Comment(1)
+1 : I've been looking for a way to reraise an exception outside of the try...except block for quite some time now. I realize you lose the call stack, but for reraising an exception in the main VCL thread that occurred in another thread -- this works very nicely. Thanks!Saccharoid
L
4

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.

Loo answered 28/5, 2010 at 5:56 Comment(3)
Interesting I will give a try to this. Thanks for the details.Klos
Beware--In Delphi 2007 at least, ReleaseExceptionObject is an empty method. Using it in conjunction with AcquireExceptionObject results in a memory leak. I have found FreeAndNil to work as an alternative.Mulberry
In D2009 ReleaseExceptionObject is also empty. I wonder wheter documentation is correct about this reference counting. Exception is derived from TObject not from TInterfacedObject.Shinshina
T
2

You should be able to just use the Raise command by itself to re-raise the exception:

begin
  MyHandleException(E);
  Raise;
end;
Timeworn answered 27/5, 2010 at 17:15 Comment(6)
I think that's exactly what he's trying not to do: have to put a raise after MyHandleException every time he calls it by putting the raise inside MyHandleException itself.Discrete
@Mason: You may well be correct. I was probably reading it incorrectly. I'll delete my answer ... after a bit so you have a chance to see this comment :)Timeworn
I think it is right. He put the raise in a procedure called within the except block, which does not work. The raise needs to be precisely in the except block. If you write raise AException in the external procedure, you do get an access violation.Symbolism
Don't delete your answer. Your code snippet might be useful to others wondering how to re-raise an exception from within the except block. And learning that it is not possible to do it anywhere else.Godroon
That makes sense - I'll leave it in place since I don't think the information is inherently wrong.Timeworn
Yep I'm trying to avoid raise on all try except blocks. But it seems unavoidable at this point :) Thanks guys!Klos
M
1

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.

Moderato answered 21/5, 2018 at 13:52 Comment(0)
D
1

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;
Disembroil answered 13/11, 2018 at 17:1 Comment(1)
This will work, but it always raises an exception of type Exception, even if the original was different type, e.g EFileNotFound.Denys
D
0

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.

Duquette answered 12/3, 2016 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.