Proper Catastrophic Error Handling

1.2k Views Asked by At

There's something I keep running into that I really haven't solved with Delphi programs and was wondering if anyone could instruct me on it. As the topic says, how do you do proper catastrophic error handling? For instance:

// is file necessary for the program present?
if not FileExists(FilePath1) then
   begin
     raise Exception.Create(FilePath1 + ' does not exist and is required for this program to function.');
   // I obviously need to do something here to make the program QUIT and not have
   // any more code run.

     Application.Terminate;
     Abort;
   end;

I can use the exception unit there as well and throw out an exception, but the program continues as before. I've used the halt call in the past, but it seems to not do any cleanup or the like so I end up making a big procedure with close and free calls for everything I've done just to be sure (and even then I'm not sure of any of the behind the scenes stuff).

So what is the right way to handle such things?

Edit: To clarify, I'm wanting to know about how to make the program do what clean-up it needs to do and then EXIT NOW and not do any other code.

4

There are 4 best solutions below

0
On

You can write your own Application.OnException handler, ex:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure HandleException(Sender: TObject; E: Exception);
  end;

var
  Form1: TForm1;

type
  EMyFatalError = class(Exception);

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Application.OnException:= HandleException;
  raise EMyFatalError.Create('OOPS!');
end;

procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
  Application.ShowException(E);
  if E is EMyFatalError then
    Application.Terminate;
end;

end.
6
On

You can instruct the application global object to terminate the program by calling Application.Terminate.

Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.

Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.

Since the call can occur deeper in the stack, you can also raise an Exception, and you code your program to not handle it in order to let the execution reach the main application loop and the default exception handler catch it.

That way, you effectively prevent's more code to run in your application.

In code it may look like this:

if not FileExists(FilePath1) then
  begin
    MessageDlg(FilePath1 + ' does not exist and is required for this program to function.',
               mtWarning, [mbOK], 0);

    Application.Terminate;
    Abort;  //raising a EAbort just as an example
  end;

Depending on where this code is called, I advise you not to show the message directly, but rather raise an exception with the message and let the application object default HandleException method show the message for you:

if not FileExists(FilePath1) then
  begin
    Application.Terminate;
    raise EMyFatalException.Create(FilePath1 
      + ' does not exist and is required for this program to function.');
  end;

Which looks more natural to me. EMyFatalException is a hypothetical exception class you can declare and never handle in your except clauses.

6
On

IMHO the only clean way would be checking for "Fatal conditions" before Application is running.

program Project2;

uses
  Forms,Dialogs,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  ReportMemoryLeaksOnShutDown := true;
  Application.Initialize;
  if True then // your condition here
    begin
      MessageDLG('Fatal Error',mtError,[mbok],0);
    end
  else
    begin
      Application.CreateForm(TForm1, Form1);
      Application.Run;
    end;
end.

Any other approach will have side effects

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
    FSL:TStringList;
  public
   Destructor Destroy;override;
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

destructor TForm1.Destroy;
begin
  FreeAndNil(FSL);
  Showmessage('Will never be seen with Application.Terminate + HALT');
  inherited;
end;

procedure TForm1.FormCreate(Sender: TObject);
const
  Testing=0; // try 1 and 2 too
begin

   FSL:=TStringList.Create;
   Try
      raise Exception.Create('Terminating now');
   except
     case Testing of
         0: begin
             // exception object will not be freed other code will be prevented, Form won't be shown
             Application.Terminate;
             HALT;
            end;
         1: begin
             // exception object will not be freed Form won't be shown
             HALT;
            end;
         2: begin
             // clean but Form will be shown
             Application.Terminate;
            end;

     end;
   end;

end;

end.
0
On

To perform abnormal termination call Halt() passing the exit code.

if CatastropicErrorDetected then
begin
  ... show error message
  Halt(1);
end;

On Windows this results in a call to TerminateProcess and will stop execution there and then.

You note that no cleanup is performed and usually that's what you want. Since you are performing this check at application startup there should be nothing to cleanup.