Another key feature of Delphi is its support for
exceptions
. Exceptions make programs more robust by providing a standard way for
At run time, Delphi libraries raise exceptions when something goes wrong (in the run-time code, in a component, or in the operating system). From the point in the code at which it is raised, the exception is passed to its calling code, and so on. Ultimately, if no part of your code handles the exception, the VCL handles it, by displaying a standard error message and then trying to continue the program by handling the
The whole mechanism is based on four keywords:
try Delimits the beginning of a protected block of code.
except Delimits the end of a protected block of code and introduces the exception-handling statements.
finally Specifies blocks of code that must always be executed, even when exceptions occur. This block is
generally used to perform cleanup operations that should always be executed, such as closing files or database tables, freeing objects, and releasing memory and other resourcesacquired in the same program block.raise Generates an exception. Most exceptions you'll encounter in your Delphi programming will be generated by the system, but you can also raise exceptions in your own code when it discovers invalid or inconsistent data at run time. The raise keyword can also be used inside a handler to
re-raise an exception; that is, to propagate it to the next handler.
| Tip |
Exception handling is no substitute for proper control flow within a program. Keep using if statements to test user input and other foreseeable error conditions. You should use exceptions only for abnormal or unexpected situations. |
The power of exceptions in Delphi
Consider this code, which
Screen.Cursor := crHourglass; // long algorithm... Screen.Cursor := crDefault;
In case there is an error in the algorithm (as I've included on purpose in the TryFinally example's event handlers), the program will break, but it won't reset the default cursor. This is what a try / finally block is for:
Screen.Cursor := crHourglass; try // long algorithm... finally Screen.Cursor := crDefault; end;
When the program executes this function, it always resets the cursor, regardless of whether an exception (of any
This code doesn't handle the exception; it merely makes the program robust in case an exception is raised. A try block can be followed by either an except or a finally statement, but not both of them at the same time; so, if you want to also handle the exception, the typical solution is to use two nested try blocks. You associate the internal block with a finally statement and the external block with an except statement, or vice versa as the situation requires. Here is the skeleton of the code for the third button in the TryFinally example:
Screen.Cursor := crHourglass; try try // long algorithm... finally Screen.Cursor := crDefault; end; except on E: EDivByZero do ... end;
Every time you have some
| Tip |
Handling the exception is generally much less important than using
finally
blocks, because Delphi can survive most exceptions. Too many exception-handling blocks in your code probably
|
In the exception-handling statements shown earlier, you caught the EDivByZero exception, which is defined by Delphi's RTL. Other such exceptions refer to run-time problems (such as a wrong dynamic cast), Windows resource problems (such as out-of-memory errors), or component errors (such as a wrong index). Programmers can also define their own exceptions; you can create a new inherited class of the default exception class or one of its inherited classes:
type EArrayFull = class (Exception);
When you add a new element to an array that is already full (probably because of an error in the logic of the program), you can raise the corresponding exception by creating an object of this class:
if MyArray.Full then raise EArrayFull.Create ( 'Array full' );
This Create constructor (inherited from the Exception class) has a string parameter to describe the exception to the user. You don't need to worry about destroying the object you have created for the exception, because it will be deleted automatically by the exception-handler mechanism.
The code presented in the previous excerpts is part of a sample program called Exception1. Some of the routines have been slightly modified, as in the following DivideTwicePlusOne function:
function DivideTwicePlusOne (A, B: Integer): Integer; begin try // error if B equals 0 Result := A div B; // do something else... skip if exception is raised Result := Result div B; Result := Result + 1; except on EDivByZero do begin Result := 0; MessageDlg ( 'Divide by zero corrected.' , mtError, [mbOK], 0); end; on E: Exception do begin Result := 0; MessageDlg (E. Message , mtError, [mbOK], 0); end; end; // end except end;
|
|
When you start a program from the Delphi environment (for example, by pressing the F9 key), you'll generally run it within the debugger. When an exception is
In the case of the Exception1 test program, however, this behavior will confuse a programmer not well aware of how Delphi's debugger works. Even if the code is prepared to properly handle the exception, the debugger will stop the program execution at the source code line
If you just want to let the program run when the exception is properly handled, run the program from Windows Explorer, or temporarily disable the Stop on Delphi Exceptions options in the Language Exceptions page of the Debugger Options dialog box (activated by the Tools Debugger Options command), shown in the Language Exceptions page of the Debugger Options dialog box shown here (as an alternative you can also disable the debugger):
|
|
In the Exception1 code, there are two different exception handlers after the same try block. You can have any number of these handlers, which are evaluated in sequence.
Using a hierarchy of exceptions, a handler is also called for the inherited classes of the type it refers to, as any procedure will do. For this reason, you need to place the broader handlers (the handlers of the
Another important element of the previous code is the use of the exception object in the handler (see on E: Exception do ). The reference E of class Exception refers to the exception object passed by the raise statement. When you work with exceptions, remember this rule: You raise an exception by creating an object and handle it by indicating its type. This has an important benefit, because as you have seen, when you handle a type of exception, you are really handling exceptions of the type you specify as well as any descendant type.