Exceptions and Errors

An exception is an exceptional condition in a program, generally associated with an error that could make a program crash. Exceptions provide the means to handle errors in an elegant and robust manner. Exceptions provide the means to separate error handling from the rest of the code and it also provides a consistent way of reporting errors. 

There are many errors that a programmer should trap with standard program code, rather than leaving it to Delphi to handle them, for example checking that a number is positive before trying to find its square root or that a divisor is not zero. The exceptions mechanism is provided for detecting system errors such as a faulty file or a printer being unavailable, it is not a safety-net for poor programming practice. 

Delphi provides two related constructions for dealing with exceptions, the try-except statement that can deal with exceptions or errors, and the try-finally statement that guarantees the code in the finally section will run even if an error occurs. Delphi raises exceptions at run-time when something goes wrong. There are just four keywords used to provide handling for exceptions:

try - starts a protected block of code

finally - specifies blocks of code that must be executed, even when exceptions occur; cannot be used at the same time as except

except - ends a protected block of code and introduces exception-handling code; cannot be used at same time as finally

raise - raises exceptions explicitly instead of waiting for Delphi to raise them

Places where an exception structure might be used include file processing and printing, both situations where errors might occur due to failure of hardware. When processing a file there may be a disc error, the file may be corrupted or it may not be in the path specified, and we would, in all these cases, want to make sure that the error was identified and that the program closed down the file correctly. When printing we need to make sure that the program will overcome problems like the printer being unavailable (off-line, switched off, or paused due to a technical problem such as a paper jam).

The finally keyword should be used whenever a method performs some tidying up or finalising when it finishes. For example:

if PrintDialog1.Execute then
begin
 try
  AssignPrn (FPrn);
  Rewrite (FPrn);
  writeln (FPrn, 'To dance beneath the diamond sky with one hand waving free');
 finally
  CloseFile (FPrn);
 end;
end;

Whenever there is some finalisation code at the end of a method you should use a finally block to protect the code and to prevent resource or memory leaks (filling up memory and not reclaiming it). Here the assigned printer file FPrn is closed even if the attempt to open it and write something to it fail. Similarly with files, to avoid problems when reading from a file:

if OpenDialog1.Execute then
begin
try
 filename:=OpenDialog1.FileName;
 Assignfile(intfilename, filename);
 reset (intfilename);
 while not eof (intfilename) do
 begin
  read(intfilename,rec);
  memo1.lines.add (rec.name);
 end;
 finally
  closefile(intfilename);
 end;
end;

In the following example a try...except structure is used to trap potential file errors, specifically the failure to create a file.

procedure TForm1.Open1Click(Sender: TObject);
resourcestring
 sCannotCreate='Cannot create file: %s';
begin
 if OpenDialog1.Execute then
 begin
  try
   filename:=OpenDialog1.FileName;
   Assignfile(filename, filename);
   reset (filename);
   while not eof (filename) do
   begin
    read(filename,rec);
    memo1.lines.add (rec.name);
   end;
  except
   on Ex: EFCreateError do
    raise EFileCopyError.CreateFmt(sCannotCreate, [Ex.Message]);
 end;
//try
 closefile(filename);
 end;
//if
end;

The following example uses the division by zero situation to illustrate the use of exceptions:

procedure TForm1.divide;
begin
 a:=strtoint(inputbox('Enter first integer','First Integer', '1'));
 b:=strtoint(inputbox('Enter second integer','Second Integer', '1'));
 Screen.cursor:=crHourglass;
 try
  try
   c:=a div b;
   showmessage ('Answer is ' + inttostr(c));
  finally
   Screen.cursor:=crDefault;
  end;
 except
  on EDivByZero do
   raise Exception.Create('Division by zero. Cannot complete operation.');
 end;
end;

Here we have a try..finally nested inside a try..except structure. The syntax for try..finally is relatively simple but that for try..except can be more complicated. In this case we use a pre-defined exception, EDivByZero, along with the Exception object and one of its methods, Create, to raise an error and supply a message to the user if the divide by zero error occurs.

We could have written the exception handler like this:

 on Ex: EDivByZero do
   raise Exception.Create('Division by zero. Cannot complete operation.');

The slight difference here is the introduction of an exception variable, Ex. The programmer does not need to define Ex or Exception in advance. The object Ex, which is of class Exception type, receives the value of the exception object passed by the raise statement. The exception object is raised and then handled by reference to its type.

Note that there is a difference in behaviour of the exception handling routines when a program is being debugged in the IDE and when it is running independently. When debugging Delphi displays the system messages regarding the error:

However, when the program is running outside the Delphi environment (double-click the .exe file) the system messages will be intercepted and the programmer's own messages will be displayed.

To stop the display of system error messages from within the debugger select Tools/Debugger Options, the Language Exceptions tab and un-tick the Stop on Delphi Exceptions mark.

Other built-in exceptions include: EExternal (whose derived classes include EAccessViolation, EControlC, EIntError, EMathError and EStackOverflow), EConvertError, EInOutError, EPackageError and EWin32Error (see TException for further details). If you think that your program may raise any of these errors then you should consider replacing the default error handler with one of your own in the way illustrated above. On the other hand, you should leave unknown exceptions to Delphi and not try to devise your own routines to handle them: leave them to the onException event of the global Application object.

Delphi Home