Athena

Part 9: Raising Exceptions

Brian Long (www.blong.com)

This month Brian Long looks further at the Kylix exception system.


This article first appeared in Linux Format Issue 27, May 2002.

Click here to download the files associated with this article.

If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.


Introduction

Last month we saw how the try..except statement could be used to catch exceptions from a specified block of code. This month we will see how to broaden the scope of an exception handler and how to raise exceptions of our own. We will also look at resource protection, a necessary consequence of Object Pascal's support of exceptions.

Exception Handling Statements

We have seen that any exceptions emanating from the try part of the try..except statement could be picked up by appropriate exception handlers in its except part, where you can have as many exception handlers as you like. You can incorporate an optional else section as well for other unspecified exceptions.


try
  //code that may cause exceptions
except
  on ExceptionType1 do
    // ExceptionType1 handler
  on ExceptionType2 do
    // ExceptionType2 handler
  //and so on
  else
    //handle any other exception
end

One aspect of this statement that wasn't really emphasised last month is that any given exception handler will react to exceptions from the specified exception class or from classes inherited from it. So the first handler in the code above would handle exceptions that are either of type ExceptionType1 or inherited from this type.

This might sound no big deal until you realise that many exceptions are organised in branches of the CLX class hierarchy. Figure 1 shows a small portion of the exception class hierarchy (the top-level class Exception inherits directly from TObject). Looking back at the exceptions we saw last time you can see that EConvertError and EInvalidCast are immediate descendants of Exception. On the other hand EAccessViolation inherits from EExternal, which is inherited from Exception, and EDivByZero inherits from EIntError, which itself also inherits from EExternal.

Figure 1: A branch of the exception class hierarchy

In fact you can see that EIntError is the base class for all integer exceptions (including the integer divide by zero exception, EDivByZero) and EMathError is the base class for all floating point exceptions (including the floating point divide by zero exception, EZeroDivide).

What all this means is that you can write exception handlers that are more generic than for just handling an individual error. For example, you could write an exception handler to handle all integer arithmetic exceptions that might occur in a block of code like this academic example:


try
  //code that performs integer arithmetic
except
  //handle all integer arithmetic errors
  on EIntError do
    ShowMessage(
      'An unexpected error occurred during some ' +
      'integer arithmetic. Sorry about that!')
end;

If you need to handle one particular exception in a branch of the hierarchy, but want to handle the rest generically you can do this as long as the specific handler goes first and the generic handler goes second, as in:


try
  //code that performs integer arithmetic
except
  on EDivByZero do
    ShowMessage('A divide by zero error occurred. Oops!');
  //handle all other integer arithmetic errors
  on EIntError do
    ShowMessage(
      'An unexpected error occurred during some ' +
      'integer arithmetic. Sorry about that!')
end;

In fact, you can take this a bit further and catch any exception that may occur in a block of code by writing a handler for the Exception class. However this is generally considered bad practice unless you are catching all exceptions to perform some action and then chain back to the previous error handler (the same applies to an else clause in the except part of a try..except statement).


try
  //some code statements
except
  on E: Exception do
  begin
    LogException(E); //fictitious logging routine
    raise
  end
end;

If you have no need to access the exception object, you can use an abbreviated version of the try..except statement that has no exception handlers at all. For example, you might need to set some variable or property to False if an error of any description occurs, but you don't want to affect the natural exception handling. This can be achieved like this:


try
  //some code statements
except
  //set some flag variable to False
  raise
end

Note that external hardware errors (propagated to Kylix applications through signals and represented by exception classes inherited from EExternal) are not well handled in Kylix 1 (they tend to cause the application to hang). As a consequence our advice is to install Kylix 2 Open Edition (or later) to get proper recoverable signal support in CLX applications.

Application-Wide Exception Handlers

It is common to write blocks of code and then write error handling for the errors that may occur in that code block. But sometimes you want to change the default response to certain kinds of exceptions throughout the entire application. In other words you might want to alter the implementation of the default application-wide exception handler.

Fortunately this is a straightforward proposition as the helpful Application object has an event that fits the bill. The OnException event provides an opportunity for the developer to replace the default exception handling code that normally catches any unhandled exception.

In the normal course of events Kylix event handlers have no obligation to do anything in particular. They can do as little or as much as the programmer wants and it makes no odds to the operation of the application. This scheme is sometimes called contractless event programming as you are not required to do any specific tasks if you make an event handler.

