Web Tech Training & Development
Article: Procedural Types Introduction| Author: Rick Spence


Back to Basics - Procedural Types Introduction


Are you fed up of writing yet another routine to search a stringlist? Do you know about procedural types? A procedural type is a native Pascal data type of which you may not have heard, but which you are undoubtedly using. In this article I'll explain where you are using them already, dissect the syntax, and suggest some situations in which they are invaluable. You'll never need to write code to search a stringlist again.

The Basics
From the Delphi 5 help file: "Procedural types allow you to treat procedures and functions as values that can be assigned to variables or passed to other procedures and functions". In other words, you can store references to code in variables, which you can use to subsequently call that piece of code. This takes a bit of getting used to; you're used to using variables to store pieces of data, such as numbers or strings. Procedural types, on the other hand, allow you to store references to pieces of code. The variable containing the procedural type is actually a pointer to the piece of code.

Let's start with the basic syntax. Bare with me with this contrived example- you'll appreciate the real uses in a moment. Imagine you have the following two procedures:

Procedure DisplayWarning;

Begin
ShowMessage( 'This is a warning');
End;

Procedure DisplayError;

Begin
ShowMessage( 'This is an error' );
End;

We'll now declare a new type, which will allow us to declare variables which can reference one of these procedures:

Type TProcedurePtr = Procedure;

This declares a new type, TProcedurePtr, as a reference, or a pointer, to a procedure. We can then declare variables of that type:

Var
MyProc : TProcedurePtr;

Before you can use the variable you must assign a procedure to it, as in:

MyProc := DisplayError;

An important point to note is that this assignment does not call the procedure DisplayError. Rather, it stores a reference to DisplayError in the variable MyProc (actually it stores its address). Alternatively you could assign the DisplayWarning procedure to the variable myProc:

MyProc := DisplayWarning;

In fact, as you will see, you can assign any procedure which does not accept any parameters.

So now we have a variable which references a procedure - at some time we will want to run that procedure, which we do simply by using the variable in a statement:

MyProc;

This simple statement calls the subroutine referenced by the variable MyProc. Think of it as an indirect call. This does not call a subroutine called MyProc; indeed there isn't one, MyProc is a variable. It calls the subroutine to which MyProc refers, in this case DisplayWarning. Run the following through the debugger to convince yourself:

Var
MyProc : TProcedurePtr;

Begin
MyProc := DisplayError;
MyProc; // Call DisplayError

MyProc := DisplayWarning;
MyProc; // Call DisplayWarning;

That's the basics of procedural types. You may not know how you would use them yet, but by now you should understand the syntax.

Assigning Procedural Types
You can assign procedural types to each other in the same way you assign other variables - you are just copying the address of a procedure from one variable to another:

Var
MyProc1 : TProcedurePtr;
MyProc2 : TProcedurePtr;

Begin
MyProc1 := DisplayError;
MyProc2 := MyProc1;

MyProc1; // Call DisplayError
MyProc2; // Call DisplayError

As you'll see in a moment, this is exactly what you do when you share event handlers between two or more components.

Procedural Types with Parameters
What you just saw were two trivial procedures which did not receive parameters. You can also use procedural types with sub routines which do receive parameters, but you must declare the parameters along with the type. Previously we used:

Type TProcedurePtr = Procedure;

To declare a new type, TProcedurePtr, which was a reference to a procedure which did not receive any parameters. If the procedure must receive parameters, you must explicitly list them, with their type, as part of the type declaration, as in:

Type TProcedureMsgPtr = Procedure (msg : String );

The syntax on the right hand side of the equals sign may take a little getting used to - think of it as a normal procedure declaration without the procedure name.

You can now declare variables of type TProcedureMsgPtr:

Var
MyMsgProc : TProcedureMsgPtr;

But you must assign the variable a procedure which accepts one parameter, of type string:

Procedure DisplayMessage( s : String );
Begin
ShowMessage( s );
End;

// …
Var
MyMsgProc : TProcedureMsgPtr;
Begin
MyMsgProc := DisplayMessage;
End;

If you attempt to assign MyMsgProc a procedure which does not accept a single parameter, of type string, the compiler will complain.

Now, when you invoke the procedure using the procedural type, you must supply the parameter:

MyMsgProc('This is my message');

