4

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.

Glenn1234
  • 2,542
  • 1
  • 16
  • 21
  • Do you want a clean exit after some condition? what about calling Application.Terminate? – jachguate Jan 20 '13 at 05:59
  • @jachguate Yes, I'm wanting a clean exit. But that call still results in the form being shown in cases where showing the form would not be logical. – Glenn1234 Jan 20 '13 at 06:00
  • Where in your question do you state you don't want any form to be shown? – jachguate Jan 20 '13 at 06:01
  • @jachguate the last comment of the sample code. Showing the form if I were to have this in (for example), the Create event, would imply that more code was run. – Glenn1234 Jan 20 '13 at 06:03
  • It's not the same thing. If you want to prevent more code to run, raise an exception. If you don't handle the exception yourself it will reach the main loop exception handler. Application.Terminate; + Abort; will instruct the application to shut down (clean exit) and will prevent more code to be executed. – jachguate Jan 20 '13 at 06:06
  • But exceptions aren't working like that. I put a raise call in there and it does the exception and throws up a message box, but then the form comes up normally, where I can do what I please on the form. – Glenn1234 Jan 20 '13 at 06:14
  • 1
    Why you changed the code in the question? as you show it now, the first raise prevents the application.Terminate to be called. – jachguate Jan 20 '13 at 06:38
  • Because I wanted to put what I was actually trying here instead of some random sample code. – Glenn1234 Jan 20 '13 at 06:42

4 Answers4

6

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.

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

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.
bummi
  • 27,123
  • 14
  • 62
  • 101
  • Your first approach I've tried to suggest, however OP seems to worry about modifying *.dpr files. – TLama Jan 20 '13 at 09:02
  • @TLama you deleted your post? Perhaps you should undelete it, I would delete mine if you undelete yours. In my opinion, if it's possible to avoid hard aborts it should be done. – bummi Jan 20 '13 at 10:50
  • Let's keep it as it is. Your answer provides wider scope than mine. – TLama Jan 20 '13 at 10:53
  • @bummi We learnt that hard aborts were bad in DOS/Win3.1 days. But with NT based Windows, *nix etc. there's no problem. OS reclaims all resources. Terminating threads inside a still running process is bad. Calling ExitProcess is fine. – David Heffernan Jan 20 '13 at 11:54
  • @DavidHeffernan I know and agree as far as OS is affected. But I am still of the opinion preventing if possible is the better way, as far as other effect might happen. Leaving tmp files, hanging BDE and stuff like this. – bummi Jan 20 '13 at 12:12
  • 1
    Temporary files can be created with `FILE_FLAG_DELETE_ON_CLOSE`. If the BDE can't cope with process termination then the BDE sucks. If you are stuck with something like that then sure, I agree that it gets more complicated. – David Heffernan Jan 20 '13 at 12:16
1

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.

jachguate
  • 16,976
  • 3
  • 57
  • 98
  • The second code block just flashes up a box quickly and then terminates. I have Delphi 3 here I'm testing it on at the moment (all that's handy for me right now), if that's useful to know. – Glenn1234 Jan 20 '13 at 06:29
  • Shows the message, then flashes the main form and terminates - basically the best I can do now. – Glenn1234 Jan 20 '13 at 06:43
  • In D3 I think it is.. since you don't want to touch the DPR. You can try to set the Visible property of the main form to false, but I'm almost sure it will not work. Feel free to try it and let us know. – jachguate Jan 20 '13 at 06:47
  • I touch DPRs repeatedly and have no problem putting the check in this example in the DPR (as long as the IDE lets me, that's part of why I don't want to get into the *habit* of it). The problem I have also involves checks that are within execution code itself (i.e. if service not available) which continues after exceptions, so doing graceful termination right is a concern there as well. Setting TForm.Visible to false gave the same result. – Glenn1234 Jan 20 '13 at 06:58
  • It's pretty normal to modify the DPR, even in D3. A buggy version may mess it up if you add a new auto-create form, but as long as you know what are you doing, you can make it work again if needed. If you have too many checks to do, you can group all of them in a new unit and add a single procedure call in your DPR. You can make that even a boolean function and use an approach similar to the [TLama's answer](http://stackoverflow.com/a/14422210/255257) to just skip running the program if something goes wrong. – jachguate Jan 20 '13 at 07:03
  • If it's really a catastrophic error, maybe you should use `Halt` or even `ExitProcess()` instead of `Application.Terminate`. `Application.Terminate` does not immediately exit, as you know... – iMan Biglari Jan 20 '13 at 07:18
1

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.
kludg
  • 27,213
  • 5
  • 67
  • 118