Procedural Types (Delphi)

From RAD Studio
Jump to: navigation, search

Go Up to Data Types, Variables, and Constants Index

Procedural types allow you to treat procedures and functions as values that can be assigned to variables or passed to other procedures and functions.

This topic does not refer to the newer type of procedural type used with anonymous methods, that is, a "reference to a procedure". See Anonymous Methods in Delphi.

About Procedural Types

The following example demonstrates usage of a procedural type. Suppose you define a function called Calc that takes two integer parameters and returns an integer:

 function Calc(X,Y: Integer): Integer;

You can assign the Calc function to the variable F:

 var F: function(X,Y: Integer): Integer;
 F := Calc;

If you take any procedure or function heading and remove the identifier after the word procedure or function, what is left is the right part of a procedural type declaration. You can use such type names directly in variable declarations (as in the previous example) or to declare new types:

 type
   TIntegerFunction = function: Integer;
   TProcedure = procedure;
   TStrProc = procedure(const S: string);
   TMathFunc = function(X: Double): Double;
 var
   F: TIntegerFunction; // F is a parameterless function that returns an integer
   Proc: TProcedure;    // Proc is a parameterless procedure 
   SP: TStrProc;        // SP is a procedure that takes a string parameter 
   M: TMathFunc;        // M is a function that takes a Double (real)
                        // parameter and returns a Double
 
   procedure FuncProc(P: TIntegerFunction);  // FuncProc is a procedure
                        // whose only parameter is a parameterless
                        // integer-valued function

Method Pointers

The variables shown in the previous example are all procedure pointers - that is, pointers to the address of a procedure or function. If you want to reference a method of an instance object (see Classes and Objects (Delphi)), you need to add the words of object to the procedural type name. For example:

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

These types represent method pointers. A method pointer is really a pair of pointers; the first stores the address of a method, and the second stores a reference to the object the method belongs to. Given the declarations:

 type
   TNotifyEvent = procedure(Sender: TObject) of object;
   TMainForm = class(TForm)
     procedure ButtonClick(Sender: TObject);
      ...
   end;
 var
   MainForm: TMainForm;
   OnClick: TNotifyEvent

we could make the following assignment:

 OnClick := MainForm.ButtonClick;

Two procedural types are compatible if they have:

  • the same calling convention,
  • the same return value (or no return value), and
  • the same number of parameters, with identically typed parameters in corresponding positions. (Parameter names do not matter.)

Procedure pointer types are always incompatible with method pointer types. The value nil can be assigned to any procedural type.

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values, nor can predefined procedures and functions. If you want to use a predefined routine like Length as a procedural value, write a wrapper for it:

 function FLength(S: string): Integer;
 begin
   Result := Length(S);
 end;

Procedural Types in Statements and Expressions

When a procedural variable is on the left side of an assignment statement, the compiler expects a procedural value on the right. The assignment makes the variable on the left a pointer to the function or procedure indicated on the right. In other contexts, however, using a procedural variable results in a call to the referenced procedure or function. You can even use a procedural variable to pass parameters:

 var
   F: function(X: Integer): Integer;
   I: Integer;
   function SomeFunction(X: Integer): Integer;
     ...
   F := SomeFunction;    // assign SomeFunction to F
   I := F(4);            // call function; assign result to I

In assignment statements, the type of the variable on the left determines the interpretation of procedure or method pointers on the right. For example:

 var
   F, G: function: Integer;
   I: Integer;
   function SomeFunction: Integer;
     ...
   F := SomeFunction;      // assign SomeFunction to F
   G := F;                 // copy F to G
   I := G;                 // call function; assign result to I

The first statement assigns a procedural value to F. The second statement copies that value to another variable. The third statement makes a call to the referenced function and assigns the result to I. Because I is an integer variable, not a procedural one, the last assignment actually calls the function (which returns an integer).

In some situations it is less clear how a procedural variable should be interpreted. Consider the statement:

if F = MyFunction then ...;

In this case, the occurrence of F results in a function call; the compiler calls the function pointed to by F, then calls the function MyFunction, then compares the results. The rule is that whenever a procedural variable occurs within an expression, it represents a call to the referenced procedure or function. In a case where F references a procedure (which doesn't return a value), or where F references a function that requires parameters, the previous statement causes a compilation error. To compare the procedural value of F with MyFunction, use:

 if @F = @MyFunction then ...;

@F converts F into an untyped pointer variable that contains an address, and @MyFunction returns the address of MyFunction.

To get the memory address of a procedural variable (rather than the address stored in it), use @@. For example, @@F returns the address of F.

The @ operator can also be used to assign an untyped pointer value to a procedural variable. For example:

 var StrComp: function(Str1, Str2: PChar): Integer;
    ...
 @StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');

calls the GetProcAddress function and points StrComp to the result.

Any procedural variable can hold the value nil, which means that it points to nothing. But attempting to call a nil-valued procedural variable is an error. To test whether a procedural variable is assigned, use the standard function Assigned:

 if Assigned(OnClick) then OnClick(X);

See Also