XCMD in App
Volume Number: 9
Issue Number: 7
Column Tag: C Workshop
XCMD’s in Standalone Applications 
Here’s a way to write XCMD’s so they’re usable for more than
By Gerry H. Kenner, Magna, Utah
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
About the author
Gerry Kenner is a professional electrical and computer engineering consultant,
university re searcher and sometimes writer who specializes in image analysis
systems for investigative scientists.
INTRODUCTION
This paper shows how to access HyperCard XCMDs which have been incorporated
into the resource file of a standalone application. This is done by creating a function
named LoadXCMD which takes the name and parameters of an XCMD and provides glue
code for accessing them. In addition, code is provided for responding to callbacks by
the HyperCard utility functions.
HyperCard stacks are unequaled as dynamic front ends for interfacing with
scientific instruments. Hypercard facilitates entering data and displaying results by
using buttons and text fields. Elaborate front ends can be thrown together in a matter
of hours and the resulting scripts can be altered within minutes when rapid changes in
the interface are required.
One major problem with HyperCard stacks is the slow execution speed of the
HyperTalk scripts. Fortunately, they can be speeded up by liberal use of XCMD’s and
XFCN’s for time intensive operations.
A price is paid for these advantages. Most obvious is that HyperCard stacks can
become very large. Another disadvantage is that they are susceptible to damage. One
quickly learns to keep at least two back-up copies of working stacks. A more subtle
problem is that of bullet-proofing large Hypercard programs so that the average
technical person can run them.
Once the stacks become finalized, one alternative is to replace them with
standalone applications developed using THINK C with objects. The THINK Class
Libraries can be used to provide the replacement interface. The XCMD’a and XFCN’s
could be modified into modules which could be called by the application or else
incorporated directly using the system described in this paper. An application
prototyper such as AppMaker or Marksman would be invaluable for this.
I obtained some insights on how to incorporate XCMD’s into standalone
applications from a note in the October 1989 MacTutor by Peter B. Nagel of Denver,
CO. With this information as a basis I proceeded to write the demo code published here.
Disclaimer
As in my previous articles I am only including the code necessary to understand
the project. This code is enough for an intermediate programmer to fill in what is
missing. Typically, I do not declare variables or state which header files must be
included. A copy of the complete project is available on the MacTutor Disk.
STANDALONE APPLICATION
The standalone program passes the number 5 and the message “Return to
application” to a modified version of Apple’s Flash XCMD demo which inverts the
screen 5 times (flashes) and returns a message which is then displayed in a window.
The program requires two files, pTest and pXCMD. PTest is an entry file
containing the main function which needs code for initializing the toolbox, creating a
window, calling the XCMD and outputting the return value to the window. The XCMD
function call is as follows.
/* 1 */
TempHdl = LoadXCMD(3, “pXFCN”, “5”, “Hello World!”);
Three is the number of parameters being passed, pXFCN is the name of the XCMD
code resource, 5 is the number of beeps requested while “Hello World” is the string
which will be returned by the XCMD. TempHdl points to the return string.
Although the memory allocated to TempHdl was assigned elsewhere, it must be
deallocated with a call to DisposHandle.
The pXFCN.h file references HyperXcmd.h and declares prototypes of four
functions. The function declarations are as follows.
/* 2 */
Handle LoadXCMD(short Count, ...);
void JumpToXCMD(XCmdPtr ParamPtr, Handle CodeAddr);
void SwitchXCMD(void);
char *StrCpy(char *s1, char *s2);
It also contains an enum list of Apple’s HyperCard request codes. Complete lists
of these can be found in the pre 1988 HyperCard header files. To facilitate
identification, I am including a partial list here.
xreqSendCardMessage = 1,
xreqEvalExpr,
xreqSendHCMessage = 5;
xreqSendHCMessage = 8;
...
xreqScanToReturn,
xreqScanToZero = 39 // was suppose to be 29! Oops!
The function LoadXCMD takes the parameters passed, converts them into XCMD
readable form and then calls the code resource. The code is as follows.
/* 3 */
1. Handle LoadXCMD(short Count, ...)
2. {
3. void *ListPtr;
4. char *CharPtr;
5. Handle CodeAddr, TempHdl;
6. short i, Err;
7. size_t Size;
8. Count = Count - 1;
9. ListPtr = &Count + 1;
10. CharPtr = *(*(char***)&ListPtr)++;
11. CtoPstr(CharPtr);
12. CodeAddr = GetNamedResource(‘XFCN’, CharPtr);
13. MoveHHi(CodeAddr);
14. HLock(CodeAddr);
15. ParamPtr = (XCmdPtr)NewPtr(128L);
16. ParamPtr->entryPoint = (Ptr)SwitchXCMD;
17. ParamPtr->paramCount = Count;
18. for (i = 0; i < ParamPtr->paramCount; ++i)
19. {
20. ParamPtr->params[i] = NewHandle(256);
21. HLock(ParamPtr->params[i]);
22. CharPtr = *(*(char***)&ListPtr)++;
23. strcpy((char*)*(ParamPtr->params[i]), CharPtr);
24. }
25. JumpToXCMD(ParamPtr, CodeAddr);
26. Size = strlen(*(ParamPtr->returnValue));
27. TempHdl = NewHandle((long)Size);
28. strcpy((char*)*TempHdl,
(char*)*(ParamPtr->returnValue));
29. HLock(TempHdl);
30. for (i = 0; i < ParamPtr->paramCount; ++i)
31. {
32. HUnlock(ParamPtr->params[i]);
33. DisposHandle(ParamPtr->params[i]);
34. }
35. HUnlock(CodeAddr);
36. HUnlock(ParamPtr->returnValue);
37. DisposHandle(ParamPtr->returnValue);
38. DisposPtr((XCmdPtr)ParamPtr);
39. ReleaseResource(CodeAddr);
40. return(TempHdl);
41.}
Instruction 8 retrieves the number of parameters passed. Instructions 9
through 12 get the name of the XCMD and load the resource code. The address of the
XCMD is obtained by using GetResource to load the code resource into memory and get a
handle to its location. Instruction 15 allocates memory for the XcmdBlock pointed to
by ParamPtr. ParamPtr was declared as a global since it is used both here and by
SwitchXCMD. Instruction 16 sets up the function SwitchXCMD as the entry point for
HyperCard XCMD utility calls. Instructions 17 through 24 finish setting up the
XcmdBlock for the XCMD call. Note that space was not allocated for returnValue even
though the code disposes of a handle to this value before terminating. Variables were
assigned to params[0] and params[1].
Instruction 25 calls the function JumpToXCMD which is an assembly lanquage
glue routine for placing the address of the XCmdBlock on the stack and then jumping to
the address of the XCMD (actually XFCN in this case). Instructions 26 through 29
prepare the contents of returnValue for passing back to the calling function. The final
portion of the function disposes of the various handles and pointers created in the
program. CodeAddr was disposed of with ReleaseResource.
After returning from JumpToXCMD, the function StrCpy was used to copy
returnValue into a temporary string. StrCpy was used rather than the ANSI library
routine strcpy to avoid the possibility of LoadSeg being called with attendent movement
of memory. This is necessary because the compiler will not permit the locking of the
handle returnValue, apparently because the handle was not created within the function.
Here is the code for JumpToXCMD.
/* 4 */
1. void JumpToXCMD(XCmdPtr ParamPtr, Handle CodeAddr)
2. {
3. asm
4. {
5. move.l ParamPtr(a6), -(a7)
6. move.l CodeAddr(a6), a0
7. move.l (a0), a0
8. jsr (a0)
9. }
10.}
The address of the XCmdBlock is moved on to the stack in line 5 while the handle
pointing to the code resource is moved into register A0, dereferenced twice and then
jumped to in lines 6 to 8.
Remember that the address of the function SwitchXCmd was placed in the
entryPoint field of the XCmdBlock. This is the code which is jumped to when
HyperCard utility functions are called. It consists of a switch statement which
identifies the code for each utility function. I am only going to show the code for
PasToZero and SendHCMessage but the principal applies to accessing all the functions.
The code itself is self-explanatory and doesn’t require a detailed explanation.
/* 5 */
void SwitchXCMD(void)
{
WindowPtr TempWindow, OldPort;
Handle TempHandle;
long TempLong;
Str255 TempStr;
Rect TempRect;
switch (ParamPtr->request)
{
case xreqZeroToPas:
strcpy((char*)ParamPtr->inArgs[1],
(char*)ParamPtr->inArgs[0]);
CtoPstr((char*)ParamPtr->inArgs[1]);
break;
case xreqSendHCMessage:
GetPort(&OldPort);
SetRect(&TempRect, 20, screenBits.bounds.bottom - 80,
480, screenBits.bounds.bottom - 20);
TempWindow = NewWindow(0L, &TempRect, “\pHC Message”,
TRUE, plainDBox, (WindowPtr)-1L, TRUE, 0L);
SetPort(TempWindow);
MoveTo(10, 30);
DrawString((char*)ParamPtr->inArgs[0]);
Delay(90L, &TempLong);
DisposeWindow(TempWindow);
SetPort(OldPort);
break;
default:
break;
}
}
Writing to code for StrCpy is left as an exercise for the reader. My version is
written in assembly lanquage.
THE XCMD (XFCN)
For completeness I have included a partial listing of CFlash, the modified XCMD
which is called by pXFCN. This example uses the HyperCard utilites SendHCMessage
and ZeroToPas. In addition it uses several Toolbox function calls and the C library call
strcpy.
There is a problem with some of the C library calls. Functions which do not use
globals referenced from A5 or A4 appear to work without problems. Thus, strcpy and
strcat can be used. Routines such as atoi and atol which use globals will not run
properly in the program as written and attempts to use them often result in crashs.
/* 6 */
RememberA4();
SetUpA4();
HLock(paramPtr);
paramPtr->returnValue = NewHandle(256L);
StrPtr = (StringPtr)NewPtr(256L);
ZeroToPas(paramPtr, (char*)*(paramPtr->params[0]), StrPtr);
StringToNum(StrPtr, &TempLong);
flashCount = (int)TempLong;
GetPort(&port);
for (again = 1; again <= flashCount; again++)
{
InvertRect(&port->portRect);
InvertRect(&port->portRect);
}
SendHCMessage(paramPtr,
(StringPtr) ”\pput \”This is a message\” into msg”);
Delay(60L, &TempLong);
strcpy((char*)*(paramPtr->returnValue),
(char*)*(paramPtr->params[1]));
DisposPtr(StrPtr);
RestoreA4();
HUnlock(paramPtr);
ADDING XCMD CODE RESOURCES TO THE .RSRC FILE
This can be done directly in THINK C 5.0 by using the merge option when building
the code resource. For other versions of C use ResEdit to add the files.
DISCUSSION
Initially I was disturbed to discover I could not use some of the C library routines
in XCMD’s which I eventually planned to use in standalone applications. Upon
reflection, combined with some insights gained while writing the programs for this
article, I finally decided that for me at least the problem was minor, if not
non-existent.
The insights referred to above were the discovery that the inclusion of atoi
increased the size of the XCMD from 3k to 10k. This is catatrophic if one hopes to
write a large program which includes perhaps 100 XCMD’s. It is a nuisance with
smaller routines.
The reflection says that the complete object code of a C library routine would
have to be included in every XCMD which made use of it. The redundancy would quickly
get out of hand.
The Toolbox and the SANE library have functions for almost everything that needs
to be done in a program or code resource. This code is in the ROM and is always
available for use without adding to the overhead. Portability considerations aside, it is
desirable to take advantage of it whenever possible while writing Macintosh specific
applications.
CONCLUSION
A practical method of using XCMD’s in standalone C applications was presented.
An evaluation of the memory size problems encountered while developing this
procedure would indicate that high-level lanquage library routines (C, Pascal, etc.)
should be avoided in XCMD’s used by Hypercard as well as those written for standalone
applications.
I can be reached on internet at ghkenner@cc.utah.edu, on Prodigy at BSSX14B and
Apple Link at UUTL.