Writing XCMDs in Lightspeed C: About the Aboutbox

Paul R. Potts

December 1988

This simple project creates a new “aboutbox” command for HyperCard which will display a picture, wait for a mouse click or keypress, and return to HyperCard. To construct this project you will need Lightspeed C version 3.0, HyperCard version 1.2, and ResEdit. You will also need an object-oriented or bit-mapped drawing program to create a PICT resource.

To access a resource of type PICT, the toolbox function GetPicture can be used. We can get the handle like this:

PicHandle the_pic;
the_pict = GetPicture(id_num);

where id_num is an integer. Now that we have a handle on the PICT, we can access the parts of the structure. In C, referencing the elements of a structure pointed to by a handle can be done in three ways. The first uses a double dereference to access the elements of the structure directly. The parentheses are necessary in order to dereference the handle rather than the field of the structure:

the_rect = (**the_pict).frameRect;

The second method uses the “->” operator, which does one dereference and also accesses a particular field in a structure. This is the equivalent to “Pointer ^. field” syntax in Pascal, where the carat indicates the dereference and the period the field offset. Thus,

the_rect = (*the_pict)->frameRect;

is the equivalent of our first example. It is also possible to dereference the handle, store the result in a pointer, and access the field through the pointer:

Ptr deref;
deref = (*the_pict);
/* ... other code here... */
the_rect = deref->frameRect;

Note that this method is either extremely unsafe or unfriendly, depending on how it is done. Why? First of all, if the the handle is not locked there is a chance that memory may be rearranged behind your back, and that deref may no longer point to the PICT by the time you get around to accessing it later in your program. The alternative is to lock the handle using HLock. This is very unfriendly, because if another application needs memory space it will be unable to compact the heap properly due to your locked handle. I include this example to show how not to dereference a handle.

Since C is a weakly typed language, I like to program with the “Check Pointer Types” option turned on. This ensures that C will not do any implicit type conversion for you without your knowledge: you must do typecasting on your own. Unfortunately, this often makes code harder to read. Let us take a specific example from my Convert procedure to discuss typecasting:

pstr = (StringPtr)CtoPstr((char *)*c_han);

The function CtoPstr is a Lightspeed C built-in function (I give the prototype in the source code), which converts a zero-terminated string to a Pascal-type string. Note that it works on a pointer to the string rather than the handle, and this explains the dereference of the handle *c_han. CtoPstr accepts a generic pointer __char*__, so we must cast the pointer to this type. Since pstr is defined as a (Pascal type) StringPtr for the later call to the toolbox routine StringToNum, we must force the function to return type StringPtr — hence, the cast of the entire function call.

In order to know which PICT to display with this XCMD, I let HyperCard pass the XCMD an argument. Understanding how this works requires an explanation of a special data structure maintained by HyperCard called the XCmdBlock. The XCmdBlock is a non-relocatable block referenced by a simple pointer: HyperCard passes this pointer to your XCMD when it is called. Here are the first four elements of the XCMDBlock structure:

short   paramCount;
Handle  params[16];
Handle  returnValue;
Boolean passFlag;

When calling an XCMD from inside HyperCard, you simply give the name of the XCMD and follow it with a series of up to 16 parameters. HyperCard stores these parameters as zero-terminated strings (C-type strings) and gives your XCMD handles to them, contained in the params array. paramCount holds the number of parameters sent. If we call our XCMD “aboutbox,” the XCMD call

aboutbox 35

would make the start of the XCmdBlock look like this:

paramCount = 1;
params [0] = Handle -> Pointer -> 35

The next element of the XCmdBlock is also simple: returnValue is used for an XFCN (External Function). The only difference between an XCMD and and XFCN is that an XFCN is called as a function from HyperCard, and should put a return argument into a zero-terminated string and a handle to that argument in returnValue.

If your XCMD is called from within HyperCard, it can choose to ignore the call and simply return. The Boolean variable passFlag can then be set to TRUE by your XCMD to indicate that it did not handle the command, and that the message should continue up the inheritance chain (see the HyperCard Help stack for more information on messages and the inheritance chain.) The default value of passFlag is FALSE, so we will not be concerned with it.

I have included the definition of the XCmdBlock in the file working.xcmd.h. This is an extremely abbreviated version of Apple’s header file. The complete XCMD include files as they are written for MPW C do not function properly in Lightspeed C, but this header file provides the definitions necessary for this introductory project.

Now we are ready to construct our XCMD. Find a HyperCard stack (your Home stack will do nicely). Select part of a drawing and copy it into the Scrapbook, then paste it into your HyperCard stack using ResEdit. You will need to know the resource ID number that ResEdit assigns your PICT when you paste it into your stack. Use ResEdit’s Get Info command to find out this information.

Now create a new project in Lightspeed C and add the MacTraps library to it. Use the Set Project Type menu option as shown in figure 1. You can use any ID number as long as it doesn’t conflict with another XCMD resource already in your stack.

“The Project Type dialog”

Note that I have set the creator of the resulting file to RSED. This way you can launch ResEdit by opening the code resource file.

Now that you have created the project, type in each of the files as a separate document and save it in the same folder with the project document. This ensures that the compiler can find all the included files quickly. Then use the “Add” command to add each file to your project. (working.xcmd.h should be in the folder but not added to the project.) The image below shows the files that should be included in your project:

“The Project window”

Finally, choose “Build Code Resource” from the Project menu and copy the resource from the file Lightspeed C created into your HyperCard stack.

You should now have a new command in your stack. Entering the command into the message box or running it in a script will draw the about box and wait for a mouse click or keypress. The command is:

aboutbox <PICT number>

Next time: I will explore the second half of the XCMDBlock, and discuss how to jump back into HyperCard to call a number of special functions put there just for that purpose.

