Last Updated:

Exceptions | Delphi | Tutorial

Table of Contents

If an error occurs and an exception is initiated, it will be processed according to the following algorithm:

  1. If the situation occurs inside the try.. except, then there it will be processed. If the IC is "advanced" further using the raise statement, as well as if it originated in the try. finally, processing continues.
  2. If the programmer defines a handler for the Application.onException event, it will have control. The handler is declared as follows:
TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;
  1. If the programmer has not determined the response to the IC, the standard showException method will be called, which will inform about the class and the location of the exception.

Steps 2 and 3 are implemented in the TAppiication.HandieException method. Actually, they look like this:

if not (ExceptObject is EAbort) then
if Assigned(FOnException) then
FOnException(Sender, Exception(ExceptObject))
else
ShcwExceptior. (Exception(ExceptObject));

You need the onException handler if you want to perform the same action in any exceptional situation that occurs in your application. For example, name yourself, specify the coordinates for the appeal or warn that this is still a beta version.

program Project!;
uses
forms,
SysUtils, //added manually - the Exception Dialogs class is described there,
Unitl in 'Unitl.pas' {Forml};
{$R *.RES}
type
TExceptClass = class
public
procedure GlobalExceptionHandler(Sender: TObject; E:Exception);
end;
procedure TExceptClass.GlobalExceptionHandler(Sender: TObject;
E:Exception);
begin
ShowMessage('An exception has occurred ' + E.ClassName
+ ': ' + E.Message
+ #13#10'Contact the developers at tel. 222-33-44');
end;
begin
with TExceptClass.Create do
begin
Application.OnException := GlobalExceptionHandler;
Application.Initialize;
Application.CreateFormfTForml, Form);
Application.Run;
free;
end;
end.

Here, the TExceptClass is created only to be the carrier of the GiobalException method. The handler for any event is a method, and it must refer to an object. Since it is needed here even before the forms of the application and its other components are initialized, the object of the TExceptClass class is created first. Now the user knows that it is necessary to thank for the surprises on the phone number of the developers specified in the error message.

Note
There is also an easier way to assign a handler to the Application.OnException event by placing a component of type TApplicationEvents (The Additional Component Palettes page) on the form whose role is to provide "visual" access to the properties of the non-visual TApplication object. Among its events is OnException
.

But how to "touch" the object transferred in an exceptional situation? Conventional design:

on EExceptionType do…

Points to an object class, but not to a specific instance. If you need to access the properties of this instance during processing, you must name it inside on.. do, putting an identifier before the class name:

on EZD: EZeroDivide do EZD.Message: = 'Division by zero!';

Here, the exception that occurred is named EZD. You can change its properties and send it further:

var APtr: Pointer;
Forml: TForm;
try
APtr: = Forml;
with TObject(APtr) as TBitmap do;
except
on EZD: EInvalidCast do EZD.Message: =. EZD.Message + 'xa-xa!';
Raise;{ now the processing will be done elsewhere }
end;

But how to name an exception that did not fall into any of the directives on.. do? Or maybe your handler doesn't have on at all. do, but do you need to work with the object? The path described above does not apply here. For these cases, there are a pair of system functions Exceptobject and ExceptAddr. Unfortunately, these functions are initialized only inside the try. except; in try.. finally working with the object – it is not possible to work with an exceptional situation.

Exception logging

 

It is often necessary to have detailed material for analyzing the causes of the ip. It would be reasonable to write all the data about them to a file in order to predict the situation later. This approach is important for programs that will somehow be alienated from the developer: in the event of an unforeseen situation, this will allow you to answer the questions "who is to blame?" and "what to do?". In the following example, an option for implementing IC logging is proposed.

const LogName: string = 'c:\appexc.log';
procedure LogException;
var fs: TFileStream; m: word;buf: array[0..511] of char;
begin
if FileExists(LogName) then m: = fmOpenReadWrite else m: = fmCreate;
fs: = TFileStream.Create(LogName,m);
fs.Seek(0,soFromEnd);
StrPCopy(Buf,DateTimeToStr(Mow)+'. ');
ExceptionErrorMessage
(ExceptObject,ExceptAddr,@buf[StrLenfbuf)],
SizeOf(Buf)-StrLen(buf));
StrCat(Buf,#13#10);
fs.WriteBuffer (Buf, StrLer.;buf));
fs.Free;
end;
procedure TForml.ButtonlClick(Sender: TObject);
var x,y,z: real;
begin
try
try
x: = 1.0;y: = 0.0;
z: = x/y;
except
LogException;
raise;
end;
except
on E:EIntError do ShowMessage('IntError');
on E:EMathError do ShowMessage('MathError');
end;
end;

