TweetFollow Us on Twitter

Accelerating Code Resources

Volume Number: 13 (1997)
Issue Number: 6
Column Tag: Plugging In

Accelerating Code Resources and Import Libraries

by Fabrice Ferino, MDL Information Systems

How your plug-in can take advantage of the Power Macintosh run-time architecture without telling the host application

Import Libraries as Plug-ins

The traditional mechanism for the extension of a Macintosh application has been the use of code resources. The Power Macintosh line of computers, and its associated run-time environment, have enabled applications to provide a new way to implement extensions as import libraries using the "universal plug-in" architecture described in [4]. However, even if the application does not provide explicit support for the use of import libraries as their extension mechanism, it is possible and advantageous for a number of reasons to implement PowerPC native extensions as import libraries, and to still package the complete plug-in a single file without contributing to the clutter of the Extensions folder.

A Bit of History

For a long time, many of the successful Macintosh applications have provided a mechanism to extend their functionality. In the 68k run-time environment, this mechanism relied on the loading and executing of code resources. However, this type of code has been more difficult to write than application code because of the limitations imposed by the 68k run-time environment: no A5 world to reference the globals, the limitation of the size of the code resource to less than 32K, and limit to a single segment (because there is no jump table).

The majority of the modern development environments provide some ways to overcome most of these limitations. The A4 register is cleverly hijacked to reference the globals of the code resource, branch islands or the 68020 32-bit branching instructions are used to allow code resource of a size larger than 32k. Still, writing a 68k code resource requires more care and attention to detail than the common application code. Readers interested in this area are directed to the excellent information available on this subject. ([3], [5]).

The situation improved significantly with the arrival of the PowerPC run-time architecture. With the availability of the Code Fragment Manager (CFM), an integral part of the PowerPC system software, it became possible to implement extensions using import libraries (colloquially known as shared libraries). Each import library is a full-fledged MacOS citizen with its own global data space, and no size limits. Additionally, if virtual memory is enabled and the import library is located on a device that supports paging, the import library will be file-mapped and its code will not take space in the heap of the host application. With all these advantages, many PowerPC native applications (Code Warrior IDE, FreeHand, etc...) quickly adapted their plug-in mechanism to use import libraries.

However, some applications still require their extensions to be code resources (some of them have not be rewritten for the PowerPC architecture and others did not adopt the new mechanism for diverse reasons). For this case, it is still possible to take advantage of the increase of performance provided by the PowerPC family of microprocessors by adopting an accelerated code resource. An accelerated code resource runs only on Power Macintosh but can be called by both a 68k application or a PowerPC application.

The format of an accelerated code resource is simple: this is a CFM code fragment preceded by a routine descriptor. The first instruction in a routine descriptor is the Mixed Mode Magic A-trap that triggers the mode switch necessary when the PowerPC code resource is called by 68k code. But accelerated code resources, although they provide some of the advantages of import libraries, still have some of the old limitations (see [2] 1-39). Finally, accelerated code resources have a very annoying characteristic: they have been difficult to debug with a source level debugger like the Metrowerks Debugger. The Macintosh Debugger from Apple provides some support for debugging memory-based code resource but it is still awkward. Source level debuggers become confused because the CFM removes the connection information from its tables after a memory based fragment has been prepared.

All these problems can be solved if most of the code of the extension resides in an import library. The accelerated code resource then is just used as an interface between the application and the import library. Figure 1 shows the general structure of a 68k code resource plug-in, of an accelerated code resource plug-in, and of a hybrid accelerated code resource-import library.

Figure 1. Some architectures for plug-in code resources. Two other useful types not represented here are safe fat code resources and import library based safe fat code resources. The article focus on the build of the third type of code resources. RD: routine descriptor.

Another advantage is that import libraries can have multiple entry points. If you need to implement several code resources, each one providing an entry point to the application, each of these code resources can call its own entry point in the import library. This design makes the development of plug-ins that share code much easier and offers the possibility for plug-ins to easily share data at run-time. This can be done without having to allocate some data structure in the heap and devising a mechanism to inform the plug-ins how to access this import data. The idea for this plug-in architecture came when reading [2] 1-40:

"Note that a code fragment stored as an accelerated code resource can import both code and data from an import library. The code and data in an import library do not move in memory. As a result you can sidestep the restrictions on global data in an accelerated code resource by putting the global data used by the accelerated code resource into an import library."

To solve the debugging problem, this idea is extended to have most of the data and of the code in an import library.

A Model Plug-in Application Based on Sprocket

An example host application is needed to demonstrate the implementation of import library based accelerated code resources. Instead of selecting one of the real world applications that do not take advantage of import libraries for their plug-in mechanism, a modified version of the Sprocket framework that can load and execute plug-ins has been implemented. With these extensions, Sprocket becomes a good model of a 68k CFM unaware application. Note that the implementation is not claimed to be the best one, but is simple and powerful enough to explore the concepts exposed in this article. Using a 68k program makes the environment even more hostile for import libraries.

