Routines as Values
Volume Number: 13
Issue Number: 10
Column Tag: Programming Techniques
Routines As Values
by Lawrence D'Oliveiro
The High-Level Programmer's Guide to Generating Code at
Run-Time
This article is about passing around routines (procedures and functions) as values.
That is, you take the address of a routine, pass it to another routine as an argument,
maybe even store it in a data structure for later use. Ultimately the whole point with a
routine is that you make a call to it; but many interesting things can happen on the way
there.
There are many useful programming techniques to do with routines as values. In my
view, these techniques deserve to be more widely known than they are, because they
can contribute so much to the principles of reusable code and information hiding that
good programmers value. This article will give you an introduction to some of these
techniques.
To make sense of this article, it helps to have some understanding of 68K assembly
language and how it interfaces to the Mac OS. The definitive reference to 68K machine
language is the 68000 Family Programmer's Reference, available from Motorola's
Web site as an Acrobat PDF file at
http://www.mot.com/hpesd/aesop/68k/68kprm.pdf. Links to related documents can
be found at http://www.mot.com/SPS/HPESD/docs/pubs/index.html. For a
description of 68K calling conventions on the Mac OS, see the Trap Manager chapter of
Inside Macintosh: Operating System Utilities, available on-line at
ftp://ftpdev.info.apple.com//Developer_Services/Technical_Documentation/Inside_
Macintosh/Operating_System_Utilities/. [Another useful and brief article introducing
the basics of 68K ASM is "The Secrets of the Machine" by Malcolm Teas in MacTech
9.5. It is available from
http://www.mactech.com/Articles/Vol.09/09.05/Read-Assembly/text.html -- ed].
Language Notes
Most of the code examples in this article are written in Modula-2. Specifically, they
were written for the (now-defunct) Metrowerks Modula-2 compiler that runs under
Apple's MPW (Macintosh Programmer's Workshop). I use this combination because
it's been the best language and development environment for the kinds of programs I
write. I recently got hold of the p1 Modula-2 compiler from Germany, but have yet to
start using that in earnest.
At the end of the article, I will discuss the applicability of these techniques to other
languages: everybody's favorite, C, and everybody else's favorite, Pascal.
In the meantime, I'll explain a few things for those who might be unfamiliar with
Modula-2, but are familiar with Pascal. And if you're not familiar with Pascal, then
you're not a real programmer...
Modula-2 has a lot in common with Pascal, and most of the following code should be
comprehensible to Pascal programmers. It has the same signed integer types as Pascal
(INTEGER for 16-bit integers and LONGINT for 32-bit ones), but it also has unsigned
integer types: CARDINAL for 16-bit ones and LONGCARD for 32-bit ones. Pointer
types are defined using the syntax "POINTER TO t" instead of Pascal's "^t". And there is
a general untyped pointer type, ADDRESS, which is compatible with any pointer type.
The Pascal compilers on the Mac don't seem to have any equivalent to this; it is like the
"void *" type in C. The built-in ADR function returns the address of something; it is
equivalent to the "@" operator in Pascal or "&" operator in C. Like the Pascal version
(but unlike the C one), it always returns an untyped pointer value, that is, its return
type is ADDRESS.
If you want to do unsafe type conversions, there are two ways. One is the traditional
record-variant trick from Pascal (or unions in C). The other is the use of the CAST
function. You will see both in use later. Unlike Pascal or C, you can't just use a type
name as a function to do a cast; this makes unsafe conversions a little easier to find in
the code!
The big difference from Pascal, for the purposes of this article, is that Modula-2
allows you to declare routine types. That is, you can write pieces of code like this:
TYPE
MyProcType =
PROCEDURE
(
INTEGER
);
VAR
ProcVar : MyProcType;
K : INTEGER;
PROCEDURE MyProc
(
Arg : INTEGER
);
BEGIN
...
END MyProc;
BEGIN (* main program *)
MyProc(K); (* invokes MyProc *)
ProcVar := MyProc;
ProcVar(K); (* also invokes MyProc *)
...
In Pascal, you can take the address of the routine:
Var
ProcVar : Ptr;
Procedure MyProc
(
Arg : Integer
);
Begin
...
Begin
{ main program }
MyProc(K);
ProcVar := @MyProc;
...
But there is no way to define a type like MyProcType in the Modula-2 version, and the
language provides no standard mechanism for calling the routine once you get its
address in ProcVar. Of course, you can get around this, in a type-unsafe way, with
appropriate use of inline machine code.
C programmers will already be familiar with all this, since C has similar sorts of