XCMD CookBook
Volume Number: 4
Issue Number: 6
Column Tag:
XCMD CookBook
By Donald Koscheka, Apple Computer, Inc.
[Donald Koscheka is a Software Engineer employed by Apple Computer Inc. He has
written some very important development XCMDs which you will hear about soon.
-HyperEd]
Introduction to XCMD’s
If you’ve used Hypercard and its programming language HyperTalk for any length
of time, you’ve no doubt discovered some of the things that you can’t do with
Hypercard. Perhaps you’re writing a forms generator in HyperTalk and you want to be
able to print customized reports. The designers of Hypercard knew that their product
would have to be a lot of things to a lot of people and it would be nearly impossible to
provide every possible feature in the language. They did, however, provide us with the
capability of customizing HyperCard and HyperTalk directly. To paraphrase Abraham
Lincoln, “You can fool some of the people all of the time, or all of the people some of
the time, but to fool all of the people all of the time, write an XCMD”.
As a programming language, HyperTalk is highly extensible; you can add your
own commands and functions to the language very easily. These extensions are known
as eXternal CoMmanDs ( XCMDs) and eXternal FunCtioNs (XFCNs). The capital letters,
ie. case, are significant as I’ll discuss later. XCMDs and XFCNs are identical at the
coding level, so I’ll refer to both as XCMDs. The determination of whether a command
will be an XCMD or an XFCN is made at link-time.
Creating an XCMD is a straightforward process. First, you write the XCMD using
the language of your choice (I’ll show examples in both “C” and Pascal). Next, you
compile and link the XCMD as a resource and then you add it to the resource fork of the
stack that you want to call it from, the home stack or even Hypercard itself.
Where you put the XCMD is significant. When Hypercard encounters a command
that it does not recognize, it first checks the current object (button or field) to see if
it contains any handlers for that command. If no handler is found, the search continues
in the following order: card, background, stack, home stack, HyperCard. If after
looking in all the places that it could expect to find a handler no handler turns up,
Hypercard then searches its own resource fork for any external commands that can
handle the command. It identifies the appropriate command by name. Note what this
hiearchy implies. If you want your XCMD to be globally visible to all stacks, put the
XCMD in HyperCard’s resource fork or Home Card. There is a tradeoff, though. If you
want the stack that uses the XCMD to be “portable”, you’ll need to put the XCMD in that
stack. There is no conflict of interest here, you can put the XCMD in both places. [Use
Rescopy , via scripts, to allow the user the option of installing it into the Home Stack.
-HyperEd] Hypercard stops searching as soon as it finds a copy of the command. If it
finds a copy in your stack, it’ll stop the search there. If you call the XCMD from
another stack that does not contain the XCMD, then it’ll go all the way back to the Home
Stack or Hypercard to search for the command.
Now that you have an idea of where to place the XCMD, let’s take a look at how
they are created. All XCMDs and XFCNs interface to Hypercard in a standard and
straightforward way. When an XCMD is invoked, Hypercard will pass it a parameter
block that contains, among other things, the number of parameters and handles to the
parameters. For argument’s sake, let’s see how the following XCMD would be called:
Pizza “Cheese”, “Pepperoni”, “Anchovies”
When this XCMD is called, Hypercard will pass a record, referred to as the
Parameter Block, to the XCMD. The first argument in the parameter block will be the
number of parameters passed; in this case three, one for each item in the argument
list. Each parameter is passed as a handle to a zero-terminated text string. Parameter
1 will be a handle to the string “Cheese\0”, where the the ‘\0’ means the character
whose ASCII value is 0. “C” programmers will recognize this format as a standard
string definition in “C”. Pascal programmers often refer to this format as a C-String.
The actual structure of a parameter block (in Pascal) is:
TYPE
XCmdPtr = ^XCmdBlock;
XCmdBlock =
RECORD
paramCount : INTEGER;
params : ARRAY[1..16] OF Handle;
returnValue : Handle;
passFlag : BOOLEAN;

entryPoint : ProcPtr; { to call back to HyperCard }
request : INTEGER;
result : INTEGER;
inArgs : ARRAY[1..8] OF LongInt;
outArgs : ARRAY[1..4] OF LongInt;
END;
Let’s examine this record in more detail. First, we define a pointer to the
record, called an XCmdPtr. Hypercard passes a pointer to the record so you’ll need to
be comfortable with pointers to work with XCMDs.
The first field in the record, paramCount , is a count of the number of
parameters that have been passed to the XCMD. This is a number between 1 and 16,
which is the maximum size of the parameter array, params, in field 2. Params is an
array of handles which implies that the fundamental element in the array is a signed
byte.
Field 3 in the record, returnValue, is a handle to the data that you want to return
to Hypercard on completion of the XCMD. You create the handle and pass it back to
hypercard with the assignment:
paramPtr^.returnValue := Handle_You_Created;
Herein lies the most important difference between XCMDs and XFCNs.
ReturnValue os places in the global container, result, when returning from an XCMD
and by value when returning from an XFCN. The following illustrates the mechanics:
XCMD: Pizza “cheese”, “pepperoni”, “anchovies”
Put the result
XFCN: Put Pizza (“cheese”, “pepperoni”, “anchovies”)
XCMDs, like any other command in hypercard, take their parameters on the same
line as the command itself. XFCNs, like any other function in Hypercard, take their
parameters in parentheses and return a value. This is the most important difference
between XCMDs and XFCNs. You make the decision at link-time as to whether a code
resource will be an XCMD or an XFCN. Please note, the code is identical for both
XCMDs and XFCNs. Hypercard re-vectors ReturnValue for you depending on whether