MIT Information Systems

Macintosh Development

[Home] [About Us] [People] [Information Systems]
[Kerberos for Macintosh] [Applications] [Miscellaneous Documentation]


From classic 68K to CFM and back

Magic, magic everywhere

By Miro Jurisic
meeroh@mit.edu

Don't read this first

This document is supposed to supplement the Technote 1077; you should have at least a vague idea of the following before you can hope to understand this: Universal Procedure Pointers, Code Fragment Manager, and Mixed Mode Manager.

Read the Technote

If you've already read the Technote 1077, you know that most of the Technote explains how to call CFM library from classic 68K code, assuming that you have to do all of the work in your classic 68K code. None of that is repeated here; most of that is relevant here.

Making a friendly library

If you reread the Technote 1077 carefully you will notice there is a 2-sentence mention of how your life could be much easier if you could do some work on the library side. This document should answer the questions that the Technote left unanswered (or assumed were either trivial or explained elsewhere).

To make your life significantly easier, you need to:

Writing and exporting your UPPs

The most important thing you can do to make calling your CFM library easier from classic 68K code is to export UPPs for all functions that you export. Here's an example of how that works; suppose you have functions declared as follows that you are exporting from your segment:

OSErr MyFirstFunction (UInt32 inParam1, Boolean* outParam2, Boolean* outParam3);
OSErr MySecondFunction (UInt32 inParam1, Boolean* outParam2, Boolean* outParam3);
Then, you should add the following code in the corresponding source files:
enum {
	uppMyFirstFunctionGlueProcInfo = kThinkCStackBased |
		RESULT_SIZE (SIZE_CODE (sizeof (OSErr))) |
		STACK_ROUTINE_PARAMETER (1, SIZE_CODE (sizeof (UInt32))) |
		STACK_ROUTINE_PARAMETER (2, SIZE_CODE (sizeof (Boolean*))) |
		STACK_ROUTINE_PARAMETER (3, SIZE_CODE (sizeof (Boolean*))),
	uppMySecondFunctionGlueProcInfo = kThinkCStackbased |
		RESULT_SIZE (SIZE_CODE (sizeof (OSErr))) |
		STACK_ROUTINE_PARAMETER (1, SIZE_CODE (sizeof (UInt32))) |
		STACK_ROUTINE_PARAMETER (2, SIZE_CODE (sizeof (Boolean*))) |
		STACK_ROUTINE_PARAMETER (3, SIZE_CODE (sizeof (Boolean*)))
};


RoutineDescriptor MyFirstFunctionGlue = BUILD_ROUTINE_DESCRIPTOR (uppMyFirstFunctionGlueProcInfo, MyFirstFunction);
RoutineDescriptor MySecondFunctionGlue = BUILD_ROUTINE_DESCRIPTOR (uppMySecondFunctionGlueProcInfo, MySecondFunction);

Note that the proc info is declared with kThinkCStackBased. This works for Metrowerks and Think C compilers; for MPW compilers you must use kCStackBased. You could use preprocessor symbols to get around this, but be careful that you only use the preprocessor symbols in your source files, and not in any of your public header files (because then you might lose when somebody includes those headers into code compiled under a compiler different from the one where you compiled your library). However, you shouldn't put the UPPs anywhere in your public header files anyways.

Finally, add those routine descriptors to your exported symbols.

At this point, I should clarify that when I say 'export foo', I don't mean that foo should be in your public header files. Although you should be exporting some UPPs, you should not put them in your public header files, because there are some gotchas with preprocessor symbols. All that is meant by 'export foo' is that the symbol foo should be exported from your CFM fragment. The exact way you do this depends on whether you are using pragma directives or export files -- see your favorite compiler documentation for details.

Adding sugar to your public header file

First, you'll need to split your public header file into two sections, one for CFM and one for classic 68K code. Use #ifdef GENERATINGCFM for that. The CFM section, for now, should be the same as it was before (we'll add to that later), i.e. it should contain the usual function declarations. In the 68K section you should to do this:

typedef OSErr (*MyFirstFunctionProcPtr) (UInt32 inParam1, Boolean* outParam2, Boolean* outParam3);
typedef OSErr (*MySecondFunctionProcPtr) (UInt32 inParam1, Boolean* outParam2, Boolean* outParam2);

extern MyFirstFunctionProcPtr	gMyFirstFunctionGlue;
extern MySecondFunctionProcPtr	gMySecondFunctionGlue;

#define MyFirstFunction(inParam1, outParam2, outParam3)\
	((gMyFirstFunctionGlue)(inParam1, outParam2, outParam3))
#define MySecondFunction(inParam1, outParam2, outParam3)\
	((gMySecondFunctionGlue)(inParam1, outParam2, outParam3))

(At some point, I will rewrite these macros so that they actually do some sanity checking before they dereference a nil procedure pointer and jump off to the la la land. This requires that we have a good standardized assert macro.)

