TOP | UP: Reference | NEXT: Operators |
This chapter provides some technical detail supplementing Chapter 23, Extending the Language. It is intended for those thinking of writing a UCMD or XCMD.
Both XCMDs and UCMDs can be written in any development environment you're comfortable with; Metrowerks's CodeWarrior is a likely candidate. For definitive information about XCMDs and how to write them, see the HyperCard documentation. For UCMDs, it is de rigueur to download the Frontier Software Developer's Kit (SDK) from ftp://ftp.scripting.com/userland/. It contains documentation and examples, and some model projects that you can copy and use as shells; also, it provides the "IAC Toolkit", which greatly simplifies the programming interface for dealing with Apple events. There is also a splendid online tutorial on writing UCMDs, by Brent Simmons, at http://www.ranchero.com/frontier/ucmds.
You may be in doubt as to which to write, an XCMD or a UCMD. UCMDs possess several advantages over XCMDs, of which the most salient are:
Communication with an XCMD depends upon a pointer, called an XCmdPtr , to a data structure called an XCmdBlock. The XCmdPtr is the argument handed to the XCMD when it is called.
The XCMD can get the actual parameter values by way of params; they are all 0-terminated strings. The XCMD ultimately places into result a handle to a 0-terminated string, if desired, and returns.
Here is an outline of the C code of a typical XCMD. It is from the Dartmouth XCMDs, a HyperCard stack by Kevin Calhoun and Roger Brown which includes many XCMDs and their source code.2 It sorts the lines (fields separated by return) of its first parameter according to the item (fields separated by comma) specified in its second parameter; but that's not important here. In fact, I have omitted most of the actual code, reproducing just enough to illustrate typical tasks that an XCMD performs. ValidStrToNum() shows how you have to worry about converting string parameters to the desired type. ResultIs() and HandleDoSort() show ways to construct a result and attach it to the XCmdPtr. Main() shows typical global and memory management concerns. Comments in italic are mine.
XCMDs feature a callback mechanism whereby the XCMD can ask the calling application to perform certain utility actions or provide information, using a fixed repertoire of functions. This involves setting up the XCmdBlock in a particular way and passing it back to the calling application, but the programmer is usually shielded by the API from having to attend to these details. Frontier provides HyperCard-like services in response to some callbacks, but there are many callbacks that are so HyperCard-specific that they just aren't supported (they won't crash Frontier, but they won't be executed either). Table 43-1 shows the supported callbacks.
For how to import an XCMD into the database and write glue for it, refer to Chapter 23.
Communication with a UCMD is by way of Apple events. This adds a great deal of overhead to the UCMD, but the Frontier SDK's API shields the programmer from having to attend to the details. The SDK provides a shell script whose main() calls UCMDmain() , which the programmer must write. This routine obtains the parameters, and returns a result, entirely by the use of IAC verbs from the IAC Toolkit which is included in the SDK. Declarations for these verbs may be found in the file iac.h.
Each Apple event datatype (to which UserTalk's datatypes very closely correspond) has a repertory of verbs for manipulating it. IACget xxxparam() obtains a parameter of type xxx passed by the caller; the OSType passed to it names the parameter (more about this in a moment). IACreturn xxx() causes a value of type xxx to be the returned result. IACget xxxitem() and IACpush xxxitem() are for obtaining or setting, respectively, the specified item of a list (lists are of type AEDescList). For example, in the case of a string:
Boolean IACgetstringparam (OSType, StringPtr);
Boolean IACreturnstring (StringPtr);
Boolean IACgetstringitem (AEDescList *, long, StringPtr);
Boolean IACpushstringitem (AEDescList *, StringPtr, long);
To understand the workings of a typical UCMD we must start with how the UCMD will be called. A UserTalk "glue" verb will construct an Apple event (see Chapter 32, Driving Other Applications) and send it to the UCMD. For example, consider the glue (simplified here) for Brent Simmons's pbs.deleteListItem() :
on deleteListItem (x, index)
return (appleEvent (@pbs.code, 'pbsu', 'dlis', \
'----', list (x), 'indx', number(index)))
The first parameter for appleEvent() is the address of the UCMD living as a binary in the database. The second parameter is the identification code unique to the UCMD as a whole; it isn't actually important what this is, since the UCMD won't check it (we're just satisfying the rules for Apple events). The third parameter identifies the command you want the UCMD to obey; thanks to this, a UCMD can contain more than one functionality. Then comes the usual series of name-value pairs, of which the first is conventionally the "direct object," whose name is '----'.
Now let's look at the source for pbs.code, omitting the code for all of its functionality except what is needed for deleteListItem() (you can examine the full source at pbs.source). Comments in italic are mine.
The main routine uses IACgetverbtoken() to learn what the third parameter to appleEvent() was, and then just switches on that, performing the desired command (and calling IACnothandlederror() if it doesn't recognize the command). deletelistitemverb() uses IACgetlistparam() and IACgetlongparam() to obtain the parameters by name, IACgettextitem() to run through the input list and IACpushtextitem() to create the output list, and IACreturnlist() to package the output list as the value to be returned.
Like XCMDs, UCMDs can call back to Frontier while they are executing, to ask it to evaluate any valid UserTalk expression, by means of a supplied runscript() function. Use of this function is illustrated by a couple of the examples included in the SDK.
Like XCMDs, a UCMD compiled as a resource file can simply be dropped onto Frontier's icon; it will then be imported into the database. It is up to the programmer to write glue.
1. However, people have written utility shells that make writing XCMDs much easier; these are available on the Internet. Particularly recommended is Mark Hanrek's shell, available from ftp://iw.cts.com/public/InfoWorkshop/Shareware/. Also, because of the long history of XCMDs, there are commercial utilities that make writing them much easier than is shown here; with these utilities, you can write your XCMD in BASIC or pseudo-HyperTalk and have all the gory details taken care of for you.
TOP | UP: Reference | NEXT: Operators |