Handling exceptions in Delphi

By: Corbin Dunn

Abstract: Examples using try ... except and try .. finally

This article provides some information on setting up error handling with Delphi.

One of the big advantages of using exceptions is that you do not have to check the result of every function call that you make. Typical C programming involves making a function call (such as allocmem) and checking the result to see if the function succeeded or not. More than likely, the function will always work. Having an exception being raised when an error does happen, makes less work for the programmer.

The best way to catch exceptions is by defensive programming. Any code which could possibly raise an exception can be wrapped in one of two blocks: a try ... except block or a try ... finally block.

Here is an example of using try ... except:

procedure TForm1.FormCreate(Sender: TObject);
begin
  try
    // Some code which may raise an exception
  except
    on E: EConvertError do
    begin
      // Handle only EConvertError, or any derived classes
    end;
    on E: Exception do
    begin
      // Handle only Exception, or any derived classes
    end;
  end;
end;
Once an exception is raised, the exception handler jumps immediately to the nearest except (or finally) block. Notice how you can pick exactly what exceptions you want to handle. Also, notice that EConvertError derives from Exception. You must list more specific (derived) exceptions before generic exceptions. Also, if you do not catch the exception (such as, if we had not put E: Exception in the handler), then the exception would continue to propagate outside of the try ... except block, and be caught else where.

The big difference between a try ... except block and a try ... finally block is that the try ... except block is only executed if an exception was raised, while the try ... finally is always executed. Because finally blocks are always executed, it doesn't make sense to catch specific exceptions with the on keyword, so this is not possible in them. Try ... finally's are typically used 3 to 1 times over try ... except's, as handling for an exceptional case it is much rarer than simply doing resource mangement. Normal resource management with a try ... finally block typically looks like this:

procedure TForm1.FormCreate(Sender: TObject);
var
  Form: TForm1;
begin
  Form := TForm1.Create(Self); // Allocate some memory
  try
    // Some code which may raise an exception
    Form.DoSomethingWhichMayRaiseAnException;
  finally
    Form.Free; // Always free the memory.
  end;
end;
The Form will always be freed. One may ask why the line Form := TForm1.Create(Self); is not inside the try ... finally block. The reason is simple: what if Create raised an exception? Then you would be freeing the Form variable when it was never allocated! Now, the compiler is smart enough to realize this, and would give you a warning if you put the Form inside the block:

[Warning] Unit1.pas(34): Variable 'Form' might not have been initialized

The next question may be what to do with the exception once you catch it. Sometimes, you may want to ignore certain exceptions (such as EConvertError), or execute certain code when an exception has been raised. If you don't handle the exception (or catch with a try...except block), then the default exception handler will show a dialog with the exception's message. You can customize this for your entire application by placing a TApplicationEvents component on your form and writing code for the OnException event. A reason for doing this might be to log the exception do a file. Or, you may want to display a custom dialog. I have an application which automatically closes the dialog after 15 seconds with no mouse clicks. Very handy!

Now, raising an exception is simple:

raise Exception.Create('some exception!');
Notice that it is possible to raise an exception inside of a try...except/finally block. If you want to re-raise the current exception, you can simply use raise:
procedure TForm1.FormCreate(Sender: TObject);
var
  Form: TForm1;
begin
  Form := TForm1.Create(Self); // Allocate some memory
  try
    // Some code which may raise an exception
    Form.DoSomethingWhichMayRaiseAnException;
  except
    on E: Exception do
      raise; // re-raise the currently caught exception
  end;
end;
Note that it is wrong to try and re-raise it with:
      raise E; // DON't DO THIS!
After you catch an exception, it is implicitly freed by the compiler (since, it has to be freed at some time!). Re-raising it with just raise doesn't free the current exception object. Calling raise E, where E is the caught exception, means that the compiler will implicitly free E twice!

This should give you a pretty good overview as to how to handle exceptions with Delphi.


Server Response from: ETNASC03