RAD Studio (Common)
Procedural Types

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

Procedural types allow you to treat procedures and functions as values that can be assigned to variables or passed to other procedures and functions. For example, 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's left is the name of a procedural type. You can use such type names directly in variable declarations (as in the previous example) or to declare new types:

  TIntegerFunction = function: Integer;
  TProcedure = procedure;
  TStrProc = procedure(const S: string);
  TMathFunc = function(X: Double): Double;
  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 }

On Win32, 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), you need to add the words of object to the procedural type name. For example

  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

  TNotifyEvent = procedure(Sender: TObject) of object;
  TMainForm = class(TForm)
    procedure ButtonClick(Sender: TObject);
  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.)
On Win32, 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;
            Result := Length(S);

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:

  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,

  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);
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!