Calling CFM Code
Volume Number: 13
Issue Number: 8
Column Tag: develop
Calling CFM Code From Classic 68K Code
or There and Back Again, A Mixed Mode Magic
Adventure
by George Warner, Apple Developer Technical Support (DTS)
There are specific instances when you must call Code Fragment Manager (CFM) code
from classic 68K code -- for example, if your application cannot be converted to CFM,
but you want to be able to use CFM libraries. Or, you want to add plug-in support to an
existing classic 68K application without having to convert it to CFM68K. In addition,
you may want to use CFM68K to develop an application to run on both 68K and
PowerPC computers and use a single fat library for both environments.
Another instance would be developing for OpenDoc, which requires shared library
support. Prior to this article, only CFM applications could take advantage of OpenDoc.
This article explains how to add library support to classic code.
Calling CFM From Classic Code
The basic steps for calling CFM from classic code are as follows:
1. Determine the address of the CFM routines you want to call.
2. Create a routine descriptor for the CFM routine.
3. Call the routine descriptor.
4. Clean-up after yourself.
Determining the Address of the CFM Routines
The most common way to determine the address of a CFM routine is to use FindSymbol
against a shared library.
OSErr FindSymbol (ConnectionID connID, Str255 symName,
Ptr* symAddr, SymClass *symClass);
The first parameter is a connection ID to a fragment. This can be obtained from a call
to GetSharedLibrary, GetDiskFragment or GetMemoryFragment. The second parameter
is the name of the symbol, in this case the name of the routine we want to call. The
address of the symbol is returned in the third parameter and the last parameter
returns the symbols class.
Creating a Routine Descriptor
In CFM code, you normally use the NewRoutineDescriptor routine to create routine
descriptors. The non-CFM version of this is the NewRoutineDescriptorTrap() routine.
See the notes on using New[Fat]RoutineDescriptor[Trap] at the end of this article.
pascal UniversalProcPtr NewRoutineDescriptorTrap(
ProcPtr theProc,
ProcInfoType theProcInfo,
ISAType theISA);
To create a routine descriptor, you need the address, the procedure information and the
architecture of the routine being described. The address was obtained in the first step.
The procedure information is based on the calling conventions for the parameters
passed to and returned from the routine. The last parameter is the architecture of the
routine being called. In the headers, this is defined as the ISAType, which is a
combination of two separate 4 bit values, the Instruction Set Architecture (ISA)
and Runtime Architecture (RTA).
Determining the Procedure Information
The Mixed Mode Manager supports many calling conventions. The most common are
stack based for Pascal and C, register based for operating system traps and register
dispatched for selector based Toolbox traps. The easiest way to define the procedure
information is via a enum: for example, if your routine was defined like this:
pascal Ptr Get_Message(short pResID, short pIndex);
Its procedure information would then be defined like this:
kGet_MessageProcInfo = kPascalStackBased
| RESULT_SIZE(SIZE_CODE(sizeof(Ptr)))
| STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(short)))
| STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(short)))
Note: The RESULT_SIZE, SIZE_CODE & STACK_ROUTINE_PARAMETER macros are
defined in MixedMode.h. ProcInfo is documented in Chapter Two, Inside Macintosh:
PowerPC System Software.
Determining the Architecture
Everyone seems to be tempted to use GetCurrentArchitecture here; JUST SAY NO!
GetCurrentArchitecture is a macro that returns the architecture of the currently
compiling code, in our case, classic. What we want is the architecture of the CFM code
that we are calling from our classic code. Remember: the architecture includes the ISA
and the RTA. The correct thing to do is to call Gestalt to find out what kind of CPU the
code is running on (68K or PPC) and use this to create the routine descriptor with the
correct architecture:
static pascal OSErr GetSystemArchitecture(OSType *archType)
// static so we only Gestalt once
static long sSysArchitecture = 0;
OSErr tOSErr = noErr;
// assume wild architecture
*archType = 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?
*archType = kMotorola68KArch;
else if (sSysArchitecture == gestaltPowerPC) // PPC?
*archType = kPowerPCArch;
else
tOSErr = gestaltUnknownErr;
// who knows what might be next?
}
return tOSErr;
}
Note: Don't confuse the OSType architecture used to specify fragment architecture
with the SInt8 ISA/RTA architecture used to specify routine descriptors. This routine
determines the OSType architecture. I did it this way because I use it to open the
connection to my shared library, which requires the OSType. When I create my
routine descriptors, I use this value to conditionally execute the
NewRoutineDescriptorTrap routine with the correct ISA/RTA type parameter:
static OSType sArchType = kAnyCFragArch;
ISAType tISAType;
if (sArchType == kAnyCFragArch) // if architecture is still
undefined...
// & determine current atchitecture.
sOSErr = GetSystemArchitecture(&sArchType);
if (sOSErr != noErr)
return sOSErr;
}
if (sArchType == kMotorola68KArch) // ...for CFM68K
tISAType = kM68kISA | kCFM68kRTA;
else if (sArchType == kPowerPCArch) // ...for PPC CFM
tISAType = kPowerPCISA | kPowerPCRTA;
else
sOSErr = gestaltUnknownErr; // who knows what might be next?
if (sOSErr == noErr)
myUPP = NewRoutineDescriptorTrap((ProcPtr) * pSymAddr,
pProcInfo,tISAType);
return sOSErr;
Calling the Routine Descriptor
From CFM code you normally use CallUniversalProc to call the routine associated with
a routine descriptor. (A Universal Procedure Pointer (UPP) is a pointer to a routine
descriptor.) However, CallUniversalProc is only implemented in shared libraries. For
compatabality reasons, in classic code UPP's can be treated like ProcPtr's, i.e., they
are pointers to executable code. This is because the first two bytes of a routine
descriptor is the 68K mixed mode magic ATrap. When we jump here from 68K code,
the trap is executed and the Mixed Mode Manager takes over, setting up passed
parameters in registers or on the stack, based on the ProcInfo in the routine
descriptor: switching the architecture and then jumping to the CFM code. This is how
you would call your routine: