4D Externals
Volume Number: 4
Issue Number: 11
Column Tag: Database Corner
4th Dimension® Externals 
By Todd Carper, ACIUS, Inc., Cupertino, CA
[Todd Carper is a Technical Support Engineer for 4th Dimension® and handles all
external procedure development. He has been there for one year.]
External Procedures in 4th Dimension®
4th Dimension is a powerful and sophisticated database for the Macintosh. It
provides an excellent environment for developing custom databases and applications.
4th Dimension allows you to quickly generate screens, menus, buttons, scrollable
areas, and procedures to create a true Macintosh interface without starting from
scratch.
4th Dimension’s language enables you to avoid pages of code and hours of work by
accessing the Macintosh ToolBox with high level commands. Though the language is
very powerful it may not contain all the built-in commands to meet every need. For
this reason 4th Dimension was designed as an open architecture database that can
utilize your own commands. These commands are called “external procedures”, and
this document will discuss the design and implementation of external procedures.
External procedures enable you to add unlimited functionality to a 4th Dimension
database or application. For example, you can add sound, pop-up menus, picture
buttons, mathematics, or telecommunications capabilities. External procedures are
extensions to the 4th Dimension commands that you create like XCMDs for HyperCard.
Once the procedure is installed, it is part of 4th Dimension and can be called just as
you call built-in commands.
External procedures can be written in assembly language, or any higher level
language that compiles to native 68000 code. It is very important that the code be
stand-alone, in that it does not require additional modules to function properly. This
article will use Pascal programs created with the Apple Macintosh Programmers
Workshop (MPW) Pascal.
Calling the Externals
When 4th Dimension ‘calls’ the external procedure, it simply performs a direct
link, Link A6, to the routine and executes the supplied code until the code ends, and by
nature executes a return statement to the calling routine.
Because the supplied code is being called from within another application, you
should not try to access any global variables, as those are stored off the A5 register and
it’s contents may not be correct for your call. Therefore always declare your
variables local. If you need the variables to be used as if they were global then define
them within a procedure, and have all called procedures and functions defined within
the procedure defining the ‘global’ variables.
The idea is to make all references relative. This is important because when the
code is moved into 4th Dimension, the Code 0 segment is not used. (This is where the
Jump table is located). Therefore if you have procedures you want to call, they should
be declared as FORWARD and your top most procedure should be the one that ‘runs’
your program.
Following is an example external procedure. This procedure will receive a
number, multiply the number by 2, and return the result. Because the variable is
declared as a var in the procedure parameter list, the result can be returned to 4th
Dimension.
Program Ext_Doubler;
Var DumInt:Integer;
procedure Doubler(var TheInt:Integer);
begin
TheInt:=TheInt*2;
end; {Doubler}
Begin
Doubler(DumInt); {Used for compile only}
End. {Main Block}
Following is an example utilizing multiple procedures which doesn’t take any
parameters. It simply assigns the string ‘Hello there’ to the global variable Global2.
It then beeps once for each character in the string. The variables Global1 and Global2,
appear to be global type variables because the procedures which use them are within
the scope of the procedure which defines both the variables and the procedure using the
variables.
The main block calls the procedure Caller, (this call i4th Dimensionns only.)
Caller then calls the procedure to be executed, HaveGlobals. HaveGlobals was declared
as FORWARD so that Caller would know that it exists. HaveGlobals defines the
variables to be used by the procedures defined in it’s scope. HaveGlobals assigns the
string to Global2. It then calls First to assign the length of Global1 to Global2. Then
Second is called to have the system beep Global1 times.
Program TestProg;
Uses Memtypes, QuickDraw, OSIntf;
procedure HaveGlobals; FORWARD;
{so Caller knows about the procedure}
procedure Caller;
begin
HaveGlobals;
end;
procedure HaveGlobals;
var
Global1:Integer;
Global2:str255;
procedure First;
begin
Global1:=Length(Global2);
end; {First}
procedure Second;
var Count:Integer;
begin
For Count:=1 to Global1 do Sysbeep(2);
end; {Second}
begin {HaveGlobals}
Global2:=’Hello there’;
First;
Second;
end; {HaveGlobals}
Begin {Main Block}
Caller;
End. {program}
Creation of External procedures
Be sure that if your external compiles to more than 1 code segment, or if one of
the linked modules is placed in it’s own segment, that you join the segments into 1
complete segment. This is important if your application uses the SANE library.
You can compile to an application and then use the 4D External Mover™ to
transfer the code segment and to specify the parameters. You can also compile directly
to a Proc.Ext type file and specify the parameter list. Then you can use the 4D
External Mover to copy the already set-up external to the proper Proc.Ext file. There
will also be an item in the list, (if viewed in the 4D External Mover), with the name
%A5Init. This item may be deleted from the list as it is not needed by the external.
Here is an example of a MPW linker spec to join the SANE code segment with the
Main code segment and the method of installing directly into a Proc.Ext type file.
link -w -p MyProg.p.o ∂
“{Libraries}”Interface.o ∂
“{Libraries}”Runtime.o ∂
“{PLibraries}”Paslib.o ∂
“{PLibraries}”SANELib.o ∂
-rt ‘4DEX=15000’∂ #Specify resource ID.
# Joins segment Main and the Sane libraries into
# one. *
-sg “Main2=Main,SANELib “∂
# Change segment name. **
-sn ‘Main2’=’MyProg(&S;&R)’∂
# Specify the creator and type.
-c “4DMX” -t “PEXT”∂
-o MyProg
* Note: for this external procedure, the Sane libraries are needed, therefore, they
must exist in the same segment as the main code. If your code does not require
the Sane libraries then this command is not necessary. Be sure to note the case
used in “SANELib ” and the space which is between ‘b’ and the quote mark.
** The segment name was changed from Main2 to MyProg, the additional
parameters, in parens, specify the types of the parameters being passed from
4th Dimension. In this example, (&S;&R), specifies that a String and a Real are
to be passed. String first, Real second. Each parameter is preceded by an
ampersand, ‘&’. A semicolon is used to separate each parameter.
The parameter types are as follows:
I Integer
L LongInt
R Real
S String
T Text
D Date
P Picture
Following are the Pascal type definitions for Date and Text fields.
Date type
Date4D = record
day:integer;
month:integer;
year:integer;
end;
Text type
TERec4D = record
length:integer;
text:CharsHandle;
end;
With the release of the 128k ROMs, the restriction of code segments being less
than 32k has gone away. Because of this, your external procedure may be very large.
There is one important note however; you cannot perform a branch to an address which
is farther than 32k from where you are branching from. The linker will catch this
and you will have to put in a ‘stepping stone’ procedure. All these procedures do is call
the procedure you wanted to call originally. They are located between the calling
procedure and the procedure to be called and are used as stepping stones to get to the
proper procedure.
In this example, we want to call Proc4 from Proc1. Proc1 and Proc3 are very
large; such that the distance from the call to Proc3 and the location of Proc3 is greater
than 32k when compiled. To accomplish the call we need to insert a ‘stepping stone’
procedure after Proc1 called Step. Proc1 calls the procedure Step. Step then calls
Proc3. In this way, the distance between calls is less than 32k.
Program Ext_StepStone;
Uses Memtypes;
Var
procedure Proc2; Forward;
procedure Proc3; Forward;
procedure Step; Forward;
procedure Proc1;
var
MyVar:str255;
begin
MyVar:=’Hello’;
{Now we call Step to get to Proc3}
Step;
á{Lot’s of Code}
end; {Proc1}
procedure Step;
begin
{This routine just calls Proc3}
Proc3;
end; {Step}
procedure Proc2;
begin
á{Lot’s of code}
end; {Proc2}
procedure Proc3;
begin
{Here’s the code we want to use}
á{Some more code}
end; {Proc3}
Begin {Main Block}
Proc1;
End. {Main Block}
Handles and External procedures
Whenever storing a data structure, be sure that you are accessing it via a Handle
and that you lock the Handle if you want to ensure that it is there when you return.
Be sure to Dispose of all Handles that you have created when you have finished
with them. If you leave unused handles in memory, you could run out of memory at a
later point in the program.
Using Pictures in Externals
When passing Picture variables, to and from external procedures, be sure to
include room for the additional 6 bytes needed by 4th Dimension to store the
(Horizontal, Vertical) origin of the picture used when it is displayed in an
On-Background field, (4 bytes), and the mode in which the picture is to be displayed,
(2 bytes). For most pictures this means using SetHandleSize to increase the allocated
space:
SetHandleSize(Handle(MyPic),
GetHandleSize(Handle(MyPic))+6)
* Remember, Pictures can now be greater than 32k in size. Therefore the PicSize
element of the PicHandle record, may not be valid. You should always call
GetHandleSize to learn the size of the Picture.
When using externals it is sometimes necessary to save a complex data structure
or just a handle of a type unknown to 4th Dimension. To store types of handle other
than PicHandle, you can simply use Type Coercion to change the handle to type
PicHandle and then pass the handle back to 4th Dimension in the form of a picture. Do
not attempt to display the ‘picture’ as strange things will definitely happen.
Here is an example of storing SND’s in a 4th Dimension picture field. This
example reads an SND resource from a file and returns to 4th Dimension a picture
variable containing the SND. You can then store the picture (SND) and replay it at a
later time using PlayPict, (the routine PlayPict follows this one). SoundToPict takes
3 parameters, SoundToPict(Parm1;Parm2;Parm3). Parm1 is the name of the file on
disk containing the SND. Parm2 is the name of the SND to be retrieved. Parm3
returns a picture variable containing the SND. For information about the Resource
Manager, see Inside Macintosh.
Program Ext_SoundToPict;
Uses MemTypes, QuickDraw, OSIntf,
ToolIntf, PackIntf, Sound;
Var
DFile, DSound:str255;
DPict:PicHandle;
procedure SoundToPict(
var TheFile, SoundName:str255;
var MyPict:PicHandle);
FFSynthHandle = ^FFSynthPtr;
var
MySound:FFSynthHandle;
TheRes:integer;
MyPtr:Ptr;
MyFFPtr:FFSynthPtr;
begin
MyPict:=Nil;