Exception Handling

Exceptions let you interrupt a program's normal flow of control. You can raise an exception in any function, procedure, or method. The exception causes control to jump to an earlier point in the same routine or in a routine farther back in the call stack. Somewhere in the stack must be a routine that uses a try-except-end statement to catch the exception, or else Delphi calls ExceptProc to handle the exception.

Delphi has two related statements for dealing with exceptions. The try-except statement sets up an exception handler that gets control when something goes wrong. The try-finally statement does not handle exceptions explicitly, but guarantees that the code in the finally part of the statement always runs, even if an exception is raised. Use try-except to deal with errors. Use try-finally when you have a resource (such as allocated memory) that must be cleaned up properly, no matter what happens. The try-except statement is similar to try-catch in C++ or Java. Standard C++ does not have finally, but Java does. Some C++ compilers, including Borland's, extend the C++ standard to add the same functionality, e.g., with the_finally keyword.

Like C++ and Java, Delphi's try-except statement can handle all exceptions or only exceptions of a certain kind. Each try-except statement can declare many on sections, where each section declares an exception class. Delphi searches the on sections in order, trying to find an exception class that matches, or is a superclass of, the exception object's class. Example 1-10 shows an example of how to use try-except.

Example 1-10: Using try-except to Handle an Exception function CoirputeSomething: begin try

PerformSomeDifficultConputation; except on Ex: EDivideByZero do

WriteLn('Divide by zero error');

22 Chapter 1 - Delphi Pascal

Example 1-10: Using try-except to Handle an Exception (continued)

on Ex: EOverflow do

WriteLn('Overflow error'); else raise; // reraise the same exception, to be handled elsewhere end; end;

In a multithreaded application, each thread can maintain its own exception information and can raise exceptions independently from the other threads. See Chapter 4 for details.

When your code raises an exception, it must pass an object to the raise statement. Usually, a program creates a new exception object as part of the raise statement, but in rare circumstances, you might want to raise an object that already exists. Delphi searches the call stack to find try statements. When it finds a try-finally, it executes the code in the finally part of the statement, then continues to search the stack for an exception handler. When the stack unwinds to a try-except block, Delphi searches the on sections to find one that matches the exception object. If there are no on sections, Delphi runs the code in the except part of the statement. If there are on sections, Delphi tries to find a match, or it runs the code in the else part of the except block.

The variable that is declared in the on statement contains a reference to the exception object. Delphi automatically frees the object after the exception handler finishes. (See Chapter 2 for more information on objects.)

If Delphi reaches the end of the call stack without finding a matching exception handler, it calls ExceptProc. ExceptProc is actually a pointer variable, pointing to a procedure of two arguments: the exception object and the address where the exception occurred. For example, you might want to record unhandled exceptions in a special log file, as shown in Example 1-11.

Example 1-11: Logging Unhandled Exceptions to a File var

procedure LogExceptProc(ExceptObject: TObject; ErrorAddr: Pointer); const

Size = 1024; resourcestring

Title = 'Internal error: Please report to technical support'; var

Buffer: PChar[0..Size-1]; F: TextFile; begin

ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, Size);

AssignFile(F, LogFileName); if FileExists(LogFileName) then

AppendFile(F) else

Rewrite(F);

Example 1-11. Logging Unhandled Exceptions to a File (continued)

WritelJi(F, Buffer); CloseFile(F);

MessageBox(0, Buffer, Title, Mb_IconStop) ; end;

// Tell Delphi to use your exception procedure. ExceptProc := @LogExceptProc;

Delphi also catches runtime errors, such as stack overflow, and calls ErrorProc for each one. Note that ErrorProc is actually a pointer variable whose value is a procedure pointer. To set up an error handler, declare a procedure and assign its address to ErrorProc.

The System unit deals with two kinds of error codes: internal and external. If you write an ErrorProc procedure, it must deal with internal error codes. These are small numbers, where each number indicates a kind of error. Chapter 6 lists all the internal error codes. Delphi's default ErrorProc maps internal error codes to external error codes. External error codes are documented in Delphi's help files and are visible to the user. Chapter 6 also lists the external error codes.