One main difference with real world applications is that the application does not scan a special plug-in folder but allows the user to load plug-ins by selecting the new Load Plug-in... menu item in the File menu. Consequently, the plug-ins used by the application can be located anywhere on any volume. (This feature will be used later in the article.)

Most of the code for these extensions reside in a new file PlugInSupport.cp. This file implements the loading and initialization of plug-ins and the forwarding of events to the loaded plug-ins. It also implements a simple callback mechanism. The only function provided by the callback is the creation of a new Sprocket window so that a plug-in window behaves correctly in the floating window environment of Sprocket. (A new subclass of Sprocket window designed to be used by plug-ins is also implemented.) Otherwise, the plug-in rules are typical of those found with most applications: the plug-ins must be code resources of a certain type ('PLUG' in our example), and the communication between the plug-in and the application is the exchange of pointers to data structures. The file PlugInBlock.h defines how the application and its plug-ins communicate.

Listing 1: PlugInBlock.h

#define kNumParam (8)

/* 
The align pragma will be ignored by most 68k compilers. If you are 
applying the techniques described here for an old 68k application, 
it is very likely that the include file provided does not include 
these pragma statements: add them now before you build your PowerPC 
native plug-in 
*/
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=mac68k
#endif

// message that the plug-in can receive 
enum
{
   plugInInit,
   plugInEvt,
   plugInLoadFile,
   plugInWindowClosed,
   plugInExit
};

// callback request message sent by the plug-in
enum
{
   createWindow
};

// the callback in the application will receive a pointer to this structure
// message is the code for the operation requested by the plug-in
// param is used to pass the parameters back and forth between the application
// and the plug-in
typedef struct 
{
   short            message;
   Ptr               param[kNumParam];
} CallbackParamBlock, *CallbackParamPtr; 

// definition of the callback entry point
// we use pascal callling convention because C calling conventions may 
// vary between compilers
typedef pascal Boolean (*CallBackEntryPoint)                                                    (CallbackParamPtr pb);

// the plug-in will receive a pointer to this structure
// similar to the struct received by the callback with the addition of 
// a pointer to the callback function in the application
typedef struct 
{
   short             message;
   Ptr               param[kNumParam];
   CallBackEntryPoint   callback;   
} PlugInParamBlock, *PlugInParamPtr; 

// definition of the plug-in entry point
// we use pascal callling convention because C calling conventions may 
// vary between compilers
typedef pascal Boolean (*PlugInEntryPoint) 
                         (PlugInParamPtr pb);

The Plug-in: a QuickDraw3D Viewer

The plug-in implemented is a QuickDraw3D file viewer. QuickDraw3D is available only on Power Macintosh. (A Windows95 implementation should be available by the time you read this.)

Figure 2. The QuickDraw3D viewer plug-in in action.

To build the plug-in we will use two projects: one for an accelerated code resource as the bridge between an import library and the host application, the other for the import library with all of the code of the plug-in. The accelerated code resource and import library will be packaged as a single file, instead of adding yet another file to the growing crowd in the Extensions folder. This also facilitates installation and removal of the plug-in. (The Extensions folder should really contain only the import libraries used by several applications, that is shared libraries, and not every import library used by any single application.) Each CodeWarrior project will use the product of the other project, so this requires some bootstrapping. Since we must have the PEF of the import library to successfully link the accelerated code resource, we start by building the import library.

The Import Library Project

The import library will export only one symbol: its main function called PlugInMain(). This function, like the code resource entry point, accepts a single parameter, a pointer to a PlugInParamBlock, and returns a Boolean. Displaying a QuickDraw3D file is very easy because QuickDraw3D provides a QD3DViewerObject that does most of the work [1]. The other interesting parts in the code are the definitions to build and call a universal procedure pointer (UPPs) to get to the callback procedure in the 68k application. The classic trap is to copy and paste similar definitions from the universal headers. However, the definitions in the universal headers are there to help set up UPPs for the functions in the source file not for the functions called by the source file. In this example we know that the callback procedure is in 68k code, so we use kM68kISA for the run-time architecture parameter in NewRoutineDescriptor().

Listing 2: main.c

/************************************************************
   main.c
   Entry point for the import library
************************************************************/
#include "PlugInBlock.h"

#include <QD3DViewer.h>
#include <CodeFragments.h>


typedef UniversalProcPtr CallBackUPP;

// procedure info for the callback procedure
enum {    
   callBackProcInfo = kPascalStackBased
       | RESULT_SIZE(SIZE_CODE(sizeof(Boolean)))
       | STACK_ROUTINE_PARAMETER(1,
     SIZE_CODE(sizeof(CallbackParamPtr)))
};