Notice that the two #defines effectively replace function declarations; you should not have function prototypes in the classic 68K section of your headers file.

Initializing and terminating your library

Finally, you want to provide an easy way for your library to be initialized and terminated, and you need to instantiate the global procedure pointers that we declared extern above. For example,

MyFirstFunctionProcPtr		gMyFirstFunctionGlue		= nil;
MySecondFunctionProcPtr		gMySecondFunctionGlue		= nil;
ConnectionID			gExampleLibConnID		= kNoConnectionID;

/* These two names are the names of the corresponding fragments (_not_ files) */
Str255	kExampleLibCFM68KLibraryName 		= "\pExampleLib68K";
Str255	kExampleLibPPCLIbraryName		= "\pExampleLibPPC";
/* These two names are the names of your UPPs, as you exported them from your library */ 
Str255	kExampleLibMyFirstFunctionGlueName	= "\pMyFirstFunctionGlue";
Str255	kExampleLibMySecondFunctionGlueName	= "\pMySecondFunctionGlue";

OSErr GetSystemArchitecture (
	OSType* outArchType);

OSErr InitializeExampleLib (
	void)
{
	OSErr		err = noErr;
	Ptr		mainAddr;
	Str255		errName;
	SymClass	symClass;
	OSType		archType;

	if ((err = GetSystemArchitecture (&archType)) != noErr)
		return err;

	if (archType == kMotorola68KArch)
		if ((err = GetSharedLibrary (kExampleLibCFM68KLibraryName, kMotorola68KArch, kLoadNewCopy, &gExampleLibConnID, &mainAddr, &errName)) != noErr)
			return err;
		else {
		}
	else
		if ((err = GetSharedLibrary (kExampleLibPPCLibraryName, kPowerPCArch, kLoadNewCopy, &gExampleLibConnID, &mainAddr, &errName)) != noErr)
			return err;

	if ((err = FindSymbol (gExampleLibConnID, kExampleLibMyFirstFunctionGlueName, &(Ptr)gMyFirstFunctionGlue, &symClass)) != noErr)
		return err;

	if ((err = FindSymbol (gExampleLibConnID, kExampleLibMySecondFunctionGlueName, &(Ptr)gMySecondFunctionGlue, &symClass)) != noErr)
		return err;

	return noErr;
}

OSErr TerminateExampleLib (
	void)
{
	return CloseConnection (&gExampleLibConnID)
}

/* This code is from Technote 1077 */
OSErr GetSystemArchitecture (
	OSType *outArchType)
{
	/* static so we only Gestalt once */
	static long sSysArchitecture = 0;
	OSErr err = 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)
		err = Gestalt (gestaltSysArchitecture, &sSysArchitecture);

	if (err == noErr) {
		if (sSysArchitecture == gestalt68k)
			*outArchType = kMotorola68KArch;
		else if (sSysArchitecture == gestaltPowerPC)
			*outArchType = kPowerPCArch;
		else
			err = gestaltUnknownErr;
		/* who knows what might be next? */
	}
	return err;
}

This done, all you need is to provide interface to these two calls in your public header file. In the classic 68K section, you want to write:

OSErr InitializeExampleLib (void);
OSErr TerminateExampleLib (void);
If you want to make your interface completely transparent to the calling code, you should provide these two in the CFM section of your header file:
#define InitializeExampleLib()
#define TerminateExampleLib()
so that the calling code can use those two calls regardless of the architecture it's being compiled for.

Using your friendly library

By this point, you've done three things:

To use this library from CFM (PPC or 68K) code, you don't have to do anything special. Just as before, you will link against the appropriate version of the library (CFM-68K, PPC, fat), include the public header file, and you are ready to build.

To use your library from classic 68K code, however, you need to do a bit more. First, you ahould not link against either version of the library. If you do, you will get linker errors. Instead, you need to include in your project the source file that contains initialization and termination routines for the library (which you wisely wrote in the third step above). You must make sure that you actually call the initialization routine at the very beginning of your main() and the termination routine at the very end of your main(). Do not call exit(), ExitToShell, terminate(), or abort()! (Or, if you really have to, make sure that you call the termination routine before you exit the application; it's better to just avoid them. Be really careful about porting UNIX code that might call it, however. While you are at it, be very careful about porting UNIX code in general...) Thanks to the magic that is written into your library's public header file, you should call your library's functions just as you usually would -- the macros defined above will do their job to translate that into calls to UPPs.

Related readings

Scott's thoughts on CFM
The one and only: Technote 1077

Acknowledgements

Thanks to Alexis Ellwood for useful feedback, and to Scott McGuire for his thoughts on CFM.


Questions or comments? Send mail to macdev@mit.edu
Last updated on $Date: 2003/11/18 21:58:06 $
Last modified by $Author: smcguire $