Technote PT 14 | May 1993 |
Programmers live in a silicon world. Their environment can be made up of a single development platform (for example, MPW Pascal), or it can be mixed and matched (THINK C and MPW Assembly and Pascal, and so on). When an environment is a single package, the designers of that package can take care of the nitty gritty of code interfacing almost transparently. Examples of this transparency would be having a C compiler straighten out all of the parameters necessary to deal with a Toolbox coded in Pascal with Pascal calling conventions or having a compiler generate extra interface code to make sure the programmer never sees some problem. When the environment does this for you, it does it by providing glue code. If you are using more than one development environment, if you need to call register-based assembly routines, or if you would like to use otherwise good routines that are written in another language, you run into the need for glue code.
Glue code is any code that straightens out interfacing protocols between two
different calling interfaces; it "glues" together otherwise noncommunicating
procedures. It also provides a method of prefacing calls to fix parameters,
adjusting for environmental oddities, and cleaning up after returns to get the
results "just so."
There are three common ways that routines pass information back and forth.
The first, register-based, is for the routine to store the value in a data register (D0), or, if the data is more than a single value, to pass a pointer to a record (parameter block, control block, buffer, and so on) in address register A0. This method is commonly found on the Macintosh in the system routines in the ROM, like the Memory Manager. Results are returned in the same way: The result is placed in a register (D0) or fields of the parameter record are changed or filled in.
The second method is in Pascal and involves placing values, pointers to variables, and space for the result on the system stack (pointed to by address register A7). With this method, values up to 4 bytes are pushed onto the stack, or, if the value is a variable or is longer than 4 bytes, a pointer (itself a 4-byte entity) is pushed onto the stack.
The sequence for this method is as follows:
* (If a function call) push space for or pointer to the result.
* Push the first parameter or pointer, then the second parameter or pointer, then the third, and so on, until all parameters or pointers are pushed.
* Make the call, which places the return address on the stack.
Before returning, the routine called is responsible for placing the result into the result space on the stack or into the space pointed to by the result pointer and popping off all of the parameters.
The third method is in C, and it is much like the Pascal method; however, there are some differences between the two methods. In the C method, no result space is reserved on the stack--the result or the pointer to the result is always passed back in register D0; the parameters are pushed into the stack in the reverse order of the Pascal method; and the routine called does not remove parameters from the stack, leaving this clean up for the calling routine to do.
It could be said that C programmers have an advantage, in that their compiler can generate code to make the call with the Pascal method if they declare the function as pascal, while Pascal programmers have an advantage on the Macintosh as the majority of the Toolbox managers are designed with Pascal interfaces (with the rest of the calls being register based).
The MPW C compiler also provides the #pragma parameter. This instructs the compiler to pass the handle in A0 and flags in D0 automatically, and without any glue. Also note that #pragma parameter only works for routines declared as inlines. So you cannot define your own routines as being register-based. An example of using #pragma parameter to better "glue in" an inline call to HSetState would be to:
#pragma parameter HSetState(__A0,__D0)
and then define your inline as:
pascal void HSetState(Handle h,char flags) = 0xA06A;
"We are writing a HyperCard XFCN in MPW Pascal that calls an external function written in MPW Assembler. When our assembler code gets control, we find that in addition to pushing return space and parameters onto the stack, the Pascal code has also pushed A6. Is this normal?"
This section includes a template that is useful for most of the kinds of glue you could encounter. In addition to demonstrating the Pascal method of parameter passing already described, it also sets up for the use of address register A6 as a local (and recursive) frame reference.
When Pascal calls a procedure or function in Pascal or assembly (or if assembly calls Pascal for that matter), it takes the following steps to set up the stack to: pass parameters, have a result returned, be able to return from the code, and allow for local variables.
If there is a returned value (that is, if the call is to a function), the calling routine makes space on the stack for either the returned value (if it is four bytes or less) or a pointer to the data to be returned (in this case the pointer size of 4 bytes is reserved). For example, for the call
MyResult := MyFunction(VAR MyValue: EventRecord);
a calling routine would do the following:
1. Reserve 4 bytes for a returned value. Typically, the calling routine would subtract 4 bytes from the current address in the Stack Pointer, then zero the 4 bytes to which it then points (that is, CLR.L -(SP)).
2. Push the value (or a pointer to the value) for each parameter in the call onto the stack. In the case of VAR parameters, a calling routine should always pass a pointer to the value, regardless of byte count. In this example, Pascal would generate a PEA MyValue(A5) if MyValue is a global variable or a PEA MyValue(A6) if it is a local variable.
3. Make the call with a BSR MyFunction, remembering that this pushes the 4-byte return address onto the stack.
At this point, the stack is set up properly. Listing from low addresses to high addresses, there is the Stack Pointer, which points to the first byte of the return address; next are 4 bytes that contain the pointer to MyValue; and following that are 4 bytes of zero, which are ready to hold the return value. Above all this in the stack, there may well be another of this same kind of structure from the call to the procedure that calls MyFunction. Figure 4 in Inside Macintosh Volume I, page 92, "Using Assembly Language," illustrates this structure in a general way.
Once you call a subroutine, it may want to allocate space for its own local variables. Even if it does not need to allocate this space, it is convenient to set up a register to access the result, parameters, and return address. By convention, register A6 is used for the purpose, and you can use the assembly-language instruction LINK (later followed by UNLK to remove the link) to set up this stack frame. Pascal (and most of the assembly language seen on the Macintosh) use
LINK A6,#MyLocalSize
where MyLocalSize is a negative value representing the total number of bytes needed to hold the local variables. This instruction does the following:
1. Pushes the old value of A6 onto the stack (thus saving the original calling routine's stack frame) and setting A6 to point to this location. By setting A6 to point to this location, you can retrieve the old A6 later, use offsets from A6 to access the result, parameters, and return address, and use negative offsets from A6 to access all of your local variables.
2. Adds MyLocalSize (a negative value) to the stack pointer, thus allocating local variable space and setting up the stack for additional use (that is, more calls from this subroutine).
The following template is an assembly-language version of the Pascal procedure definition, which is taken from the MPW example program Sample.a:
* PROCEDURE MyProcedure (Param1: type, Param2: type, ParamN: type) Result1: type; * * VAR Local1: type; * Local2: type; * LocalN: type; * * or for C * pascal type MyProcedure(Param1 type, Param2 type, ParamN type) * MyProcedure PROCEDURE EXPORT ; any source file can use this routine * define registers that need to be saved as EQUATES (typically A3-A7 and D4-D7) StackFrame RECORD {A6Link},DECR ; build a stack frame record Result1 DS {size} ; function's result returned to caller ParamBegin EQU * ; start parameters after this point Param1 DS. {size} ; the first parameter on the stack Param2 DS. {size} ; rest of the parameters passed by caller ParamN DS. {size} ; the last parameter passed by caller ParamSize EQU ParamBegin-* ; size of all the passed parameters RetAddr DS.L 1 ; placeholder for return address A6Link DS.L 1 ; placeholder for A6 link Local1 DS. {size} ; a local variable for this procedure only Local2 DS. {size} ; other local variables for this procedure LocalN DS. {size} ; the last local variable LocalSize EQU * ; size of all the local variables ENDR * The above record can be confusing the first time you see it as it generates both * positive and negative offsets. The MPW 3.0 Assembly Reference provides a complete * description of how this works on pages 78-80. WITH StackFrame ; cover our local stack frame LINK A6,#LocalSize ; allocate our local stack frame * save registers trashed by this routine * # # INSERT YOUR CODE HERE # # Exit restore registers trashed by this routine UNLK A6 ; destroy the link MOVEA.L (SP)+,A0 ; pull off the return address ADDA.L #ParamSize,SP ; strip all of the caller's parameters JMP (A0) ; return to the caller ENDP
The UNLK A6 (UnLink) instruction near the end of this example sets the stack pointer to the current A6 (automatically deleting the local variable allocation) and pulls the old A6 off the stack, putting it into A6, thus restoring the calling routine's local stack frame. All of this rigmarole allows for recursive subroutines as well as a stack frame list for intelligent debuggers to know about your procedure's calling history. The code following the UNLK instruction basically deallocates the parameters from the stack, leaving just your returned value.
As for the original question, the A6 you see on the stack when your XFCN is called is just the calling routine's stack frame (pointing back the calling chain of stack frames), which should be all set up for your LINK and local stack frame use. For more detailed information on this topic, refer to Inside Macintosh Volume I, page 85, "Using Assembly Language."
"Okay, so what does the code look like that we insert to do the the glue call?"
The following code is actual glue to return an extra parameter, which is available in a returned register, but not already part of the standard Pascal interface.
* ================================================ * PROCEDURE MungerGlue(h: Handle; offset: LONGINT; * ptr1: Ptr; Len1: LONGINT; * ptr2: Ptr; Len2: LONGINT; * VAR ErrFromD0: LongInt) : LONGINT; * Glue code to call Munger and return the undocumented * error return code available in D0. * ================================================ PRINT PUSH,OFF ; don't print any of this stuff INCLUDE 'Traps.a' PRINT POP MungerGlue PROCEDURE EXPORT StackFrame RECORD {A6Link},DECR ; build a stack frame record MungerGlueReturn DS.L 1 ; LongInt returned value ParamBegin EQU * ; start parameters after this point h DS.L 1 ; Handle offset DS.L 1 ; LongInt ptr1 DS.L 1 ; Pointer len1 DS.L 1 ; LongInt ptr2 DS.L 1 ; Pointer len2 DS.L 1 ; LongInt ErrFromD0 DS.L 1 ; New LongInt ParamSize EQU ParamBegin-* ; size of all the passed parameters RetAddr DS.L 1 ; place holder for return address A6Link DS.L 1 ; place holder for A6 link * No need for local variables but any would be inserted here LocalSize EQU * ; size of all the local variables ENDR WITH StackFrame ; Use this record for our local stack ; frame. Link A6,#LocalSize ; Allocate our local stack frame. Clr.L -(SP) ; Reserve space for out returned value. Move.L h(A6),-(SP) ; Place parameters on the stack Move.L offset(A6),-(SP) ; by copying the values passed Move.L ptr1(A6),-(SP) ; to us in the same order that Move.L len1(A6),-(SP) ; they appear on the stack as Move.L ptr2(A6),-(SP ; passed to us. Move.L len2(A6),-(SP) ; _Munger Move.L (SP)+,MungerGlueReturn(A6) ; Pass what is returned to ; us back to our caller. MoveA.L ErrFromD0(A6),A0 ; Pass the value from register D0 into Move.L D0,(A0) ; the variable pointed to by the new ; parameter. Exit Unlk A6 ; Clean up for return by destroying the MoveA.L (SP)+,A0 ; link. Pull off the return address. AddA.L #ParamSize,SP ; Strip all of the caller's parameters. Jmp (A0) ; Return to the caller. ENDP
This glue includes the file Traps.a for the definition for the symbol (label) _Munger, and it is surrounded by PRINT pseudo-operators to tell the assembler not to include the entire file in the listing. The PROCEDURE statement defines MungerGlue as the entry point (or procedure name) of this routine, while the code between the RECORD and ENDR instructions define a record named StackFrame that defines the information passed to this routine on the stack. Finally, the body of the assembly-language code passes the parameters it receives to the _Munger call, passes back the additional value, and then cleans up the stack and returns.
This final example is of Pascal calling a native C routine (not defined as a pascal function). Assuming the routine
pascal Boolean BitTst(Ptr bytePtr,long bitNum)
was instead defined in C as
Boolean CBitTst(Ptr bytePtr,long bitNum)
then you would need the following glue to make this call from Pascal:
* FUNCTION MyCBitTstGlue(bytePtr: Ptr;bitNum: LONGINT): BOOLEAN; MyCBitTstGlue PROCEDURE EXPORT ; any source file can use this routine StackFrame RECORD {A6Link},DECR ; build a stack frame record Result1 DS.W 1 ; Boolean always takes a word on the stack ParamBegin EQU * ; start parameters after this point bytePtr DS.L 1 ; Pointer bitNum DS.l 1 ; LongInt ParamSize EQU ParamBegin-* ; size of all the passed parameters RetAddr DS.L 1 ; placeholder for return address A6Link DS.L 1 ; placeholder for A6 link LocalSize EQU * ; size of all the local variables ENDR IMPORT CBitTst WITH StackFrame ; cover our local stack frame LINK A6,#LocalSize ; allocate our local stack frame Move.L bitNum(A6),-(SP) ; Nth parameter Move.L bytePtr(A6),-(SP) ; N-1th (actually 1st) parameter Jsr CBitTst ; Call C version of BitTst Move.W D0,Result1(A6) ; return to Pascal the value returned by C ; Exit: UNLK A6 ; destroy the link MOVEA.L (SP)+,A0 ; pull off the return address ADDA.L #ParamSize,SP ; strip all of the caller's parameters JMP (A0) ; return to the caller ENDP
It is interesting to note another problem that Pascal has with C. Since, in C, the calling routine removes the parameters after the call, C programmers can make a call to a routine with more parameters than necessary, which means that they can call a function with a variable number of parameters. Although glue can straighten this out, the programmer must know how many parameters are actually being passed to make the connection.
Even though all the examples in this Note are in assembly-language code, it is possible to write some glue in C. In the case of creating Pascal interfaces for existing C object modules, the C programmer can create a small pascal function that calls C as C itself. In addition, the techniques in this Note work the same for any other language, only the interfacing details differ. If using another language, refer to the language manuals for the specific interfacing details.
The moral of the story is that by carefully applying a little glue code, you
can greatly simplify your development in mixed-language environments as well as
getting yourself up to speed quickly when you encounter new interfaces (such as
QuickTime, AppleScript, and so on) that your development environment does not
already include.
Further Reference:
THINK C and THINK Pascal are trademarks of Symantec Corporation.