Source code file 1 of 5: working.xcmd.h

/**************************************************************/
/*  File: Working.xcmd.h  
    Information ©Apple Computer, Inc. 1987
    Abbreviated version of XCMD header info */

typedef struct XCmdBlock 
{ 
    short   paramCount;  /* We are only concerned */
    Handle  params[16];  /* With the first half of the */
    Handle  returnValue; /* XCMDBlock in this project */  
    Boolean passFlag; 

    char    *entryPoint; /* to call back to HyperCard */
    short   request;
    short   result;  
    long    inArgs[8];
    long    outArgs[4];
} XCmdBlock, *XCmdBlockPtr;

typedef struct Str31 
{   char guts[32];
} Str31, *Str31Ptr, **Str31Handle;
/**************************************************************/

Source code file 2 of 5: aboutbox.shell.c

/**************************************************************/
/* File: aboutbox.shell.c                                     */
/* Simple XCMD shell by Paul Potts                            */
/* Needs MacHeaders turned on.                                */
/* This should be compiled in a project along with the
   following files:
    
    Center.c
    Convert.c
    Internal.c
    the MacTraps library */
    
#include "working.xcmd.h" /* needed to define XCmdBlock, etc. */
/*
    Input:  XCmdBlockPtr paramPtr (from Hypercard)
    Output: none (may alter contents of XCmdBlock)
    Calls:  Internal 
*/
void Internal (XCmdBlockPtr paramPtr);   /* prototype for Internal */
pascal void main(XCmdBlockPtr paramPtr); /* prototype for self */

pascal void main(paramPtr)
    XCmdBlockPtr paramPtr;
{
    Internal(paramPtr);
}
/**************************************************************/

Source code file 3 of 5: Internal.c

/**************************************************************/
/* File: Internal.c                                           */
/* Function to display a specified "PICT" resource as an      */
/* "about box" type of dialogue. The input argument, pointed  */
/* to by the handle in inArgs[0], is the ID of a PICT         */
/* resource, which should be contained in the calling stack.  */
/* Needs MacHeaders on.                                       */
#include "working.xcmd.h"
/*
    Input:  paramPtr XCmdBlockPtr
    Output: none
    Calls:  Center, Convert
*/
Rect Center (Rect the_rect);
long Convert(Str31Handle c_han);
void Internal (XCmdBlockPtr paramPtr); /* This function */

void Internal(paramPtr)
    XCmdBlockPtr paramPtr;
{
    long        which_pict;
    EventRecord theEvent;
    Rect        bounding_rect;
    WindowPtr   theWindow;
    PicHandle   AboutBox;

    FlushEvents(everyEvent, 0);

    /* Get the PICT resource ID from Hypercard, then the PICT 
       itself */

    which_pict = Convert ((Str31Handle)paramPtr->params[0]);
    AboutBox = GetPicture(which_pict);

    /* Get the PICT frame rectangle and  center it, then
       enlarge 3 pixels for extra space, construct the window,
       and draw the PICT in it. */

    bounding_rect = Center ((*AboutBox)->picFrame);
    theWindow = NewWindow (0L, &bounding_rect, "\P", TRUE, 
                           dBoxProc, -1L, FALSE, 0L);
    SetPort(theWindow);
    DrawPicture (AboutBox, &theWindow->portRect);

    while (1) 
    { 
        GetNextEvent (everyEvent, &theEvent);
        if (theEvent.what==mouseDown) break;
        if (theEvent.what==keyDown) break;
    } 

    DisposeWindow(theWindow);
    ReleaseResource(AboutBox);
    KillPicture(AboutBox);
    DisposHandle(AboutBox);
    FlushEvents(everyEvent, 0);
}
/**************************************************************/

Source code file 4 of 5: Center.c

/**************************************************************/
/* File: Center.c                                             */
/* Needs MacHeaders turned on. This function accepts a        */
/* rectangle, represented in global coordinates, and centers  */
/* it on the screen, using the Window Manager to determine    */
/* the size of the screen.                                    */
/*
    Input:  Rect the_rect
    Output: new Rect the_rect 
    Calls:  no other functions
*/

Rect Center(Rect the_rect); /* self-prototype */

Rect Center(the_rect)
    Rect the_rect;
{
    GrafPtr wp;
    short width, height, new_top, new_left;
    GetWMgrPort (&wp);
    width= wp->portRect.right; /* screen height and width */
    height = wp->portRect.bottom;

    /* Now use some distance formula math to center the rectangle
       on the screen */
    new_top = ((height / 2) - 
               (the_rect.bottom - the_rect.top) / 2);
    new_left = ((width / 2) -
                (the_rect.right - the_rect.left) / 2);
    OffsetRect(&the_rect, new_left - the_rect.left,
               new_top - the_rect.top );
    return the_rect; /* Now it has been moved */
} 
/**************************************************************/

Source code file 4 of 5: Convert.c

/**************************************************************/
/* File: Convert.c                                            */
/* Needs MacHeaders turned on. A prototype is provided        */
/* This function accepts a handle to a c (zero-terminated)    */
/* string and returns a long int containing the number        */
/* represented by the string                                  */
#include "working.xcmd.h" /* needed for Str31 types */

long Convert(Str31Handle c_han); /* this function */

long Convert(c_han)
    Str31Handle c_han;
{
    long thenum;    /* the longint to return */
    StringPtr pstr; /* pointer to a pascal string */
    pstr = (StringPtr)CtoPstr((char *)*c_han); /* convert to Str255 */
    StringToNum(pstr, &thenum);                /* convert to a long */
    return thenum;
}
/**************************************************************/

Portfolio IndexWriting Archive