This calls the DisplayMessage routine, passing the string to the subroutine's formal parameter named s. Take a moment to ensure you understand this. MyMsgProc is a pointer to a subroutine which accepts a single parameter of type string. When you call the procedure to which MyMsgProc points, in this case DisplayMessage, you must pass the parameter. In fact, the following two lines of code have the same effect:

MyMsgProc('This is my message');
DisplayMessage('This is my message');

The first is calling DisplayMessage indirectly via a pointer to it. Why bother you might ask? In this case you wouldn't, you'd just call DisplayMessageDirectly; bare with me a moment longer.

Procedural Types as Methods
The procedural types you've seen so far have been references to stand alone subroutines. You use a very similar syntax to work with methods (in essence a method is only a subroutine which can access an object's data). To declare a procedural type which references a method rather than a stand alone subroutine, simply append the words Of Object to the type declaration:

Type TMethodMsgPtr = Procedure (msg : String ) Of Object;

Var
MethodMsgPtr : TMethodMsgPtr;

This is sufficient to inform the compiler that TMethodMsgPtr is a pointer to a method of an object, rather than a stand-alone procedure.

Then, when assigning to the variable, use the object name followed by the method name:

MethodMsgPtr := SomeObject.MsgMethod;

And call it indirectly in the same way you've already seen:

MethodMsgPtr( 'Some string for the method');

Event handlers are Procedural Types
At the start of this article I mentioned that you are already using procedural types in your Delphi applications. Where have you been using them? With your event handlers. Load Delphi's help for TForm and look at the help for one of the events, such as the onActivate event. You will see it declared as:

property OnActivate: TNotifyEvent;

If you follow the hyperlink to the declaration of TNotifyEvent, you will see:

type TNotifyEvent = procedure (Sender: TObject) of object;

which you should now understand. Variables of type TNotifyEvent are pointers to methods (note the of object in the declaration) which receive one parameter, of type TObject. Events, then, are procedural types. Put another way, events are implemented by having pieces of data contain pointers to pieces of code. An object of type TForm, or likely a sub class of TForm, contains many such pieces of data which reference pieces of code.

Similarly, controls' events are declared in a similar way. Load help and look at the onClick event of TButton:

property OnClick: TNotifyEvent;

onClick is a property which is a pointer to a method which accepts one parameter of type TObject.

If you poke around for long enough in the VCL source code, you can find the Pascal code which responds to messages from the Windows API and calls the programmer assigned event handlers. Since you are not obligated to write code for events, the VCL must check whether you assigned handler, and only call it if so. You will see the following sort of code throughout the VCL:

if Assigned(FOnDblClick) then FOnDblClick(Self);

This is from the TControl class, and is checking to see whether the programmer assigned a handler for the double click event, calling it if so.

Most events are declared as type TNotifyEvent, but not all. TForm's onClose event, for example, is passed two parameters. Here are the pertinent declarations:

TCloseEvent = procedure(Sender: TObject; var Action: TCloseAction) of object;

property OnClose: TCloseEvent;

onClose is declared as being of type TCloseEvent, which in turn is declared as a pointer to a method which receives two parameters, the first one of type TObject, the second of type TCloseAction. The onClose property, then, is a procedural type.

TForm's onCloseQuery event is also different:

type TCloseQueryEvent = procedure(Sender: TObject; var CanClose: Boolean) of object;
property OnCloseQuery: TCloseQueryEvent;


onCloseQuery is also a property which is a procedural type. This time, however, the procedural type is a procedure which accepts two parameters; the first of type TObject and the second a variable boolean parameter.

When you use the object inspector to define event handlers, Delphi is generating entries in the DFM file which assigns the address of the event handler to the property used to reference it. To verify this, open up a DFM file and you'll see entries such as:

object Button1: TButton
Left = 304
Top = 104
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 1
OnClick = Button1Click

As you see, this entry is defining a TButton called button1, and declaring initial values for some of its properties, including the onClick property. Button1Click is a method of the form in which Button1 is declared.

It's very common to have toolbar buttons use the same event as a menu item. You may have a menu item labeled Open File, for example, and a toolbar button which performs the same action. To accomplish this you write the code for the onClick event of one of the components, then use the object inspector to associate the same event with the other component. Delphi is simply making entries in the DFM file to have the two onClick properties reference the same event handler.

