March 94 - STANDALONE CODE ON POWERPC
STANDALONE CODE ON POWERPC
TIM NICHOLS
A new format for standalone code in the PowerPC world brings increased functionality
and easier implementation. You'll no doubt want to port existing code resources and
write plug-ins for the new platform. Here you'll learn how to do both while also
retaining or building in the ability to run the standalone code on the old 680x0
platform.
Standalone code is an important part of the Macintosh environment and will continue to
be in the age of the PowerPC processor. Such code takes many different forms and
serves many different purposes. It can serve as a definition function -- such as an
MDEF or a WDEF -- for Macintosh system software, act as a dynamic extension to an
application, or find other, more esoteric uses. In the PowerPC world, it can also be
used to port time-critical portions of an application written in 680x0 code.
This article shows you how to develop and package standalone code modules to run in
both the PowerPC and 680x0 worlds. We start by discussing the differences between
standalone code in the two runtime environments. Then we go through the steps of
compiling, linking, and packaging different types of standalone code, and calling it
from within your application. We look at the following:
• how an application can support a plug-in that contains code in both the
680x0 and PowerPC formats, illustrated by preparing a plug-in sort
algorithm for a simple application called SuperSort
• how to use a similar mechanism to port time-critical portions of an
existing application to the PowerPC platform
• how to make an existing WDEF into a "fat" resource -- one that will work
in either a 680x0 or a PowerPC environment, depending on the machine
executing the code
SuperSort, the plug-in, and the WDEF, along with their source code, are all on this
issue's CD. All the code can run on either the 680x0 or the PowerPC platform,
although you do need MPW to compile it.
This article assumes that you know how to write a standalone code resource for the
680x0 platform and that you have a general grasp of PowerPC technology and runtime
architecture.
THE STORY ON STANDALONE CODE
The format of standalone code has changed in the PowerPC world. Standalone code in the
680x0 world is packaged in resources such as WDEFs and INITs, with limited
functionality and significant restrictions on their implementation. PowerPC
standalone code, on the other hand, can be packaged as a resource or stored in the data
fork of a file and enjoys a more flexible and powerful mechanism for managing global
data and importing and exporting functions based on shared libraries.
STANDALONE CODE IN THE 680X0 WORLD
In the 680x0 world, developers can write two types of code: applications and
standalone code. Applications have special privileges that aren't available to
standalone code. Perhaps the most notable is the ability to easily access global and
static data via the A5 world. The A5 register is maintained by the Process Manager for
each application, to facilitate access to the QuickDraw global data as well as application
global and static data. All references to global and static data by the application are
made via the A5 register.
By contrast, standalone code resources have no A5 world and therefore don't have
access to global or static data. This can limit the functionality of the code. There are
mechanisms to get around this limitation, but they differ from one environment to the
next. THINK C has a mechanism for using A4 as a pointer to global data for standalone
code, while MPW uses special functions and macros to create a pseudo A5 world for the
code resource. Both of these place a burden on developers by forcing them to set up and
restore the appropriate registers before they can access their globals.
STANDALONE CODE IN THE POWERPC WORLD
In the PowerPC world, there's only one type of code, known as acode fragment . A code
fragment is a collection of code and its corresponding data. Fragments can be packaged
in a number of different kinds of containers. A PowerPC application consists of one or
more code fragments packaged in the data fork of the application. Part of the Macintosh
system software consists of code fragments packaged in the Macintosh ROM. Standalone
code is really just another code fragment packaged in a resource or in the data fork of a
file.
Whether standalone code is packaged in a resource or in the data fork depends on how
it's being used. If you're writing a PowerPC version of an existing code resource such
as a WDEF or an XCMD, the standalone code should be packaged in a resource, for
purposes of compatibility. (The existing code only knows to look for code in resources
of a specific type; for example, the Window Manager only looks for window definition
functions in resources of type 'WDEF'.) If, on the other hand, you're developing a new
standalone code module as a plug-in or to accelerate some part of your application, the
standalone code should be stored in the data fork of your application or plug-in file to
fully exploit the PowerPC runtime environment. Code can be loaded rapidly and
efficiently from the data fork of a file without using a large memory footprint, thanks
to the mechanism of file- mapped virtual memory.
Fragments can export symbols (code or data) by name to other fragments and can
import symbols by name from other fragments. Each fragment contains an array of
pointers known as thetable of contents (TOC), which allows the fragment to share
symbols with other fragments and is used to reference the fragment's own global and
static data. Each entry in the TOC is a reference to either an imported symbol from
another fragment or a static data item in the fragment itself. For example, suppose the
code fragment Foo exports a procedure DoThis, contains a single global variable
gMyGlobal, and imports a function DoThat from the shared library Bar. The TOC will
contain an entry for each one of these symbols (DoThis, DoThat, gMyGlobal), and each
entry will point to the address of the corresponding symbol, as shown in Figure 1.
Figure 1 A Fragment's Table of Contents
The R2 register in the PowerPC processor is dedicated to storing the currently active
TOC and thus is sometimes called the RTOC. The RTOC is saved, modified, and restored
each time a new fragment is invoked. Because the TOC allows references to global and
static data, it's analogous to the A5 world in the 680x0 environment. However, it's
important to emphasize that in the 680x0 environment only applications have an A5
world and easy access to global and static data, while in the PowerPC environment, all
fragments have a TOC and easy access to global and static data. So the great thing about
standalone code being handled as a code fragment is that you can have globals in your
WDEFs, INITs, and plug-ins without having to jump through any hoops at all!
Because a fragment can contain symbols from other fragments, these symbols must be
resolved or bound at run time. This preparation is performed by the Code Fragment
Manager. In most cases, such as when a PowerPC application is loaded, this is done
transparently. Standalone code can be automatically prepared by the Mixed Mode
Manager, but the preferred method is to have your application call the Code Fragment
Manager directly. Fortunately, the Code Fragment Manager makes this an easy task, as
we'll see later. Once a fragment has been prepared, the Code Fragment Manager
returns a connection ID to identify the fragment. This connection ID is used when
unloading the fragment, similar to a refNum that's returned when opening a file and
later used to close the file.
The Code Fragment Manager has the ability to resolve symbols by name, so you can
export any routine or data by name and then import that symbol in another fragment.
This allows you to store multiple routines in your fragment, export them, and then
call each routine when necessary by asking the Code Fragment Manager for its address.
This is much nicer than having a dispatch-based, single- entry-point code resource as
we do in the 680x0 environment.
CALLING STANDALONE CODE
At any given time a PowerPC processor-based Macintosh may be executing in the
native PowerPC runtime architecture or in an emulated 680x0 runtime architecture.
The switching between the two runtime environments is transparent and handled by
the Mixed Mode Manager. And thanks to the Mixed Mode Manager, code from one
instruction set can call code from another instruction set, which is just what happens
when a 680x0 application calls a standalone code module written in PowerPC code or a
native PowerPC application calls a standalone code module written in 680x0 code.
So whenever we're running on a PowerPC processor-based Macintosh and our
application calls standalone code, we're presented with an interesting problem. Given a
pointer to standalone code, how do we know what kind of code it points to? In the
680x0 world, a procedure pointer is simply the address of a procedure. But in the
PowerPC environment, a procedure pointer is actually the address of a transition
vector, which in turn contains pointers to the actual routine and the TOC for the
fragment. Figure 2 shows the difference.
To solve this problem, the Mixed Mode Manager creates a generic procedure pointer
known as a UniversalProcPtr (UPP). A UPP can point to one of two things: a 680x0
procedure (in which case the UPP is really just a 680x0 ProcPtr in disguise) or a
routine descriptor (data type RoutineDescriptor). A routine descriptor is a data
structure that describes the instruction set,parameters, and calling convention of the
routine. The Mixed Mode Manager looks at the routine descriptor to determine whether
a mode switch is necessary and, if so, how to perform the switch.
To run in a PowerPC environment, we use a UPP anywhere we would formerly have
passed a ProcPtr, such as in specifying a dialog filter procedure. In the case of 680x0
standalone code (which typically is stored in a resource), we indirectly pass a
ProcPtr, and thus a UPP, to the calling routine via the handle to the resource. For a
PowerPC code resource (or for a "fat" resource), we have to replace this ProcPtr
with a UPP, which points to a routine descriptor describing the routine in our code
resource. Figure 3 compares the forms taken by the three different kinds of code
resources (680x0, PowerPC, and fat).
Now that you have the necessary background information on standalone code, we can
move on to demonstrate how to handle three different types of standalone code: a
universal plug-in module, a module to port time-critical code, and a fat resource.
Figure 2 680x0 and PowerPC Procedure Pointers Compared
Figure 3 Forms of Code Resources Compared
A UNIVERSAL PLUG-IN MODULE
Plug-ins are a popular way for third-party developers to extend the functionality of
an application. To demonstrate how to create and support a universal plug-in module
-- one that will run in either the PowerPC or the 680x0 world -- we'll use the
example of a plug-in module for an application called SuperSort, which you'll find on
this issue's CD.
SuperSort is a simple application that visually sorts data represented as bars of
varying height according to a specified algorithm. SuperSort has two built-in
algorithms -- bubble sort and quick sort -- and can add new algorithms through a
plug-in mechanism. We'll compile and package a shell-sort algorithm into a plug-in
that will work with either the 680x0 or the PowerPC version of SuperSort. The
application will pick the correct version of the plug-in automatically at run time.
EXAMPLE CODE
Below is the code for our plug-in sort routine that implements the shell-sort
algorithm. ShellSort's data parameter is a pointer to the data to be sorted, the size is
the number of elements to be sorted, and the swap parameter is a callback procedure to
SuperSort to animate the sort.
#if powerc
ProcInfoType swapPI = kCStackBased