// macro to create and call a Universal Procedure Pointer for the callback
// We know the callback is 68K code so we make sure we are using 
// the constant kM68kISA. 
#define NewCallBackProc(userRoutine)      \
      (CallBackUPP) \ 
      NewRoutineDescriptor((ProcPtr)(userRoutine),\
               callBackProcInfo, kM68kISA)

#define CallCallBackProc(userRoutine,param)\
      CallUniversalProc((UniversalProcPtr)(userRoutine),\
                                 callBackProcInfo, param)

// typedef to satisfy the compiler when checking for unresolved symbols
typedef TQ3ViewerObject (*Q3ViewerNewProcType) 
                     (CGrafPtr, Rect*, unsigned long);
// Our globals
WindowPtr         gMyWindow;         // our window
TQ3ViewerObject    gMyViewer;         // the QD3D viewer object
Boolean            gFileOpened;      // has a file been opened ?
FSSpec            gCurrentFile;      // FSSpec of the file being displayed

// prototype for the exported function. We use pragma export
// to inform CodeWarrior that this is an exported function.
#pragma export on
Boolean PlugInMain(PlugInParamPtr pb);
#pragma export off

// prototypes for functions in this file
Boolean DoInit(PlugInParamPtr pb);
Boolean DoOpenFile(PlugInParamPtr pb);
Boolean DoWindowClosed(PlugInParamPtr pb);
Boolean DoEvent(PlugInParamPtr pb);
Boolean DoExit(PlugInParamPtr pb);
Boolean Get3DViewer( PlugInParamPtr pb);
Boolean IsColorPort(GrafPtr port);
Boolean SameFile( FSSpec *spec1, FSSpec *spec2 );

PlugInMain
/*********************************************************************
   PlugInMain
   Exported function.
   Based on the message, forwards the call to the appropriate 
   function
*********************************************************************/
Boolean PlugInMain( PlugInParamPtr pb)
{
   Boolean retVal;
   
   switch( pb->message)
      {
      case plugInInit:
         retVal = DoInit(pb);
         break;
      
      case plugInLoadFile:
         retVal = DoOpenFile(pb);
         break;
      
      case plugInWindowClosed:
         retVal = DoWindowClosed(pb);
         break;
         
      case plugInEvt:
         retVal = DoEvent(pb);
         break;
         
      case plugInExit:
         retVal = DoExit(pb);
         break;
         
      default:
         retVal = false;
         break;   
      }
      
   return retVal;
}

DoInit
/*********************************************************************
   DoInit
   Deal with the init message. 
*********************************************************************/
Boolean DoInit(PlugInParamPtr /*pb*/)
{
   // verify the presence of QuickDraw3D Viewer
   return (Q3ViewerNew != 
      (Q3ViewerNewProcType) kUnresolvedCFragSymbolAddress);
}

DoOpenFile
/*********************************************************************
   DoOpenFile
   User wanted to load a file. Snif it and open it if we can.
   Application sends us a pointer to the FSSpec in param[0].
*********************************************************************/
Boolean DoOpenFile(PlugInParamPtr pb)
{
   Boolean   retVal;
   short      refNum;
   FInfo     fileInfo;
   Boolean     dataFileOpen = false;
   
   // Is it again the same file ?
   if (gFileOpened && 
         SameFile( &gCurrentFile, (FSSpecPtr) pb->param[0]))
   {
      return true;
   }
   
   retVal = (FSpGetFInfo( (FSSpecPtr) pb->param[0],
            &fileInfo) == noErr &&
            fileInfo.fdType == '3DMF');
   // open the data fork of the file then...
   if (retVal)
   {
      dataFileOpen = 
         retVal = 
         (FSpOpenDF( (FSSpecPtr) pb->param[0], fsCurPerm,                               &refNum) == noErr);
   }
   // make sure we have a 3D viewer
   if (retVal)
   {
      retVal = Get3DViewer(pb);
   }
      
   //and tell the QD3DViewer to use it as the source of its data
   if (retVal)
   {
      retVal = (Q3ViewerUseFile( gMyViewer, refNum) == noErr);
      // force a full redraw right now.
      Q3ViewerDraw(gMyViewer);
   }
   // close the file here if it was opened
   if (dataFileOpen)
   {
      FSClose( refNum);
   }
   
   if (retVal)
   {
      // store data in our globals for next call
      gFileOpened = true;
      BlockMoveData( pb->param[0], 
                           &gCurrentFile, 
                           sizeof(FSSpec));
   }
   return retVal;
}

