Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents
Bookmark and Share

Part I - Foundations
  Chapter 1 Delphi 7 and Its IDE
  Chapter 2 The Delphi Programming Language
  Chapter 3 The Run-Time Library
  Chapter 4 Core Library classes
  Chapter 5 Visual Controls
  Chapter 6 Building the User Interface
  Chapter 7 Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 The Architecture of Delphi Applications
  Chapter 9 Writing Delphi Components
  Chapter 10 Libraries and Packages
  Chapter 11 Modeling and OOP Programming (with ModelMaker)
  Chapter 12 From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 Delphi's Database Architecture
  Chapter 14 Client/Server with dbExpress
  Chapter 15 Working with ADO
  Chapter 16 Multitier DataSnap Applications
  Chapter 17 Writing Database Components
  Chapter 18 Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 Internet Programming: Sockets and Indy
  Chapter 20 Web Programming with WebBroker and WebSnap
  Chapter 21 Web Programming with IntraWeb
  Chapter 22 Using XML Technologies
  Chapter 23 Web Services and SOAP
  Chapter 24 The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 Delphi for .NET Preview: The Language and the RTL
       
  Appendix A Extra Delphi Tools by the Author
  Appendix B Extra Delphi Tools from Other Sources
  Appendix C Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

Working with Exceptions

Another key feature of Delphi is its support for exceptions. Exceptions make programs more robust by providing a standard way for notifying and handling errors and unexpected conditions. Exceptions make programs easier to write, read, and debug because they allow you to separate the error-handling code from your normal code, instead of intertwining the two. Enforcing a logical split between code and error handling and branching to the error handler automatically makes the actual logic cleaner and clearer. You end up writing code that is more compact and less cluttered by maintenance chores unrelated to the actual programming objective.

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 next system message or user request.

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 resources acquired 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.

Program Flow and the finally Block

The power of exceptions in Delphi relates to the fact that they are "passed" from a routine or method to the caller, up to a global handler (if the program provides one, as Delphi applications generally do), instead of following the standard execution path of the program. So the real problem you might have is not how to stop an exception but how to execute code even if an exception is raised.

Consider this code, which performs some time-consuming operations and uses the hourglass cursor to show the user that it's doing something:

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 sort) occurs.

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 finalization code at the end of a method, you should place the code in a finally block. You should always, invariably, and continuously (how can I stress this more?) protect your code with finally statements, to avoid resource or memory leaks in case an exception is raised.

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 indicate errors in the program flow and possibly a misunderstanding of the role of exceptions in the language. In the examples in the rest of the book, you'll see many try/finally blocks, a few raise statements, and almost no try/except blocks.

Exception Classes

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;

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 ancestor Exception classes) at the end. But keep in mind that using a handler for every exception, such as the previous one, is not usually a good choice. It is better to leave unknown exceptions to Delphi. The default exception handler in the VCL displays the error message of the exception class in a message box, and then resumes normal program operation. You can modify the normal exception handler with the Application.OnException event or the OnException event of the ApplicationEvents component, as demonstrated in the ErrorLog example in the next section.

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.


 
Previous Section Next Section


 


 


Copyright © 2004-2020 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
   Facebook     Twitter