3.6 Procedural types

Free Pascal has support for procedural types, although it differs a little from the Turbo Pascal or Delphi implementation of them. The type declaration remains the same, as can be seen in the following syntax diagram:

_________________________________________________________________________________________________________
Procedural types

--procedural- type-|-function-header-----------------------------------
                 procedure- header   -of- object--| ;  call- modifiers
                                  is  nested

--function-header- function -formal- parameter- list- :-type- identifier---------

--procedure- header procedure- formal- parameter- list--------------------

--call- modifiers--|----register---------------------------------------
              |-----cdecl------|
              |------c -------|
              |----cppdecl-----|
              -----pascal-----|
              -----stdcall-----|
              -----safecall-----|
              ---OldinFliPnCeCall---|
              -----SysCall-----|
              ---MWPascal ----|
              |---HardFloat----|
              |SysVABIDefault -|
              |-SysVABICDecl --|
              |-MSABIDefault--|
              |-MSABICDecl  --|
              ----VectorCall-----
____________________________________________

For a description of formal parameter lists, see chapter 14, page 758. The two following examples are valid type declarations:

Type TOneArg = Procedure (Var X : integer);
     TNoArg = Function : Real;
var proc : TOneArg;
    func : TNoArg;

One can assign the following values to a procedural type variable:

  1. Nil, for both normal procedure pointers and method pointers.

  2. A variable reference of a procedural type, i. e. another variable of the same type.

  3. A global procedure or function address, with matching function or procedure header and calling convention.

  4. A method address.

Given these declarations, the following assignments are valid:

Procedure printit (Var X : Integer);
begin
  WriteLn (x);
end;
...
Proc := @printit;
Func := @Pi;

From this example, the difference with Turbo Pascal is clear: In Turbo Pascal it isn’t necessary to use the address operator (@) when assigning a procedural type variable, whereas in Free Pascal it is required. In case the -MDelphi or -MTP switches are used, the address operator can be dropped.

Remark The modifiers concerning the calling conventions must be the same as the declaration; i. e. the following code would give an error:

Type TOneArgCcall = Procedure (Var X : integer);cdecl;
var proc : TOneArgCcall;
Procedure printit (Var X : Integer);
begin
  WriteLn (x);
end;
begin
Proc := @printit;
end.

Because the TOneArgCcall type is a procedure that uses the cdecl calling convention.

In case the is nested modified is added, then the procedural variable can be used with nested procedures. This requires that the sources be compiled in macpas or ISO mode, or that the nestedprocvars modeswitch be activated:

{$modeswitch nestedprocvars}
program tmaclocalprocparam3;

type
  tnestedprocvar = procedure is nested;

var
  tempp: tnestedprocvar;

procedure p1( pp: tnestedprocvar);
begin
  tempp:=pp;
  tempp
end;

procedure p2( pp: tnestedprocvar);
var
  localpp: tnestedprocvar;
begin
  localpp:=pp;
  p1( localpp)
end;

procedure n;
begin
  writeln( 'calling through n')
end;

procedure q;

var qi: longint;

  procedure r;
  begin
    if qi = 1 then
      writeln( 'success for r')
    else
      begin
      writeln( 'fail');
      halt( 1)
    end
  end;

begin
  qi:= 1;
  p1( @r);
  p2( @r);
  p1( @n);
  p2( @n);
                                                                            

                                                                            
end;

begin
  q;
end.

In case one wishes to assign methods of a class to a variable of procedural type, the procedural type must be declared with the of object modifier.

The two following examples are valid type declarations for method procedural variables (also known as event handlers because of their use in GUI design):

Type TOneArg = Procedure (Var X : integer) of object;
     TNoArg = Function : Real of object;
var
  oproc : TOneArg;
  ofunc : TNoArg;

A method of the correct signature can be assigned to these functions. When called, Self will be pointing to the instance of the object that was used to assign the method procedure.

The following object methods can be assigned to oproc and ofunc:

Type
  TMyObject = Class(TObject)
    Procedure DoX (Var X : integer);
    Function DoY: Real;
  end;

Var
  M : TMyObject;

begin
  oproc:=@M.DoX;
  ofunc:=@M.DOY;
end;

When calling oproc and ofunc, Self will equal M.

This mechanism is sometimes called Delegation.

Remark When comparing two variables of type method, only the method’s address is compared, not the instance pointer. That means that the following program will print True:

Type
  TSomeMethod = Procedure  of object;

  TMyObject = Class(TObject)
    Procedure DoSomething;
  end;

Procedure TMyObject.DoSomething;

begin
  Writeln('In DoSomething');
end;

var
  X,Y : TMyObject;
  P1,P2 : TSomeMethod;

begin
  X:=TMyObject.Create;
  Y:=TMyObject.Create;
  P1:=@X.DoSomething;
  P2:=@Y.DoSomething;
  Writeln('Same method : ',P1=P2);
end.

If both pointers must be compared, a typecast to TMethod must be done, and both pointers should be compared. TMethod is defined in the system unit as follows:

TMethod = record
  Code : CodePointer;
  Data : Pointer;
end;

The following program will therefore print False:

Type
  TSomeMethod = Procedure  of object;

  TMyObject = Class(TObject)
    Procedure DoSomething;
  end;

Procedure TMyObject.DoSomething;

begin
  Writeln('In DoSomething');
end;

var
  X,Y : TMyObject;
  P1,P2 : TMethod;

begin
  X:=TMyObject.Create;
  Y:=TMyObject.Create;
  P1:=TMethod(@X.DoSomething);
  P2:=TMethod(@Y.DoSomething);
  Writeln('Same method : ',(P1.Data=P2.Data) and (P1.Code=P1.Code));
end.