DoWindowClosed
/*********************************************************************
   DoWindowClosed
   User closed a window. If it is ours, release our data.
   Application sends us a pointer to the WindowRecord in param[0].
*********************************************************************/
Boolean DoWindowClosed( PlugInParamPtr pb)
{
   Boolean retVal;
   
   retVal= (gMyWindow == (WindowPtr) pb->param[0]);
   if (retVal)
   {
      Q3ViewerDispose( gMyViewer);
      gMyViewer = NULL;
   }
   return retVal;
}

DoEvent
/*********************************************************************
   DoEvent
   Application got an event? Is it ours?
*********************************************************************/
Boolean DoEvent(PlugInParamPtr pb)
{
   Boolean retVal;
   
   if (gMyViewer)
   {
      retVal = Q3ViewerEvent( gMyViewer, 
                        (EventRecord *) pb->param[0]);
   }
   else
   {
      retVal= false;
   }
   return retVal;
}

DoExit
/*********************************************************************
   DoExit
   We are being closed down. Release our objects.
*********************************************************************/
Boolean DoExit(PlugInParamPtr /*pb*/)
{
   if (gMyViewer)
   {
      Q3ViewerDispose( gMyViewer);
      gMyViewer = NULL;
   }
}

Get3DViewer
/*********************************************************************
   Get3DViewer
   make sure we have a 3DViewer
*********************************************************************/
Boolean Get3DViewer( PlugInParamPtr pb)
{
   CallbackParamBlock    cpb;
      CallBackUPP          callBack; // UPP for the callback
   Rect            wRect;

   // do we have a viewer already ?
   if (gMyViewer == NULL)
   {
      Boolean ok;
      
      // asks the application for a window using the callback
      // create UPP on the heap
      callBack = NewCallBackProc( pb->callback);
      // fill callback parameters
      cpb.message = createWindow;               
      ok = CallCallBackProc( callBack, &cpb);
      // dispose of UPP
      DisposeRoutineDescriptor( callBack);      
   
      // extract the WindowPtr if callback successful and verify the
      // portRect is not empty and it is a color port
      if (ok)
      {
         gMyWindow = (WindowPtr) cpb.param[0];   
         wRect = gMyWindow->portRect;          
         ok = (! (EmptyRect(&wRect))) &&
                  (IsColorPort(gMyWindow));             
      }
      
      // All the checks have been done: create a QD3D Viewer object
      if (ok)
      {
         gMyViewer = Q3ViewerNew( (CGrafPtr) gMyWindow, 
                              &wRect,
                              kQ3ViewerDefault);
      }
   }
   return (gMyViewer != NULL);   
}

IsColorPort
/*********************************************************************
   IsColorPort
   The magic routine to test if a GrafPort is just a CGrafPort
   in disguise
*********************************************************************/
Boolean IsColorPort(GrafPtr port)
{
   return port & ((((CGrafPtr)port)->portVersion &                            (short)0xC000)) == (short)0xC000 : false;
}

SameFile
/**********************************************************************
   SameFile
   Are two FSSpec refering to the same file?
***********************************************************************/
Boolean SameFile ( FSSpec *spec1, FSSpec *spec2 )
{
   return (spec1->vRefNum == spec2->vRefNum) &&
            (spec1->parID != spec2->parID) &&
            EqualString( spec1->name, spec2->name, false, true );
}

The last thing to mention about the import library is the settings for the project: make sure to specify PlugInMain as the main function of the Import library in the PPC Linker preferences panel, and in the PEF preferences, select use #pragma in the topmost popup menu for the export symbols. (CodeWarrior offers other ways to specify which symbols are exported, the point here is to make sure the PlugInMain is indeed exported.) The MPW tool DumpPEF can be invaluable to check which symbols are exported or imported by a PEF container like an import library.

The Accelerated Code Resource Project

The accelerated code resource project is even simpler, at least in its first version. It consists of only two files: stub.c to forward the parameters to the import library entry point and then return the result of the call to the application, and the import library which was just built and which will be used by the linker to set up the imports of our code resource.

In its first version, the code for the accelerated code resource has an almost embarassing simplicity: it just calls the exported function from the import library. (Remember that this simplicity is desired because of the difficulty of debugging accelerated code resources.)

Listing 3: Stub.c (version 1)

// Procedure info for the code resource entry point. This will be used by 
//Code Warrior to set up the routine descriptor in front of the code fragment.
enum {
   codeResourceProcInfo = kPascalStackBased
      | RESULT_SIZE(SIZE_CODE(sizeof(Boolean)))
      | STACK_ROUTINE_PARAMETER(1,
       SIZE_CODE(sizeof(PlugInParamPtr)))
};

// prototype for the import library exported function
extern Boolean PlugInMain( PlugInParamPtr pb );

// symbol used by CodeWarrior when building the accelerated code resource
ProcInfoType __procinfo = codeResourceProcInfo; 

// prototype for the code resource entry point
pascal Boolean main( PlugInParamPtr pb);

