Calling CFM Libraries From 68K
Volume Number: 15
Issue Number: 3
Column Tag: Programming Techniques
Calling CFM Shared Libraries From Classic 68K
by Miro Jurisic
Things they don't tell you in Technote 1077
One of the great things about Macintosh software is that older 68K application software
tends to work on the newer PowerPC machines and that user of the older machines can
get modern software. One example of this is Apple's support for the Code Fragment
Manager on 68K ("CFM68K") beginning with System 7.1. With this, 68K code can use
the Code Fragment Manager to implement libraries and plug-ins, or can call PowerPC
code when running emulated on a PowerPC system.
George Wagner first described how to call CFM shared libraries (whether PowerPC or
68K) from 68K code; you can find this as Apple Developer Technical Support Technote
1077 (TN 1077).
This article contains improvements to the code provided in TN1077, making the code
more robust and provides examples of additional problems and techniques. In
particular, this article describes:
• calling CFM functions that return pointers,
• calling CFM functions that cannot be directly called via the Mixed Mode
Manager because of their prototypes, and
• generating CFM glue code automatically from a Perl script.
This article assumes that you are familiar with the Code Fragment Manager and shared
libraries (as described in [IM:PPC] and [IM:RTA]), and that you have read TN 1077
[TN1077].
Correctly Dealing with System 7
The code in TN 1077 does an incorrect check to determine the runtime architecture of
the machine it's running on.
The problem with the TN 1077 code is that it returns an error when the
gestaltSystemArchitecture Gestalt selector is not installed, i.e., on all versions of Mac
OS before 7.1.2. However, CFM-68K is backwards compatible to system 7.1; as a
result, TN 1077 glue code fails to load CFM-68K shared libraries on Mac OS 7.1 and
7.1.1.
The correct version of the code is:
GetSystemArchitecture
GetSystemArchitecture determines system architecture it is running
in (68K or PPC).
static pascal OSErr GetSystemArchitecture (
OSType *outArchType)
// static so we only Gestalt once
static long sSysArchitecture = 0;
OSErr tOSErr = noErr;
// assume wild architecture
*outArchType = kAnyCFragArch;
// If we don't know the system architecture yet...
// Ask Gestalt what kind of machine we are running on.
if (sSysArchitecture == 0)
tOSErr = Gestalt(gestaltSysArchitecture,
&sSysArchitecture);
if (tOSErr == noErr) { // if no errors
if (sSysArchitecture == gestalt68k) // 68k?
*outArchType = kMotorola68KCFragArch;
else if (sSysArchitecture == gestaltPowerPC) // PPC?
*outArchType = kPowerPCCFragArch;
else // who knows what might be next?
tOSErr = gestaltUnknownErr;
} else if (tOSErr == gestaltUndefSelectorErr) {
// No system architecture gestalt
// We must be running on a system older than 7.1.2, so
// we are on a 68K machine.
*outArchType = kMotorola68KCFragArch;
tOSErr = noErr;
}
return tOSErr;
}
Checking for CFM-68K
The TN 1077 code does not check for presence of the Code Fragment Manager before
calling it to load the library. This causes the glue code to crash 68K machines with an
Illegal A-Trap when CFM-68K is not present. To fix this error, we add code to the
Find_Symbol function to check for presence of CFM:
Find_Symbol
Addition to the Find_Symbol function that correctly checks for
presence
of CFM before calling it.
static pascal OSErr Find_Symbol (
Ptr* pSymAddr,
Str255 pSymName,
ProcInfoType pProcInfo)
/* Code to determine the runtime architecture goes here*/
// If we don't have CFM68K
// return a reasonable-looking error.
sOSErr = cfragLibConnErr;
return sOSErr;
}
/* Code to load the library and find the symbol goes here */
}
Clearly, we also need to add the HaveCFM() function:
HaveCFM
Checking for presence of the Code Fragment Manager
static pascal Boolean HaveCFM(void)
OSErr theError = noErr;
// static so we only call Gestalt once
static Boolean sHaveCFM = false;
static Boolean sCalledGestalt = false;
long response;
theError = Gestalt(gestaltCFMAttr, &response);
sCalledGestalt = true;
sHaveCFM =
(theError == noErr) &&
(((response >> gestaltCFMPresent) & 1) != 0);
}
return sHaveCFM;
}
Prototype Restrictions
Restrictions in the Mixed Mode Manager limit which functions in a CFM shared library
can be accessed from classic 68K code. Only functions
• which have a fixed number of arguments
• which have no more than 13 arguments
• whose arguments and the return value (if the function returns a non-void
value) all have size of 1, 2, or 4 bytes can be called from classic 68K code.
This means that you cannot access any functions that take or return structs or some
floating-point types; however, you can use functions that take or return pointers to
structs or pointers to floating-point types.
Thus, if you control the library, you may want to consider changing the library
interface so that your functions satisfy the above conditions. If you do not control the
library, or you cannot change the library interface, you can write a wrapper library
which provides an interface that can be called from classic 68K, and just calls the real
library to do the work (with arguments appropriately rearranged).
For example, if you have a function in a shared library with the following prototype:
myFunction
Example of a function that cannot be called via the Mixed Mode Manager
struct myStruct {
UInt32 a;
UInt32 b;
}
// The following function can't be called from classic 68K
// using Mixed Mode Manager because the size of its
// arguments and the return value is not 1, 2, or 4 bytes.
myStruct myFunction (myStruct param1, myStruct param2);
then you can write the wrapper function like this:
myFunctionWrapper
Example of a wrapper function that can be called via the Mixed Mode
// The following function can be called from classic 68K
// code using the Mixed Mode Manager.
// Note how the wrapper function avoids having to deal
// with memory allocation by changing the return value
// into a non-const pointer argument
void myFunctionWrapper (myStruct* result, const myStruct* param1,
const myStruct* param2) {
*result = myFunction (*param1, *param2);
}
If you put this wrapper function in a shared library, then you can call the wrapper
function from your 68K code (because the wrapper function does not violate the Mixed
Mode Manager restrictions), and the wrapper function can call the real function
(because that is just a cross-fragment call that does not have those restrictions).
It is not possible to write a simple wrapper for a shared library function which takes
a variable number of arguments, such as the C "printf" function. First of all, even if
you could write a wrapper function, it would still be limited to no more than 13
arguments. However, the real problem is that the procedure information you would
have to pass to the Mixed Mode Manager depends on the number and the types of the
arguments, and this information is lost once the arguments are put on the stack. Either
you would have to pass the number and sizes of arguments into the glue function, or the
glue function would have to parse the arguments to determine how many there are and
how big they are.
We can use printf as an example,
printf("Hello, my name is %s\n", "Miro");
The printf function has to take the first argument and use the information provided
there to extract the remaining parameters from the stack. To write a wrapper for this
function, you would have to write a parser to extract the arguments, build the
appropriate routine descriptor, and push them back onto the stack in the correct form
before calling the function.
In any case, the work involved in writing the wrapper function is probably better
spent rewriting the library function to take a fixed number of arguments.
Correctly Handling Library Functions that Return Pointers
Suppose your classic 68K code makes a call into a CFM shared library via glue code.
The call is executed like this:
• Your classic 68K code calls the classic 68K glue function, passing
arguments according to a 68K calling convention. The particular calling
convention used depends on the compiler options used to compile the glue
functions.
• The glue calls the CFM library function using a Universal Procedure
Pointer (UPP).
• The UPP invokes the Mixed Mode Manager, which examines the
information in the UPP to determine how to rearrange the arguments into the
CFM calling convention.
• The Mixed Mode Manager calls the CFM function.
• The CFM function executes, taking its arguments according to the CFM
calling convention (68K or PPC, depending on which architecture the code is
running on).
• The CFM function places its return value in the appropriate place, again
according to the CFM calling convention.
• The Mixed Mode Manager returns control to the classic 68K code in the
glue function, leaving the return value of the CFM function in the D0 register.
• The glue function takes the return value and returns it to its caller.
To find arguments passed by the glue function and the value returned to the glue
function, the Mixed Mode Manager uses procedure information in the UPP. This means
that you have to make sure that compiler options you are using to compile the glue
functions match the procedure information in the UPPs for the shared library
functions. Otherwise, either your shared library function will get garbage arguments,
or the glue function will get a garbage return value.
If your glue functions follow Pascal calling conventions, then you should use
kPascalStackBased as the calling convention in procedure information.
If your glue functions follow C calling convention, and are compiled with an MPW
compiler, or with another MPW-compatible compiler (such as Metrowerks compiler
with "MPW Calling Conventions" turned on), then you should use kCStackBased in
procedure information.
If you are compiling with the default settings for Metrowerks compilers, then your
glue functions will follow the ThinkC calling convention, and you should use
kThinkCStackBased in the procedure information. There is one twist, however: if the
return value from a UPP call is a pointer, the glue function will expect the result to
be returned in the A0 register instead of the D0 register. However, the
MixedModeManager will always put the return value in D0 if UPP has
kThinkCStackBased for its calling convention.
Therefore, if you start with the default settings for Metrowerks compilers, you have
to compile your glue functions so that they expect the results to always be returned in
D0. This is done by surrounding your glue code with the following pragmas:
#pragma d0_pointers on
// Prototypes for glue functions go here
#pragma d0_pointers reset
Generating Glue Code Programatically
As I was working with CFM glue for several shared libraries, it occurred to me that
writing the glue was a fairly mechanical process; there is a section of code that's
invariant, and a secion of code that contains glue functions, but the glue functions
follow a fairly straightforward pattern. Thus I thought generating the glue with a
script might be a good idea. Looking at my options, I decided that Perl was a good tool
for the job, so I summoned my local Perl deity.
She consed up1 a script that takes as input a list of C function prototypes, such as:
Sample prototype list
UInt16 sampleFun1 (void* arg1, UInt32 arg2);
void sampleFun2 (UInt32* arg1, UInt16 arg[]);
void sampleFun3 (void);
Although it may seem annoying that you need to take your existing C headers and strip