Ext Code Modules
Volume Number: 9
Issue Number: 9
Column Tag: Pascal Workshop
External Code Modules 
in Pascal
Put code and resources into external modules for expandable
applications
By Rob Spencer, East Lyme, Connecticut
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
About the author
Rob Spencer is a biochemist at Pfizer Central Re search, and when he’s not
helping to develop new pharmaceuticals he’s an avid Mac enthusiast.
Code resources are everywhere. They’re in our operating system as drop-in
Extensions. Our applications use them as familiar WDEFs, CDEFs, LDEFs, and MDEFs.
We can write our own for extendible applications and development tools like
HyperCard, Excel, 4th Dimension, ProGraph, Serius Developer, and LabView. We even
see them in screen savers with zillions of drop-in modules. So how do these external
chunks of code work, and how can you write your own application to make use of them?
Here’s a set of seven successively more complex Pascal experiments that result in a
simple structure to support powerful and flexible external code modules.
EXPERIMENT 1: ONE-WAY JUMPS
First comes the most important trick: how to call procedures by address, with
parameter passing. I’ve seen this code snippet in many places, including the original
HyperCard glue routines (as in Danny Goodman’s HyperCard Developer’s Guide,
Bantam Books, 1988, appendix C), MacTutor (Jean de Combret, The Best of MacTutor,
vol 5, pp. 246-260), and Symantec magazine (Spring and Autumn 1991 in the THINK
Pascal section). All that’s needed is a small inline procedure with a parameter list
that exactly matches (in number and type) the procedure we want to remote-call, plus
a ProcPtr. The code for Experiment 1 demonstrates; it’s very short:
{1}
program ExperimentOne;
{ ---------------- }
procedure CallCodeRes (paramA, paramB: integer; var paramC:
integer; addr: ProcPtr);
{ Note that the param list must exactly }
{ match that of the called procedure in }
{ order and type, with the address added }
{ at the end. }
inline
$205F, { MOVE.L (A7)+,A0 }
$4E90; { JSR (A0) }
{ ---------------- }
procedure xmMain (x, y: integer; var z: integer);
{ Accept input parameters and do something. }
begin
z := x + y;
end;
{ ============ main ============= }
var
a, b, c: integer;
begin
a := 2;
b := 3;
c := 0;
CallCodeRes(a, b, c, @xmMain);
{ Use THINK’s lazy I/O for this demo. }
ShowText;
writeLn(‘ a,b,c = ‘, a, b, c);
end.
When this program runs, the correspondence of parameter lists in the
CallCodeRes and xmMain procedures makes the compiler set up the stack correctly
before it goes to the inline code and makes the jump. We can pass parameters by value
or address (var), and in the latter case get information back from the code module
(i.e., 2 + 3 = 5). This is all in one little program here, but the descendants of
xmMain (the prefix “xm” stands for “external module”) will reside in independently
compiled code modules, while y will remain in the host application.
EXPERIMENT 2: ADD A PARAMETER BLOCK
This structure is very useful for debugging code resources inside the THINK
environment (such as CDEFs, MDEFs, and WDEFs; see Jean de Combret’s article), but
for our purposes it’s not enough. In Experiment 2 (see the Listings section) we pass
only one parameter, but it’s a pointer to a parameter block:
{2}
xmPtr = ^xmBlock;
xmBlock = record
request: integer;
result: integer;
params: array[1..8] of longint;
callbackAddr: ProcPtr;
end;
This block is modeled after HyperCard’s XCMD parameter block. The param
longints are used to pass information between the code module and the application, in
either direction. These replace the explicit list of parameters in the procedures of
Experiment 1. With type coercion these longints can be points, pointers, handles, etc.
If you want to pass a string, cast one of these as a StringHandle or StringPtr. The other
fields in the parameter block (request, result, callbackAddr) are there to support
two-way communication to the module - which is what Experiments 3 and 4 are all
about.
EXPERIMENT 3: ADD CALLBACKS
Experiments 1 and 2 satisfy the first reason why you might want to use external
code modules: to have a way to send data out to a drop-in module and get answers back.
However, external modules can be more useful if they have the ability to manage their
own windows, menus, events, and script language commands, as well as specialized
data. The difficulty is that there are many things in the application that a separately
compiled code module can’t get access to, such as events and globals like window
pointers and menu handles. We aren’t going to re-write and re-compile our
application every time we write a new module (that’s the whole point), so we need a
way for the application to know which modules are “out there” and what messages they
know how to receive. All this means that really useful modules need two-way
communication to the host application - not just by passing parameters, but by calling
each other’s procedures. This is what callbacks allow.
Implementing callbacks takes another copy of the little inline routine to jump
back to the application. All we have to do is include the return address (the app’s
procedure to which we return) in the parameter block that we pass to the external
code. Naturally, that return procedure had better be in a locked segment or we could
jump back to the Twilight Zone.
The return address can point to only one procedure in the application, but we’ll
want to have many different callback options. The solution is to make that procedure
(CallbackDispatcher) a big case statement with the request field of the parameter
block as the case selector:
{3}
procedure CallbackDispatcher (theXmPtr: xmPtr);
{ Don’t let this move or be purged! }
begin
case theXmPtr^.request of
xreqNewXMWindow:
begin { do this... }
end;
xreqCloseXMWindow:
begin { do that... }
end;
xreqSysBeep:
begin { do this... }
end;
otherwise
{ unknown callback request }
end;
end;
The values of theXmPtr^.request are constants agreed upon by the application and
all external modules. In Experiment 3 these constants are:
{4}
xreqNewXMWindow = 5001;
xreqCloseXMWindow = 5002;
xreqSysBeep = 5003;
The prefix ‘xreq’ stands for ‘external request’; there will be ‘areq’, or
‘application requests’, coming later.
GLUE ROUTINES
Making the use of callbacks convenient for the external module programmer is
important. Remember that one reason for using external code is that you (or someone
else) may want to add functionality long after the host application is finished and
distributed. You shouldn’t have to rediscover how you typecast all the param fields;
you just want a simple declaration that you can understand immediately, as if it came
out of Inside Macintosh. Every callback has a short glue routine that takes care all
this, like this one in Experiment 4:
{5}
function NewXMWindow (theXmPtr: xmPtr; wBounds: rect;
wTitle: str255; wType: integer): WindowPtr;
begin
with theXmPtr^ do
begin
request := xreqNewXMWindow;
param[1] := longint(wBounds.topLeft);
param[2] := longint(wBounds.botRight);
param[3] := longint(@wTitle);
param[4] := longint(wType);
DoCallback(theXmPtr, callbackAddr);
NewXMWindow := WindowPtr(param[5]);
if param[5] = 0 then
result := xmFail
else
result := xmSuccess;
end;
end;
Within your module code you use NewXMWindow just as you would NewWindow.
The glue routine sets up the parameter block, does the callback, and casts the results
from the application to the appropriate type. All that matters is that the glue routine
and CallbackDispatcher agree exactly on the order and typecasting of the parameters.
Note also how xmPtr^.result is used as an error flag, which most often will be