main
/************************************************************
   main
   Code Resource entry point
   In this first version, just call the entry point
************************************************************/
pascal Boolean main( PlugInParamPtr pb)
{   
   return PlugInMain( pb);
}

The project generates a file that contains the 'PLUG' accelerated code resource, which is then added to the import library project in the final step of the bootstrapping sequence. From now on, the 'PLUG' resource will be automatically added (like any other resource) to the resource fork of the import library file by the CodeWarrior linker.

Figure 3. The final project for the plug-in. QD3DPlugInStub.rsrc is the file providing the 'PLUG' accelerated code resource.

The plug-in is now ready to use. The application calls the PLUG resource that forwards the request to the import library that can directly call back to the application. Everything works perfectly until we move the plug-in away a directory other than the application directory, such as to a Plug-ins folder. What is happening?

The Need for Manual Load

The problem is that our accelerated code resource is not a full citizen in the MacOS, and the CFM cannot resolve the import in the code resource, namely PlugInMain(). The code resource resides in its host application process and, although the import library is the same physical file as the code resource, it is no longer in the CFM search path. The CFM search path is thoroughly described in [2] (p 3-7). The CFM searches for imported libraries in the directory of the fragment being loaded only (and this is not the case here) if the fragment is being loaded by a call to GetDiskFragment() or GetSharedLibrary(). If this search is not sucessful, the CFM tries to locate the import library in the application file, the application library directory (specified by the application 'cfrg' resource), the application directory, the Extensions folder in the System Folder, the ROM registry and finally, a file and directory registry currently private to the CFM. Placing our code resource-import library hybrid in a plug-in folder effectively removes the import library from the search path. Does it mean our hybrid will have to either clutter the application directory or the Extensions folder? The solution would be to programmatically load the import library. The CFM provides the necessary function: GetDiskFragment() will load a fragment contained in the data fork of a file specified by a FSSpec:

OSErr GetDiskFragment( FSSpecPtr fileSpec, long offset,
                  long length, Str63 fragName,
                  LoadFlags findFlags, 
                  ConnectionID* connID, Ptr* mainAddr,
                  Str255 errName);

The last remaining problem is to get the FSSpec of the file containing the accelerated code resource. If the fragment had been disk based, it could get its own FSSpec courtesy of the CFM by exporting a connection initialization routine. This type of routine is called by the CFM after a fragment has been loaded and prepared and should conform to the following definition:

OSErr MyConnectionInitializationRoutine( InitBlock*
                                connectionInfo);

For disk based fragments, one of the fields in the InitBlock structure is actually the FSSpec of the file containing the code fragment. However, if the fragment is memory based (and this is the case for accelerated code resources), the InitBlock specifies the address and size of the fragment but not the FSSpec of the file. However, since the loading of the plug-in import library is only needed once, using the connection initialization routine to perform this step is convenient.

There is no universal solution to this problem, because it really depends on the run-time environment provided by the application for its plug-ins. The technique described here will be applicable in almost all the cases however, since it relies on minimal assumptions typical of most applications: the resource file of the plug-in is left opened by the application, and the plug-in has either a unique resource ID or a unique name. The loading code will work even if the plug-in is not the current resource file (some applications set the current resource file to themselves after loading plug-ins). The current resource file is not necessarily the plug-in file since the connection routine is executed just before the first call to the main() routine of an accelerated code resource and not when the resource is loaded.

To get the FSSpec of its own file, the code resource will set the current resource file to the top of the resource chain to be sure its file is in the resource chain, look for itself in the resource chain with GetResource() (or GetNamedResource()), obtain its own file reference number with HomeResFile(), and convert the file reference number to a FSSpec with PBGetFCBInfo().

Listing 4: Stub.c (version 2)

#include "PlugInBlock.h"
#include <CodeFragments.h>

// our own identity 
#define kResType 'PLUG'
#define kResId      (1000)
// this structure is the C description of a Mac Resource Map
typedef struct {
   long dataOffset;
   long mapOffset;
   long dataLength;
   long mapLength;
   Handle nextMap;
   short fileRefNum;
   short fileResAttr;
   short typeListOffset;
   short nameListOffset;
} ResourceMap, *ResourceMapPtr, **ResourceMapHandle; 
// exported from the import lib
typedef Boolean (*CFMPlugInEntryPoint) (PlugInParamPtr pb);

enum {
         codeResourceProcInfo = kPascalStackBased
                | RESULT_SIZE(SIZE_CODE(sizeof(Boolean)))
                | STACK_ROUTINE_PARAMETER(1,
                    SIZE_CODE(sizeof(PlugInParamPtr)))
};

// for auto-load
extern Boolean PlugInMain( PlugInParamPtr pb );

// the import library entry point
CFMPlugInEntryPoint gPlugInMain; 