Occasionally you bump into an event that breaks this general trend, such as the Application object's OnException event. As soon as you set up an event handler for this event, the normal response to unhandled exceptions (the simple message box) is disabled. If you make an event handler for this event, then you are expected to perform some sensible handling of any exception that comes along to ensure the application behaves in an orderly fashion.

The default response to an unhandled exception is to display its description in a message box (see Figure 2) so you could do much the same in your replacement application-wide exception handler. The Application object uses its own ShowException method to achieve this but you can use any mechanism you like to display the message (and could perhaps display a little more information).

Figure 2: The normal application response to an unhandled exception

Before we get onto such details we should look at how to set up the event handler. Unfortunately the Application object does not show up at design-time and so we will need to manually set up a method and turn it into an event handler (we did this before a few months back when looking at dynamically created components).

The online help (shown in Figure 3) confirms that the event is a property of type TExceptionEvent, which represents a procedure method with two parameters. The first parameter, Sender, will typically be the Application object and the other parameter, E, is the unhandled exception.

Figure 3: The event that acts as an application-wide exception handler

The code below (from the ExceptionHandling.dpr project in this month's source files) shows the method AppExceptionHandler being set up as this event handler. The method is implemented and then assigned to the Application's OnException property in the main form's OnCreate event handler. All AppExceptionHandler does in this case is to replicate the default behaviour by passing the unhandled exception to a call to Application.ShowException.


type
  TForm1 = class(TForm)
...
    procedure FormCreate(Sender: TObject);
...
  public
    procedure AppExceptionHandler(Sender: TObject; E: Exception);
  end;
...
procedure TForm1.AppExceptionHandler(Sender: TObject; E: Exception);
begin
  Application.ShowException(E)
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnException := AppExceptionHandler;
end;

Of course you could use a different message box if you like. ShowException is implemented using a call to Application.MessageBox but you could equally use a member of the MessageDlg or ShowMessage families. In addition you could provide more information than the default message.

Normally the exception's Message property is displayed. This is descriptive, but whilst developing an application it might be handy if the message also told you its exception class. If you actually need to handle the exception you will need to know this information anyway, so the default handler may as well display it.

Figure 4: More information in the default exception handler

Every Kylix object has a ClassName method that returns the class name as a string (it's defined in TObject). The following code displays the message and class name using a formatted string in a dialog as you can see in Figure 4:


procedure TForm1.AppExceptionHandler(Sender: TObject; E: Exception);
begin
  MessageDlg(
    Format('An %s occurred:'#10#10'"%s"', [E.ClassName, E.Message]),
      mtError, [mbOk], 0)
end;

Figure 5: A different response to any Access Violation across the application

Another common job for the application-wide exception handler is to isolate certain classes of exceptions for special-case handling. For example you might want to prevent your users from ever seeing the message: Access Violation at address XXXXXXXX, accessing address YYYYYYYY. You could do this by trapping any EAccessViolation exceptions and displaying a less alarming (and in this case less realistic) message instead as shown in Figure 5 and implemented in the following code:


procedure TForm1.AppExceptionHandler(Sender: TObject; E: Exception);
begin
  if E is EAccessViolation then
    MessageDlg('A small technical hitch has occurred. '#10 +
      'Please don''t use that part of the application again.',
      mtError, [mbOk], 0)
  else
    MessageDlg(
      Format('An %s occurred:'#10#10'"%s"', [E.ClassName, E.Message]),
        mtError, [mbOk], 0)
end;

Figure 6: A simple exception log file

Of course a more likely role of this type of event handler would be to log unhandled exceptions experienced by the application in some sort of log file (such as the one shown in Figure 6). This might give support or QA people a chance to see what type of errors are occurring most often, so the developers could be notified of some work to be done. The following code contains the additions needed to get simplistic error logging support (note the conditional compilation to cater for Linux and Windows file names, in the interest of supporting cross-platform compilation with Kylix and Delphi).


procedure TForm1.AppExceptionHandler(Sender: TObject; E: Exception);
var
  Log: TextFile;
const
{$ifdef LINUX}
  LogFile = '/tmp/CLX_app_error_log.txt';
{$endif}
{$ifdef MSWINDOWS}
  LogFile = 'C:\Temp\CLX_error_log.txt';
{$endif}
begin
  if E is EAccessViolation then
    MessageDlg('A small technical hitch has occurred. '#10 +
      'Please don''t do use that part of the application again.',
      mtError, [mbOk], 0)
  else
    //Default response to unhandled exceptions
    //Application.ShowException(E)

    //Custom response to unhandled exceptions
    MessageDlg(
      Format('An %s occurred:'#10#10'"%s"', [E.ClassName, E.Message]),
        mtError, [mbOk], 0);
  AssignFile(Log, LogFile);
  if FileExists(LogFile) then
    Append(Log)
  else
    Rewrite(Log);
  WriteLn(Log, E.ClassName, ' (', E.Message, '): ', DateTimeToStr(Now));
  CloseFile(Log);
end;

Raising Exceptions

We now know the ins and outs of handling exceptions, which is great, but what about generating exceptions of our own to indicate problems? If you think about it, exceptions occur in Kylix when you execute statements and such statements often involve calling RTL routines, CLX routines or object methods.

Last month we saw that exceptions take the responsibility of checking for errors away from the programmer and that is true for the most part. However when implementing all those commonly used library routines, at some point some checking has to be done. These library routines check their input or behaviour and when a problem is detected they raise an exception. The benefit of this is that they do the checking and your normal processing code uses exception handling.

When implementing your own reusable routines it would be prudent to follow this example and to represent any issues by raising your own exceptions rather than returning error values. As an example, let's say you wanted to write a routine that does some calculations on some numerical input. The routine is designed to take an arbitrary number of floating point values but has some specific design constraints. Let's say that to operate successfully it must have at least two values passed to it and that all values must be non-zero.

Using traditional coding you might implement the routine like this, returning special values (-1 and -2 in this case) to indicate failure. The function expects what's called an open array of doubles to be passed in. This is an array of double-precision floating point numbers, but with no dictated size (the array can be as large or as small as you like). When calling the function you can pass any existing array variable that is defined to contain Double elements or you can pass an open array without using a variable at all. An open array is made by surrounding a comma-separated list of expressions in square brackets.


function CalcFloats(Floats: array of Double): Double;
var
  I: Integer;
begin
  if Length(Floats) < 2 then
  begin
    Result = -1;
    Exit;
  end;
  Result := 1;
  for I := Low(Floats) to High(Floats) do
  begin
    if Floats[I] = 0 then
    begin
      Result := -2;
      Exit;
    end;
    Result := Result * Floats[I]
  end
end;

procedure TForm1.Button9Click(Sender: TObject);
var
  Radius, Res: Double;
begin
  Radius := 10; //this should be read from the user
  Res := CalcFloats([Pi, Radius, Radius]);
  if Res < 0 then
    ShowMessage('There was an error, code %g', [Res])
  else
    ShowMessage('The answer was %g', [Res])
end;
'

You can see some code that uses the routine, passing three double expressions to CalcFloats. It is obliged to check for error values being returned before proceeding to use the calculated return value.

Contrast that with the following version of the routine that raises exceptions to represent input problems. The code that uses the function's result has no need to check for an error. If one occurs during the call to CalcFloats the execution path will immediately jump to the closest error handle, which by default is the application error handler that automatically displays the error message.


function CalcFloats(Floats: array of Double): Double;
var
  I: Integer;
begin
  if Length(Floats) < 2 then
    raise EMathError.Create('Too few arguments');
  Result := 1;
  for I := Low(Floats) to High(Floats) do
  begin
    if Floats[I] = 0 then
      raise EMathError.Create('Arguments must be non-zero');
    Result := Result * Floats[I]
  end
end;

procedure TForm1.Button9Click(Sender: TObject);
var
  Radius, Res: Double;
begin
  Radius := 10; //this should be read from the user
  Res := CalcFloats([Pi, Radius, Radius]);
  ShowMessage('The answer was %g', [Res])
end;

Figure 7: A family of custom exceptions

In this particular case a predefined error class was used (EMathError) but you can also define your own exception class types by inheriting from existing exception classes. This allows you to design your own mini-exception hierarchy that you can use to perform specific handling of your own personal exceptions. The following version uses custom exceptions to represent each problem that can be reported.


type
  ELXFError = class(Exception);
  ELXFMathError = class(ELXFError);
  ELXFZeroArg = class(ELXFError);
  ELXFTooFewArgs = class(ELXFError);
...
function CalcFloats(Floats: array of Double): Double;
var
  I: Integer;
begin
  if Length(Floats) < 2 then
    raise ELXFTooFewArgs.Create('Too few arguments');
  Result := 1;
  for I := Low(Floats) to High(Floats) do
  begin
    if Floats[I] = 0 then
      raise ELXFZeroArg.Create('Arguments must be non-zero');
    Result := Result * Floats[I]
  end
end;

Figure 7 shows how these exceptions fit into the existing hierarchy and this code shows how you can trap any custom mathematical error (including ELXFZeroArg and ELXFTooFewArgs) coming from the CalcFloats call:


procedure TForm1.Button9Click(Sender: TObject);
var
  Radius, Res: Double;
begin
  Radius := 10; //this should be read from the user
  try
    Res := CalcFloats([Pi, Radius, Radius]);
    ShowMessage('The answer was %g', [Res])
  except
    on E: ELXFMathError do
      ShowMessage(E.Message)
  end
end;

Of course this does nothing more than the application's default exception handler but it confirms that you can trap a variety of errors by choosing a common base exception class to handle. And if you want to react to specific exceptions this can be achieved by writing individual exception handlers for each one that is of interest.

One last thing to mention about raising exceptions is the CLX Abort procedure, which simply raises an EAbort exception. The application default exception handler completely ignores EAbort, meaning it never produces a message box (as it does for all other exceptions) and does not get delivered to the Application object's OnException event.

This fact is why EAbort is sometimes referred to as a "silent" exception. You can use a call to Abort to break the natural execution path. If no try..except statement catches it the Application object will pick it up and kill it in the same way it does for other unhandled exceptions, but without any intrusive dialog. So Abort can effectively stop an event handler in its tracks and "reset" the application to wait for the next event to handle.

The Kylix debugger defaults to ignoring EAbort, even if it intercepts all others (the default behaviour). You can specify any number of exceptions that you want the debugger to ignore on the Language Exceptions page of its options dialog (Tools | Debugger Options...). Just press Add... and type in the name of an exception class and it will be added to a list of ignored exceptions (see Figure 8).

Figure 8: Telling the debugger to ignore some exceptions

The Problem With Exceptions (And The Solution)

After all this glorification of exceptions you might think we'd be hard pressed to find a problem with them. But there is indeed a problem (not with Kylix's implementation or support of them, but a problem that is common to all other languages that support exceptions, such as C++ and Ada). This problem is with resource protection.

The very nature of exceptions causes execution to divert from its natural path, hunting down an exception handler. The problem arises when the code already executed has performed some operation that needs to undone, but the code to undo it is skipped because of an exception. You should be able to picture a few obvious scenarios where this might be a problem. For example:

If an exception occurs in the in-between part (processing the file contents, using the memory or in the time-consuming operation, from the examples above) then the important last parts (closing the file, freeing the memory, or restoring the mouse cursor) will be skipped.

There are many cases where this kind of problem can arise and so a general purpose solution is required for it. All languages that support exceptions offer a solution to the problem although the one offered by the ANSI standard C++ language is rather more involved than Object Pascal's.

Resource Protection

So the resource protection issue arises where some form of resource (be it a file, cursor, memory block, dynamically created component) is set up, used by some code and then tidied up (closed or freed or whatever) in a sequential series of statements that will execute. This is shown in the following pseudocode:


resource allocation
code that uses the resource
resource deallocation

If an exception occurs in the code between the allocation and deallocation then the deallocation will not occur.

The Object Pascal solution to this problem is another compound statement dedicated to the task in hand. The try..finally statement is used to guarantee the execution of important code (such as resource deallocation code). You use it like this:


resource allocation
try
  code that uses the resource
finally
  resource deallocation 
end

The resource allocation goes first. If you fail to allocate the resource (for example there is not enough memory available to satisfy the memory request, or a file could not be opened) then there is no point trying to deallocate the resource (free the memory or close the file).

Once execution hits the try you are guaranteed that the finally part will execute, regardless of whether any exceptions occur or not. This is why a try..except statement cannot validly overcome this problem; it is designed to execution special code only if an exception occurs whereas a try..finally statement executes some special code all the time.

Some programmers occasionally "fix" this problem by catching and killing any exception that occurs, as in the following code block. This is frowned upon as it is considered bad practice to kill all exceptions arbitrarily. Instead you should handle exceptions you are either anticipating or can foresee potentially occurring, which means the problem should be solved with the try..finally statement.


//Not a good way of dealing with resource protection issues
resource allocation
try
  code that uses the resource
except
end;
resource deallocation

With this in mind, the code from last month that appended a line to a text file should now be written as follows. If AssignFile generates an exception the try..finally that closes the file will not be executed at all, but the error handling will pick the error up. If an exception occurs whilst trying to set the file to append mode (inside the try part of the try..finally) the finally part will ensure the file is closed before the exception makes it way to the EInOutError exception handler.


if dlgSave.Execute then
  try
    AssignFile(TF, dlgSave.FileName);
    try
      Append(TF);
      WriteLn(TF, 'Hello world');
    finally
      CloseFile(TF)
    end
  except
    on E: EInOutError do
      case E.ErrorCode of
        ENOENT: ShowMessage('"%s" cannot be located', [dlgSave.FileName]);
        EACCES: ShowMessage('You do not have permission to overwrite this file');
      else
        if E.ErrorCode < 100 then
          ShowMessage('I/O Error'#10#9'Error code: %d'#10#9 +
            'Error msg: %s', [E.ErrorCode, SysErrorMessage(E.ErrorCode)])
        else
          ShowMessage('I/O Error'#10#9'Error code: %d'#10#9 +
            'Error msg: %s', [E.ErrorCode, E.Message]);
      end;
  end

Abbreviated Component Creation

If you need to use a component or object for a short period of time, such as in an event handler, you should use a try..finally statement to ensure it gets destroyed. This is commonly done with TIniFile objects (used to store application settings in simple text files) as discussed a few months back. If you only have a couple of settings to make there is no need to keep the TIniFile object in existence all the time. Instead you can create it, use it and then destroy it. This can be wrapped up in a reusable routine if necessary, as in:


procedure WriteIniSetting(const IniFile, Section, Ident, Value: String); overload;
var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create(IniFile);
  Ini.WriteString(Section, Ident, Value);
  Ini.UpdateFile;
  Ini.Free
end;

It is very common to abbreviate this type of code using a with statement, which allows you to dispense with the local variable. You can create the object, enter its scope, use it and then free it like this:


procedure WriteIniSetting(const IniFile, Section, Ident, Value: String); overload;
begin
  with TIniFile.Create(IniFile) do
    try
      WriteString(Section, Ident, Value);
      UpdateFile
    finally
      Free
    end
end;

The same can be done with dynamically created forms that are to be used as dialogs if you want. This code from an example in a past issue was used to dynamically create a form:


frmDialog := TfrmDialog.Create(Application);
//Set checkbox to reflect window visibility
frmDialog.CheckBox1.Checked := frmWindow.Visible;
if frmDialog.ShowModal = mrOk then
  frmWindow.Visible := frmDialog.CheckBox1.Checked;
frmDialog.Free;
frmDialog := nil

This needs resource protection to ensure that the call to Free is made, and can also be abbreviated using a with statement as follows:


with TfrmDialog.Create(Application) do
  try
    //Set checkbox to reflect window visibility
    CheckBox1.Checked := frmWindow.Visible;
    if ShowModal = mrOk then
      frmWindow.Visible := CheckBox1.Checked
  finally
    frmDialog.Free
  end

Miscellaneous

A minor point before leaving you this month; last month we introduced the subject of exceptions by noting that the translation routine StrToInt generates an EConvertError exception if you pass it a string that does not represent a 32-bit integer. The same is also true of StrToInt64 (if the string doesn't represent an Int64, a 64-bit integer).

The exception can be avoided by using a related routine, StrToIntDef (or StrToInt64Def), which takes a default value to return in case the string is bad. So StrToIntDef('hello', 0) returns 0. Kylix 2 introduced more of these exception-free translation routine equivalents as shown in the following table.

Translation routine Exception-free equivalent
StrToBool StrToBoolDef
StrToFloat StrToFloatDef
StrToCurr StrToCurrDef
StrToDate StrToDateDef
StrToTime StrToTimeDef
StrToDateTime StrToDateTimeDef

Summary

Next month we'll look at some simple graphics programming using the CLX TCanvas class. In the meantime, if there is something about Kylix Open Edition you want to see covered here, drop us an email and we'll try our best to incorporate it into a future instalment.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware.

If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.

Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi 2000 award.


Back to top