Turbo Pascal 4.0 provides no method for passing a function (or procedure) as a
parameter to another procedure. The Turbo Pascal Owner's Handbook p 246 states

  @ with a Procedure or Function
  You can apply @ to a procedure or a function to produce a pointer to its
  entry point. Turbo Pascal does not give you a mechanism for using such a
  pointer. The only use for a procedure pointer is ... in an inline state-
  ment.

An inline statement provides one method for passing a function to another pro-
cedure using a pointer parameter. This idea is so useful and simple it  should
have been listed in the Owner's Handbook. Although different, the  procptr.pas
file on the utilities/examples distribution disk has a somewhat related idea.

To pass a function pass its address as a pointer parameter (seg & ofs, p  351)
to the procedure (possibly  in another unit). The  procedure or the unit  must
declare an inline function with precisely  the same argument list and type  as
that of the passed function. Caveat emptor.

The inline function has an inline directive (pp 83, 360) that specifies a  far
call to the function. The assembly  language mnemonic is CALL FAR  [BP+disp16]
or CALL FAR [direct] both of which are encoded with 4 bytes: the indirect call
instruction byte FF, an addressing mode byte of three fields mod|pboc|r/m  and
an address word. The pboc field is binary 011 for a far call (010 for near^1),
r/m is 110. The compiler assigns the address word as the offset address within
the base segment of the variable identifier used for the pointer parameter  in
the inline element. The base segment of a local variable is the stack segment;
a global variable, the data segment (p  359). So, to address a local  variable
use displacement addressing  relative to the  BP register (mod  10); a  global
variable, direct addressing (mod 00). Thus, with CALL FAR the addressing  mode
byte 9E is followed by a local variable; 1E, a global variable.

CALL FAR [addr] pushes the address of the next instruction onto the stack then
jumps to the address pointed to by addr. A subsequent far return  instruction,
RETF, in TP4's exit code pops the previously pushed address off the stack then
jumps to it. To encode RETF use the $f+ compiler option (p 180); near  return,
RET, $f-. TP4 codes a nested function with RET; needs PUSH BP CALL NEAR [addr]
in inline directive (p 352).

The program below has been successfully tested with the command line  compiler
(TPC) and the integrated development environment (TURBO) compiler. It has been
checked with Turbo Debugger 1.0 (TD) by stepping through the machine code  one
instruction at a time and  taking careful note of the  state of the stack  and
CPU registers at each step. TP4 provides sufficient debug information to  step
through the code (p 530). Save the program as passfunc.pas; unit as  pass.pas.
Make and debug the program with the commands:

tpc /m/$f+,t+,d+ passfunc
tpmap passfunc
tdmap passfunc
td -l passfunc

-----Program passfunc.pas-----
program passfunc;

uses pass;
var w,x,y:real;

function a(p:word;x:real):real;
function d(x:real):real;
Inline($55/$FF/$96/>P); { PUSH BP, CALL NEAR [BP+>P] }
begin
  a:=d(x);
end;

function f(x:real):real;
function e(x:real):real; begin e:=x; end;
begin
  w:=a(ofs(e),2); { pass nested function }
  f:=w*x;
end;

begin
  x:=5;
  b(@f,x,y); { @f is equivalent to Addr(f) }
  Writeln('f(x)=',w:1:0,'x f(',x:1:0,')=',y:2:0);
end.

-----Unit pass.pas-----
unit pass;

interface

procedure b(p:pointer;x:real;var y:real);

implementation

var t:pointer;

function h(x:real):real;
Inline($FF/$1E/>T); { CALL FAR [>T] }

procedure b;
function g(x:real):real;
Inline($FF/$9E/>P); { CALL FAR [BP+>P] }
begin
  t:=p;
  y:=h(x);
  y:=g(x); { g calls f using local variable p; h, global variable t }
end;

end.

-----
1 In a single program with CALL NEAR change $f+ to $f-, Addr() to Ofs(), 1E/9E
to 16/96 and pointer to word.

Copyright (C) 1998 Brent L. Carruth, Ph.D.
carruth@ee.utah.edu