// used by CW for accelerated code resource
ProcInfoType __procinfo = codeResourceProcInfo; 

// prototype for the code resource entry point
pascal Boolean main( PlugInParamPtr pb);

// connection initialization routine and prototype of the utilities
OSErr VerifySharedLibrary( InitBlockPtr initBlock);
static OSErr RefNum2FSSpec( short theRefNum, 
                      FSSpecPtr theSpec);
static short SetResToTop(void);

main
/************************************************************
   main
   Code Resource entry point
   verify our global is set up and call the import library       
   entry point as before
************************************************************/
pascal Boolean main( PlugInParamPtr pb)
{   
   Boolean retVal = false;
   
   // Verify we did resolve the import   
   if (gPlugInMain != 
         (CFMPlugInEntryPoint) kUnresolvedCFragSymbolAddress)
   {
       retVal = (*gPlugInMain) (pb);
   }
   return retVal;
}

VerifySharedLibrary
/************************************************************
   VerifySharedLibrary
   ConnectionInitialization routine that verifies that the import library is 
   loaded, and if not load it programmatically.
************************************************************/
OSErr VerifySharedLibrary( InitBlockPtr /*initBlock*/)
{
   Handle             codeResourceHandle;
   FSSpec             myFSSpec;
   ConnectionID      myConnection;
   Str255            myError;
   short            myRefNum;
   OSErr              myErr = noErr;
   unsigned char      myEmptyString = 0x00;
   if (gPlugInMain == 
         (CFMPlugInEntryPoint) kUnresolvedCFragSymbolAddress)
   {
      // the CFM has not been loaded -> do it ourselves
      // the first part is to find our resource handle
      // so that we get to our ref num. The inMem info from the InitBlock 
      // is useless
      short oldResFile = SetResToTop();
      
      // get our own handle
      codeResourceHandle = GetResource(kResType, kResId);
      if (codeResourceHandle == NULL)
      {
         myErr = ResError();
      }
      // with the handle, get our file refnum
      if (myErr == noErr)
      {
         myRefNum = HomeResFile(codeResourceHandle);
         
         // now get the FSSpec corresponding to the refnum
         // this is much easier than you think   
         myErr = RefNum2FSSpec( myRefNum, &myFSSpec);
      }
      if (myErr == noErr)
      {
         myErr = GetDiskFragment( &myFSSpec, 
                       0,      // offset 0 for fragment 
                       kWholeFork, // whole data fork
                       &myEmptyString, // used for debug
                       kLoadLib, // load it
                       &myConnection, // get the connection
                       (Ptr*)&gPlugInMain, //get address directly
                       myError);
      }      
         
      if (myErr != noErr)
      {
         // just to stay on the safe side
         gPlugInMain = kUnresolvedCFragSymbolAddress; 
      }
      
      // restore current res file to its old value not to confuse the 
      // application
      UseResFile(oldResFile); 
   }
   else
   {
      gPlugInMain = PlugInMain; //Fragment loaded automatically
   }
   return myErr;
}

RefNum2FSSpec
/************************************************************
   RefNum2FSSpec
   given a file refNum, returns the file FSSpec
************************************************************/
   OSErr RefNum2FSSpec( short theRefNum, FSSpecPtr theSpec)
{
   FCBPBRec   paramBlock;
   OSErr         theError;
   Str255      theName;

   paramBlock.ioCompletion   = nil;
   paramBlock.ioNamePtr   = theName;
   paramBlock.ioVRefNum   = 0;
   paramBlock.ioRefNum   = theRefNum;
   paramBlock.ioFCBIndx   = 0;
   theError = PBGetFCBInfo(&paramBlock, false);

   if (theError == noErr) 
   {
      theError = FSMakeFSSpec(paramBlock.ioFCBVRefNum,
                    paramBlock.ioFCBParID,
                    theName, 
                    theSpec);
   }
   return theError;
}

SetResToTop
/*******************************************************
   SetResToTop
   Sets the current resource file to the top of the resource map
   Returns the current resource file before the call.
********************************************************/
short SetResToTop(void)
{
   short topResFile;
   short oldResFile;
   
   // Save the current resource file 
   oldResFile = CurResFile();
   
   // now sets the current Resfile to the top of the resource map
   // using the low-memory global TopMapHndl
   topResFile = (**((ResourceMapHandle)                                     LMGetTopMapHndl())).fileRefNum;
   UseResFile(topResFile);
   return oldResFile;
}

To improve the robustness of the manual loading of the import library, instead of relying on GetDiskFragment() which simply returns a transition vector to the main entry point of the import library, the code could use FindSymbol() to verify that the import library loaded by GetDiskFragment() exports a routine with the expected name.

