4

The question is: after am raising an exception, can I stop it to propagate from it's own constructor? consider the code bellow:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TMyErrorClass = class(Exception)
    constructor Create(aMsg:String);
  end;
  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.FormCreate(Sender: TObject);
begin
//
 raise TMyErrorClass.Create('test');
end;

{ TMyErrorClass}

constructor TMyErrorClass.Create(aMsg: String);
begin
 {$IFDEF DEBUG}
   Showmessage(aMsg);
 {$ELSE}
  //here I want to 'kill' the exception
 {$ENDIF}
end;

end.

After raise is called, how can I terminate the Exception, without adding try except/finally where I'm raising the exception?

LE: I have an app which has almost 2000 raise like this...and I'm trying to find out an alternative solution to write error handling for it....

RBA
  • 12,337
  • 16
  • 79
  • 126
  • 1
    If you're gonna kill it before it is even constructed, why would you raise it in the first place? Do not use an exception, just show an message box instead. – Sertac Akyuz Dec 16 '11 at 12:23
  • I have an app which has almost 2000 raise like this...and I'm trying to find out an alternative solution to write error handling for it.. – RBA Dec 16 '11 at 12:25
  • To answer the question shortly - you can't do it. – kludg Dec 16 '11 at 12:42
  • That would make debug build completely different from release build which certainly isn't the way to go. Just give your app a top-level exception handler which handles exceptions. – jpfollenius Dec 16 '11 at 13:48
  • This is what I'm trying to do, but sometimes decisions concerning this are not taken by me...so I need to re-invent the wheel... – RBA Dec 16 '11 at 13:50
  • 1
    If you have 2000 instructions in your program that shouldn't be there, then someone made a very big mistake. The way to fix that mistake is to take out those 2000 statements that shouldn't be there anymore. The solution is *not* to alter the semantics of the language so that a `raise` statement doesn't really raise anything. That will make long-term maintenance of your program much harder. – Rob Kennedy Dec 16 '11 at 15:43
  • that someone is no longer here, and I need to solve this mess...so, 2000 replaces, here I come.... – RBA Dec 16 '11 at 15:46
  • How is silently killing exceptions and proceeding as if nothing happened (in release build) an "alternative solution for error handling"? – jpfollenius Dec 16 '11 at 16:47
  • @Smasher - I thought the "release" part was a mistake, why would one want to show a message if the exception is going to be raised anyway ("debug" part)? Then again, maybe I didn't get it at all.. – Sertac Akyuz Dec 16 '11 at 18:40
  • You could wrap the `raise` statements with `{$IFDEF DEBUG}`. This was perhaps the intention. – Marcus Adams Dec 16 '11 at 21:19

5 Answers5

5

Once you've entered a raise statement, there are only two ways to avoid going through with raising that exception:

  • Raise something else first. From within the constructor of the exception that's about to be raised, you can create and raise a different exception first. That will avoid your program ever reaching the first raise statement.

  • Terminate the thread so nothing gets to run. You can do this various ways, including ExitThread, Halt, and ExitProcess, which are all pretty much equivalent in this context. Your program won't be running anymore, but at least you've prevented it from raising that exception!

Neither of those will solve the problem you're trying to solve, but they will do what you asked for in your question. The bottom line is that whatever you're trying to do, quelling an exception after you've already initiated the exception-raising mechanism is the wrong approach. You can't unring a bell, and you can't unraise an exception.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
3

The way to stop an exception propagating is to catch it with an except. Note that you don't write the except where you raise the exception, but at the point where you want it to stop propagating.

However, you should probably not stop an exception raised inside a constructor from propagating outside that constructor. If you do so your object may be partially constructed.

I believe the correct solution to your problem is, as many others have said, to raise exceptions when you encounter errors and let them float up to a high level exception handler.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

When an instance of TMyErrorClass is being constructed, it does not know whether it is going to be raised or not. You are asking to break cause-and-effect relationship of something as fundamental as language exception handling. I will never put this in production code for it will be a ticking time-bomb. Enjoy (written in Delphi 6):

type
  TMyClass = class(TObject)
  public
    Name: string;
    constructor Create;
  end;

type
  REArguments = array[0..100] of DWORD;
  PREArguments = ^REArguments;

type
  TRaiseExceptionProc = procedure (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD; lpArguments: PREArguments); stdcall;

var
  RaiseExceptionProc_Old: TRaiseExceptionProc;

procedure MyRaiseException(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD; lpArguments: PREArguments); stdcall;
var
  raisedObject: TObject;
begin
  if @RaiseExceptionProc_Old <> nil then
  begin
    RaiseExceptionProc := @RaiseExceptionProc_Old;
    RaiseExceptionProc_Old := nil;

    raisedObject := Pointer(lpArguments^[1]);
    if raisedObject is TMyClass then
    begin
      raisedObject.Free;
    end
    else
    begin
      TRaiseExceptionProc(RaiseExceptionProc)(dwExceptionCode, dwExceptionFlags, nNumberOfArguments, lpArguments);
    end;
  end;
end;

constructor TMyClass.Create;
begin
  inherited;
  Name := 'MyClass';
  RaiseExceptionProc_Old := RaiseExceptionProc;
  RaiseExceptionProc := @MyRaiseException;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  raise TMyClass.Create;
  ShowMessage('Continue without interruption.');
end;
Igor
  • 15,833
  • 1
  • 27
  • 32
1

I know this is ugly on all accounts, but wanted to share. Code does not modify the exception class, this is like a global exception handler, except it won't be raised, execution will continue as if nothing had happened:

 
procedure TestException(dwExceptionCode, dwExceptionFlags,
    nNumberOfArguments: DWORD; lpArguments: PDWORD); stdcall;
var
  Arg: array of DWORD absolute lpArguments;
begin
{$IFDEF DEBUG}
  if (nNumberOfArguments > 1) and Assigned(lpArguments) and
      (TObject(Arg[1]).ClassName = TMyErrorClass.ClassName) then begin
    ShowMessage('Test');
    TObject(Arg[1]).Free;
    Exit;
  end;
{$ENDIF}
  windows.RaiseException(dwExceptionCode, dwExceptionFlags,
                            nNumberOfArguments, lpArguments);

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  RaiseExceptionProc := @TestException;
  ..

Tested only with 32bit and D2007, I have no idea if it will work otherwise.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
0

Use Application.OnException to implement generic exception handling behaviour at the top-level of your application. For example:

Application.OnException := MainForm.DefaultExceptionHandler;

procedure TMainForm.DefaultExceptionHandler (Sender : TObject; E : Exception);

begin
{$IfDef DEBUG}
ShowMessage (E.Message);
{$EndIf}
end;

Continuing normal control flow after an exception is a bad idea IMHO.

jpfollenius
  • 16,456
  • 10
  • 90
  • 156