Note that the object inspector only allows you to assign events which have the same parameter lists. For example, if you've written code for the onCloseQuery event, you cannot assign this event to a push button's onClick property. OnClick is declared as a TNotifyEvent, which accepts one parameter of type TObject. OnCloseQuery is declared as a TCloseQuery event which expects two parameters. The two types are incompatible.

Dynamically Creating Controls
Another situation in which you will need to use procedural types is when you create controls dynamically at run time - you must assign the event handlers otherwise the controls won't do anything. Consider the code in Listing 1, which is taken from a main form's onCreate event. The event is reading a list of most recently used files from an IniFile named MruFiles.Ini, stored in the same location as the executable file itself. It is adding the names of these files as new menu items at the bottom of an existing menu item named File1. Note how the code assigns the procedure MruOpen to the onClick property of the new menuItem. When the user clicks on any of these new menu items Delphi will call the MruOpen event. You must declare MruOpen in the form's class declaration:

TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Open1: TMenuItem;
N1: TMenuItem;
exit1: TMenuItem;
OpenDialog1: TOpenDialog;
Button1: TButton;
procedure Open1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Procedure mruOpen(Sender : TObject);
end;

Then implement it as you would any other method you write:

procedure TForm1.mruOpen(Sender: TObject);
begin
OpenFile( (Sender AS TMenuItem).Caption );
end;

Note how mruOpen is declared as a method which accepts one parameter of type TObject. It must be declared that way, because a TMenuItem's onClick event is declared that way.

procedure TForm1.FormCreate(Sender: TObject);
Var
iniFile : TIniFile;
s : String;
sl : TStringList;
i : Integer;
mnuItem : TMenuItem;
begin
iniFile := Nil;
sl := Nil;
mnuItem := Nil;
Try
iniFile := TIniFile.Create(
ExtractFilePath( Application.ExeName ) + 'MruFiles.Ini' );
sl := TStringList.Create;
iniFile.ReadSection( 'MruFiles', sl );
For i := 0 TO sl.Count - 1 Do
Begin
s := IniFile.ReadString('MruFiles', sl.strings[i], 'N/A');
mnuItem := TMenuItem.Create( Self );
mnuItem.Caption := s;
mnuItem.onClick := MruOpen;
File1.Insert( 2 + i, mnuItem );
End;
// Add a separator at the end
mnuItem := TMenuItem.Create( Self );
mnuItem.Caption := '-';
File1.Insert( sl.Count - 1 + 3, mnuItem );

Finally
iniFile.Free;
sl.Free;
End;

Listing 1 - Reading a list of MRU files from an INI file and Creating menu items for them

Parameterizing Code
So far you've seen how Delphi uses procedural types for event handlers, and how you use them yourself to assign event handlers dynamically. Now we'll look at a more general use of procedural types - as parameters to subroutines. Remember that since procedural variables are simply variables which contain pointers to pieces of code; you can pass them as parameters just like other variables. To see why this is useful, consider how you search a stringlist. As you may know, the TStringList class has a method called IndexOf, which searches a stringlist for a particular string and returns its position in the list. IndexOf is useless, however, if you want a case insensitive search, a substring search, or to search a stringlist's objects. Writing your own search routine is pretty easy. Listing 2 shows how to perform a case insensitive search, for example.


Function slInsensitiveSearch( sl : TStringList; s : String) : Integer;

Var
i : Integer;
lFound : Boolean;

Begin
i := 0;
lFound := False;
While (i <= sl.count - 1) and (not lFound) Do
Begin
If UpperCase(sl.Strings[i]) = UpperCase( s ) Then
lFound := True
Else
i := i + 1;
End;
If lFound Then
Result := i
Else
Result := -1
End;

Listing 2 - A case Insensitive StringList search

This is a basic stringlist search. It loops through each element of the stringlist, terminating when either the item is found, or the stringlist is exhausted. The routine uses the expression:

UpperCase(sl.Strings[i]) = UpperCase( s )

to determine whether this is the desired element or not.

Now consider listing 3, which searches a stringlist for a string where the item being searched for can be a left substring of the item in the string list. That is, if you are searching for the string Spence, and an element in the stringlist cointains Spencer, that would be considered a valid match (listing 2, by contrast, was an exact comparison).

Function slLeftSubString( sl : TStringList; s : String) : Integer;

Var
i : Integer;
lFound : Boolean;