When Delphi calls ErrorProc, it passes two arguments: the error code and the instruction address where the error occurred. Your error handler might look like the following, for example:

procedure DumbErrorProc(ErrorCode: Integer; ErrorAddr: Pointer); begin

ShovMessage(Format('Runtime error %d at %p', [ErrorCode, ErrorAddr])); end;

ErrorProc := SDuiribErrorProc;

The SysUtils unit provides extra help for working with exceptions and runtime errors. In particular, it defines ErrorProc and ExceptProc procedures. ErrorProc turns a runtime error into an exception, such as EStackOverf low for a stack overflow error. The ExceptProc routine displays the exception message, then halts the program. In a console application, the exception message is written to the standard output, and in GUI applications, it is displayed in a dialog box.

The SysUtils unit sets up the ErrorProc and ExceptProc routines in its initialization section. If your application raises an exception or runtime error before the SysUtils unit is initialized, you won't get the benefit of its routines and exception handlers. Therefore, when your application reports a raw runtime error, not wrapped as an exception, your problem probably lies in an initialization or finalization section.

24 Chapter 1 - Delphi Pascal

To raise an exception, use the raise statement, followed by an object reference. Usually, the raise statement creates a brand-new object. You can create an object of any class to use as the exception object, although most programs use SysUtils.Exception or one of its derived classes.

Delphi keeps track of information about an exception, where it was raised, the program's context when it was raised, and so on. You can access this information from various variables in the System unit. The full details are explained in Chapter 5, but Table 1-3 presents an overview of the relevant variables.

Table 1-3: Exception and Error-Related Variables

Declaration

Description

AbstractErrorProc

Abstract method error handler.

AssertErrorProc

Assertion error handler.

ErrorAddr

Address of runtime error.

ErrorProc

Error handler procedure.

ExceptClsProc

Map a Windows exception to a

Delphi class.

ExceptionClass

Exception base class.

ExceptObjProc

Map a Windows exception to a

Delphi object.

ExceptProc

Unhandled exception handler.

SafeCallErrorProc

Safecall error handler.

When an exception unwinds the call stack, Delphi calls the code in the finally part of each enclosing try-finally block. Delphi also cleans up the memory for dynamic arrays, long strings, wide strings, interfaces, and Variants that have gone out of scope. (Strictly speaking, it decreases the reference counts, so the actual memory is freed only if there are no other references to the string or array.)

If a finally block raises an exception, the old exception object is freed, and Delphi handles the new exception.

The most common use for a try-finally statement is to free objects and release other resources. If a routine has multiple objects to free, it's usually simplest to initialize all variables to nil, and use a single try-finally block to free all the objects at once. If an object's destructor is likely to raise an exception, though, you should use nested try-finally statements, but in most cases the technique shown in Example 1-12 works well.

Example 1-12: Using try-finally to Free Multiple Objects

// Copy a file. If the source file cannot be opened, or the // destination file cannot be created, raise EFileCopyError, // and include the original error message in the new exception // message. The new message gives a little more information // than the original message, type

EFileCopyError = class(EStreamError);

procedure CopyFile(const ToFile, FromFile: string); var

Example 1-12: Using try-finally to Free Multiple Objects (continued)

FromStream, ToStream: TFileStream; resourcestring sCannotRead = 'Cannot read file: %s'; sCannotCreate = 'Cannot create file: %s'; begin

FromStream := TFileStream.Create(FramFile, fmOpenRead); except

  • Handle EFopenError exceptions, but no other kind of exception, on Ex: EFOpenError do
  • Raise a new exception.

raise EFileCopyError.CreateFmt(sCannotRead, [Ex.Message]);

end; try

ToStream := TFileStream.Create(ToFile, fmCreate); except on Ex: EFCreateError do raise EFileCopyError.CreateFtat(sCannotCreate, [Ex.Message]);

end;

  • Now copy the file. ToStream.CopyFrom(FromStreaiii, 0); finally
  • All done. Close the files, even if an exception was raised. ToStream.Free; FromStream.Free; end; end;

Was this article helpful?

+1 0

Post a comment