myErr = FindSymbol( 
         myConnection,         // obtained with GetDiskFragment()
         "\pPlugInMain",         // name of routine to look for
         (Ptr*)&gPlugInMain,  //get address directly
         &symbolType);      // get class of symbol (transition vector, data)

Evidently, calls to FindSymbol() are necessary if the code resource does not call the main entry point (this would happen if the import library implemented the code for several distinct code resources all calling different entry points).

The other modifications to the import library project are the specification of VerifySharedLibrary as the connection initialization routine (Figure 4) and the addition of two libraries: InterfaceLib to link with all the Toolbox routines used, and MWCRuntimeLib to provide the glue to call the transition vectors obtained with GetDiskFragment() or FindSymbol(). A transition vector is a function pointer associated with the value of the Table Of Contents register (RTOC) used to reference global data in the called code fragment ([2] p.1-26). Calling a function located in another code fragment is known as a cross-TOC call, and the MWCRuntimeLib automatically provides the glue to set the TOC register before and after cross-TOC calls. The C programmer just deals with classic C pointers to functions.

Finally, the link to the import library should be set as "weak" so that the CFM does not give up loading and preparing the accelerated code resource if it cannot resolve the import. A weak link gives the connection initialization routine a chance to resolve the imports.

Figure 4. Setting the connection initialization routine of the accelerated code resource.

Conclusion

Adopting the mixed architecture with import libraries for accelerated code resources offers several advantages: the code of the plug-in does not impact the amount of free memory of the application, data can be easily shared between the plug-ins, the developer does not have to deal with the limitations inherent to the use of code resources, and can debug using mainstream source level debuggers. The implementation of these hybrid files is very easy once the stub resource has been built. Most, if not all of the development effort is then spent in the import library code. It is even easy to convert an existing accelerated code resource to use the hybrid architecture. All these advantages should speed up the development of new plug-ins of better quality for all the applications that still do not take explicitly advantage of the CFM. This hybrid design also can be used to develop private or fat safe code resources. (For more information on this type of code resource, see [5].) Finally, packaging the code resource in the same file helps reduce the System Folder clutter.

Even if you do not have a accelerated code resource project in the foreseeable future, the techniques presented here may give new ideas on how to take advantage of the power and versatility of the CFM, one of the most awesome parts of the Macintosh Operating System.

Bibliography and References

  1. Apple Computer Inc. "3D Graphics Programming with QuickDraw 3D", (1995) Addison-Wesley.
  2. Apple Computer Inc. "Inside Macintosh: PowerPC System Software", (1994) Addison-Wesley.
  3. Prouse, Craig. "Technical Note PT35-Stand alone code, ad nauseam", (1990) Apple Computer Inc.
  4. Nichols, Tim. "Standalone code on the PowerPC". (1994) develop 17, Apple Computer Inc.
  5. Zobkiw, Joe. "A fragment of your imagination", (1995) Addison-Wesley.
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