Begin
i := 0;
lFound := False;
While (i <= sl.count - 1) and (not lFound) Do
Begin
If Copy(sl.Strings[i], 1, Length(s)) = s Then
lFound := True
Else
i := i + 1;
End;
If lFound Then
Result := i
Else
Result := -1
End;

Listing 3 - A case sensitive substring search

This time the routine uses the expression:

Copy(sl.Strings[i], 1, Length(s)) = s

to determine whether this is the desired element or not, but the rest of the routine is identical.

How would you perform a case insensitive sub string search? You'd duplicate the routine, using the following expression to perform the comparison:

UpperCase(Copy(sl.Strings[i], 1, Length(s))) = UpperCase(s)

How would you search the stringlist for an object whose classname is passed as a parameter? You'd use the following expression

sl.Objects[i].ClassName = s

Do you see the point? The only thing that is changing each time is the comparison. It should be possible to parameterize this expression someway, and leave the rest of the routine alone. A procedural parameter is the answer. We need a generic search routine, where the routine's caller can pass in an expression which defines the comparison to make. The loop of this generic routine would then read something like:

While (i <= sl.count - 1) and (not lFound) Do
Begin
If CallUserSuppliedRoutine Then // ß---NOTE ---
lFound := True
Else
i := i + 1;

The user supplied routine will return a true if this is the element it wants, false otherwise. Obviously the user supplied routine must know which element is currently being examined, and indeed which stringlist is being searched, so the generic routine must pass these as parameters, as in the following pseudo code:

While (i <= sl.count - 1) and (not lFound) Do
Begin
If CallUserSuppliedRoutine(sl, i) Then // ß---NOTE ---
lFound := True
Else
i := i + 1;

Let's look at the syntax we need to make this happen. The user supplied routine, the procedural type, must be a function which returns a logical, and receives two parameters; the stringlist itself, and the position of the element under consideration. We'll first declare a new type for this named TSLSearchFunc:

// This function is passed the stringlist and the index of the current item
// being examined. The function must return a true if this is the
// desired element, false otherwise
Type TSLSearchFunc = Function(sl :TStrings; i : Integer) : Boolean;

The generic routine must accept this as a parameter:

// Generic StringList search with procedural type
// handling the comparison
Function SLSearch( sl : TStrings; Checker : TSLSearchFunc ) : Integer;

And must call it from inside the loop, passing the stringlist and the element number as parameters:

If Checker(sl, i) Then

Listing 4 shows the entire generic search routine.

// This function is passed the stringlist and the index of the current item
// being examined. The function must return a true if this is the
// desired element, false otherwise
Type TSLSearchFunc = Function(sl :TStrings; i : Integer) : Boolean;

// Generic StringList search with procedural type
// handling the comparison
Function SLSearch( sl : TStrings; Checker : TSLSearchFunc ) : Integer;
Var
i : Integer;
lFound : Boolean;
Begin
i := 0;
lFound := False;
While (i <= sl.count - 1) and (not lFound) Do
Begin
If Checker(sl, i) Then // ß---NOTE ---
lFound := True
Else
i := i + 1;
End;
If lFound Then
Result := i
Else
Result := -1;
End;

Listing 4 - Generic Stringlist search routine with procedural parameter handling comparison

So how do you call this routine? First you must write the routine whose address you will pass to SLSearch. Here's a case insensitive search looking for a string stored in a global variable s:

Function InSensitive(sl : TStrings; i : Integer) : Boolean;
Begin
Result := UpperCase(sl.Strings[i]) = UpperCase( s );
End;

Then pass this to slSearch, as in:

i := slSearch( ListBox1.Items, Insensitive )

For each additional type of search you want you only need to write one simple boolean function.

Summary
Procedural types are probably the most underused type in Object Pascal. As this article showed, they are simply variables which contain the address of a subroutine, and as such you can treat them much like any other variable. You can assign them, and pass them as parameters. Delphi uses procedural types for event handlers, and you can use them yourselves to parameterize the logic of a subroutine, as we did here with a generic stringlist search.

Author
Rick Spence is technical director of Web Tech Training & Development (formerly Database Programmers Retreat) - a training and development company with offices in Florida and the UK. You can reach Rick directly at 71760.632@compuserve.com. General inquiries should be directed to training_usa@webtechcorp.com.