Here, the task of recording information about the IP is solved by the LogException procedure. It opens a file stream and writes there the information formatted with the help of the already mentioned ExceptionErrorMessage function.

Its parameters are the values of the Exceptobject and ExceptAddr functions. The time of occurrence of the IC is added to the generated line. For each block of code that is protected, two nested try constructs are created. except. The first, internal, is for you; in it, the IP is logged and advanced further. External – for the user; it is in it that the ip type analysis is carried out and the message is prepared.

In Object Pascal, there is an extended version of the use of the raise operator:

raise an instance of an object of type Exception> [at <address>]

Naturally, the object type must be derived from Exception. The fact that nothing is overridden in this type is not so important - the main thing is that in the IC handler you can track this type.

ELoginError = class (Exception);
If LoginAttemptsNo > MaxAttempts then raise ELoginError.Create('User registration error');

The at < address> construct is used to change the address to which the resulting IC is bound within a single IC processing unit.

Exception error codes

 

If your app is already up for sale, if you're planning technical support for it, then it's time to think about assigning numeric codes.

A message such as "Exception EZeroDivide in module MyNiceProgram at addr $0781BABO" is suitable for the developer, but it will plunge the user into a complete stupor. If he calls your technical support service, he most likely will not be able to explain anything. It is much more competent to give him already "chewed" information and, in particular, a numerical code.

One of the ways to solve this problem is to place error messages in the resources of the program. If you still make several national versions of the program in different languages, then this way is the only one.

The "classic" way to put text into a resource file is a 3-step one:

  1. A source resource file with a GF extension is created, in which the necessary lines with the desired numbers are placed.
  2. The file is processed by the brcc32.exe resource compiler (located in the bin folder in the Delphi folder structure). The output is a file of the same name with the extension .res.
  3. The file is included in the program by specifying a directive $R, for example:
{$R mystrings.res}.

To share error number constants in a resource file and in Delphi code, let's put them in a separate include file with the extension .inc:

const
IOError = 1000;
FileOpenError = IOError + 1;
FileSaveError = IOError + 2;
InternetError = 2000;
NoConnecticnError = InternetError + 1;
ConnectionAbortedError = InternetError + 2;

Looking at the file, you will see that the errors in it are grouped into categories. We advise you to do the same, dividing the category constants by an interval of 1000 or even 10,000.

The resource file itself might look like this:

#include "strids.inc" STRINGTABLE
{
FileOpenError, "File Open Error"
FileSaveError, "File Save Error"
NoConnectionError, "No Connection"
ConnectionAbortedError, "Connection Aborted"
}

There are several ways to "pull" a string out of resources, but the simplest one is simply by the numeric identifier passed to the Loadstr function (SysUtils module). Code:

ShowMessage(LoadStr(NoConnectionError));

Will display the message "NO connection".

If the string is used when initiating the IC, then the place of the identifier is in the overlapping constructor Exception.createRes, one of the variants of which works like the Loadstr function:

if FileOpent'c:\myfile.txt", fmOpenRead) = INVALID_HANDLE_VALUE then
raise EMyException.CreateRes(FileOpenError);

Thus, half of the problem has been solved: numbers have been assigned to possible exceptions, and the text has been put in line with them. Now about the second half - how to use this number in the IP processor.

It is clear that you need to declare your own IC class that includes the error code property.

EExceptionWithCode = class(Exception)
private
FErrCode: Integer;
public
constructor CreateResCode(ResStringRec: PResStringRec);
property ErrCode: Integer read FErrCode write FErrCode;
end;

Then any handler will be able to access it:

if E is EExceptionWithCode then
ShowMessage('Error code: ' + IntToStr(EExceptionWithCode(E).ErrCode) +
#13*10
+ 'Error text: ' + E.Message);

There are two ways to set the ErrCode property to a value:

  1. Add another constructor to the IC class that contains the code as an additional parameter:
constructor EExceptionWithCode.CreateResCode(Ident: Integer);
begin
FErrCode: = Ident;
inherited CreateRes(Ident);
end;
  1. Assign a property value in the interval between the creation of the IC object and its excitation:
var E: EExceptionWithCode; begin
E: = EExceptionWithCode.CreateRes(NoConnectionError);
E.ErrCode: = NoConnectionError;
Raise E;
end;