VueScan 9.5.70 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
TextSoap 8.3.2 - Automate tedious text d...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
ForkLift 3.0 - Powerful file manager: FT...
ForkLift is a powerful file manager and ferociously fast FTP client clothed in a clean and versatile UI that offers the combination of absolute simplicity and raw power expected from a well-executed... Read more
Amazon Chime 4.0.5528 - Amazon-based com...
Amazon Chime is a communications service that transforms online meetings with a secure, easy-to-use application that you can trust. Amazon Chime works seamlessly across your devices so that you can... Read more
Sparkle 2.1.0 - $79.99
Sparkle will change your mind if you thought building websites wasn't for you. Sparkle is the intuitive site builder that lets you create sites for your online portfolio, team or band pages, or... Read more
Bookends 12.7.9 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Dash 4.0.0 - Instant search and offline...
Dash is an API documentation browser and code snippet manager. Dash helps you store snippets of code, as well as instantly search and browse documentation for almost any API you might use (for a full... Read more
TrailRunner 3.8.834 - Route planning for...
TrailRunner is the perfect companion for runners, bikers, hikers, and all people wandering under the sky. Plan routes on a geographical map. Import GPS or workout recordings and journalize your... Read more
Dash 4.0.0 - Instant search and offline...
Dash is an API documentation browser and code snippet manager. Dash helps you store snippets of code, as well as instantly search and browse documentation for almost any API you might use (for a full... Read more
Bookends 12.7.9 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more

Fantasy MMORPG MU Origin’s receives a hu...
Developer Webzen are looking to take their highly popular fantasy battler MU Origin to the next level this month, with its most ambitious overhaul yet. The latest update introduces the long sought after Server Arena, new treasure dungeons, and much... | Read more »
RPG Djinn Caster (Games)
RPG Djinn Caster 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: SPECIAL PRICE 38% OFF(USD 7.99 -> USD 4.99)!!!A Fantasy Action RPG of far foreign lands! Summon the Djinns and rise to... | Read more »
Alto's Odyssey gets its first trail...
There's finally video evidence of Alto's Odyssey, the follow up to the 2015 App Store hit, Alto's Adventure. It looks just as soothing and atmospheric as Alto's last outing, but this time players will be journeying to the desert. Whereas Alto's... | Read more »
Last week on Pocket Gamer
What’s going on in the wider world of portable gaming? Each week we ask that question of our sister website Pocket Gamer. The PG team covers iOS gaming, just like 148Apps, but it also strays into the world of Android games and handheld consoles... | Read more »
Pokémon GO Generation 2 evolution guide
At long last, Niantic Labs finally unleashed the Generation 2 Pokémon into the wild. Pokémon GO trainers are scrambling to grab up this new set of 80 Pokémon. There are some special new tricks required to catch all of these new beasties, though.... | Read more »
The best new games we played this week
It feels as though the New Year got off to a creaking start as far as mobile games go, but that's changed over the past few weeks. The last few days alone have seen the debut of a number of wonderful games, so we thought we'd take the time to... | Read more »
Recruit more scallywags and discover new...
Get ready to show off your sea legs all over again in Oceans & Empires’ new grand update, which aims to make the act of rising to the role of seven seas ruler even more fresh and appealing, thanks to a richness of new content on both iOS and... | Read more »
Mage the Ascension: Refuge (Games)
Mage the Ascension: Refuge 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: The groundbreaking roleplaying game Mage: The Ascension manifests in our turbulent present with Refuge, an... | Read more »
Vampire: Prelude (Games)
Vampire: Prelude 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: The classic roleplaying game Vampire: The Masquerade returns to digital games with a Prelude of things to come. Experience a... | Read more »
Digby Forever Guide: How to dig to the d...
Digby Forever is a sparkling homage to arcade classics, and while you may be tiring of the number of arcade games being thrown at you, this endless digger finds many ways to stand out from the rest of the pack. The game manages to be challenging... | Read more »

Price Scanner via MacPrices.net

Apple Certified Refurbished iMacs available f...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more
Apple offering Certified Refurbished Series 1...
Apple is now offering Certified Refurbished Series 1 and Series 2 Apple Watches for 14-16% off MSRP, starting at $229. An Apple one-year warranty is included with each watch. Shipping is free: Series... Read more
1.4GHz Mac mini on sale for $449, save $50
B&H Photo has the 1.4GHz Mac mini on sale for $50 off MSRP including free shipping plus NY sales tax only: - 1.4GHz Mac mini: $449 $50 off MSRP Read more
12-inch Retina MacBooks on sale for $200-$250...
Newegg has the 12″ 1.2GHz Space Gray Retina MacBook (sku MLH82LL/A) on sale for $1349.99 including free shipping. Their price is $250 off MSRP, and it’s the lowest price available for this model.... Read more
13-inch 2.0GHz Space Gray MacBook Pro on sale...
Adorama has the non-Touch Bar 13″ 2.0GHz Space Gray MacBook Pro in stock today for $100 off MSRP. Shipping is free, and Adorama charges NY & NJ sales tax only: - 13″ 2.0GHz MacBook Pro Space Gray... Read more
13-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has select 2016 Apple 13″ Touch Bar MacBook Pros in stock today and up to $100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook... Read more
KSI-1802R SX Disinfect-able Keyboard With Wav...
KSI has unveiled a new, innovative medical keyboard, the KSI-1802R SX, at HIMSS 2017, running February 19-22 in Orlando, Florida. KSI-1802R SX is the only keyboard that combines dual factor... Read more
15-inch Touch Bar MacBook Pros on sale for up...
Amazon has 15″ Touch Bar MacBook Pros in stock today and on sale for up to $150 off MSRP including free shipping: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray: $2720.38 $79 off MSRP - 15″ 2.7GHz... Read more
Apple’s Education discount saves up to $300 o...
Purchase a new Mac or iPad using Apple’s Education Store and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free: -... Read more
13-inch 1.6GHz/256GB MacBook Air on sale for...
Newegg has the 13″ 1.6GHz/256GB MacBook Air (MMGG2LL/A) on sale for $1029.99 including free shipping. Their price is $170 off MSRP, and it’s the lowest price available for this model. Choose Newegg... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Manager *Apple* Systems Administration - Pu...
Req ID 3315BR Position Title Manager, Apple Systems Administration Job Description The Manager of Apple Systems Administration oversees the administration and Read more
*Apple* Technician - nfrastructure (United S...
Let’s Work Together Apple Technician This position is based in Portland, ME Life at nfrastructure At nfrastructure, we understand that our success results from our Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Manager *Apple* Systems Administration - Pu...
Req ID 3315BR Position Title Manager, Apple Systems Administration Job Description The Manager of Apple Systems Administration oversees the administration and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.