Here, it would seem, is the final touch. But what about those who have not prepared a resource file in advance, but are working with the lines described in the PAS files? If you use the resourcestring operator, you can help.

Let's start by looking at the resourcestring keyword. It is followed by text constants. But, unlike the const keyword, these constants are placed not in the data segment of the program, but in resources, and are loaded from there as needed. Each such constant is perceived and treated as a regular string. But behind each of them is actually this structure:

PResStringRec = ^TResStringRec;
TResStringRec = packed record
Module: ^Cardinal;
Identifier: Integer;
end;

If you look again at the list of constructors in the Exception object, you'll see that those that work with resources have a loadable version with a parameter of type pResstringRec. You guessed right: they are for lines from resourcestring. And if you look at the structure above, you will see the identifier field in it. That's what we need.

To ensure that a programmer using resourcestring doesn't have a headache about the unique identifiers of resource strings, Delphi takes care of this. Numbers are assigned by the compiler, starting from 65,535 (SmallInt (-D) and below (if we consider the number as a type (SmallInt, then above): 65,534, 65,533, etc. First, in this list there are several hundred resourcestring constants described in the VCL (from modules whose name ends in const or consts: sysconst, DBConsts, etc.). Then the queue reaches the user constants (Figure 3.3).

On the one hand, the absence of unnecessary worries is a big plus; on the other hand, the developer cannot give the strings the numbers he wants.

Everything else is almost no different from working with "homemade" resources. This is the overloaded version of the constructor of our EExceptionWithCode object:

constructor EExceptionWithCode.CreateResCode(ResStringRec:
PResStringRec);
begin
FErrCode: = ResStringRec^.Identifier;
inherited CreateRes(ResStringRec);
end;

And so – the excitation of the IP itself:

resourcestring sErrorl = 'Error 1';
Raise EExceptionWithCode.CreateResCode
(PResStringRec(@sErrorl));

An EAbort exception. The Assert function.

 

If you looked closely at the code for the HandieException system procedure, you saw a mention of the EAbort class. The EAbort IC serves as the only – and very important – exception to the processing rules. It is called "Silent" and differs in that it does not receive default processing of messages on the screen. Naturally, all of this also applies to the child object classes generated from it.

The use of EAbort is justified in many cases. Here's one example. Suppose you are developing some application program or some family of objects that is not related to VCL. If an IP arises in them, then you need to somehow notify the user about it. Meanwhile, a direct call to this showMessage function or even a MessageBox is not always justified. For a small and compact dynamic link library, you don't have to drag a bunch of VCL along with you. On the other hand, in a large and heterogeneous project, you can't let each object or subroutine communicate with the user itself. If they are developed by different people, such a project can turn into a Tower of Babel. This is where EAbort will help. This exception is not created by the system – it must be created and maintained by the programmer.

The use of EAbort is a real alternative to numerous if. then and even more so (God forbid!) go.. to. This IC should not replace others, such as a memory allocation error or reading from a file. It is needed if you yourself see that certain conditions have developed and it is time to change the logic of the program.

If LogicalCondition then Raise EAbort.Create('Condition 1');

If you do not want to define a message, you can create an EAbort or more simply by calling the Abort procedure (without parameters) contained in the SYSUTILS module. PAS.

Assert function

This procedure and the accompanying EAssertionFailed IS are specifically ported to Object Pascal from the C language for easy debugging. Its syntax is simple:

procedure Assert(expr: Boolean [; const msg: string]);

When a function is called, it is checked to see what the value of the Boolean expression expir passed to it is. If it is True, then absolutely nothing happens. If it is False, an EAssertionFailed IP is created. All of this would be rather trivial from the point of view of what has already been studied, if not for two circumstances:

  1. The predefined EAssertior.Failed handler is designed in such a way that it does not give a hexadecimal address of the error, but the name of the source file and the line number where the IC occurred, as shown in Fig. 3.4.
  2. With the help of a special compiler directive {$ASSERTIONS ON/OFF} (or, equivalently, {$c+}/{$c-}), the emergence of these ICs can be centrally prevented. That is, in the debug code in {$c+} mode, you can place an Assert call in all questionable and verifiable places. When it comes time to generate the final version of the code, switching the directive to {$c-} prohibits all debug output.

Resume

Any application created in Delphi must handle possible exceptions. At first, let it seem unnecessary to create additional error-handling code for the simplest programs in which the probability of error is minimal. But later acquired skills will allow you to significantly increase the reliability of real applications.

Delphi uses special Object Pascal constructs and classes based on the Exception exception base class to handle exceptions.