December 92 - ANOTHER TAKE ON GLOBALS IN STANDALONE CODE
ANOTHER TAKE ON GLOBALS IN STANDALONE CODE
KEITH ROLLIN
While MPW is great for developing applications, it provides little support for
creating standalone code resources such as XCMDs, drivers, and custom window,
control, and menu definition procedures, especially if you have nonstandard needs. Two
roadblocks developers immediately notice are the inability to create more than 32K of
object code and the lack of access to global variables. This article addresses the latter
issue.
The Macintosh Technical Note "Stand-Alone Code,ad nauseam " (formerly #256) does
an admirable job of explaining what standalone code is and discussing the issues
involved in accessing global variables from within it. I'll describe the solution
proposed in that Tech Note later in this article, but you may also want to look over the
Note before reading further.
It's important to realize that the Tech Note discusses just one possible solution to the
problem of using global variables in standalone code. This article presents another
solution, in the form of the StART package included on theDeveloper CD Series disc.
Along the way, I'll talk a bit about what the issues are, describe how users of
Symantec's THINK environments address the problem, recap the solution presented in
the Tech Note, and show how to use MPW to implement a THINK-style solution. I'll also
take a look at the advantages and disadvantages of each approach, allowing you to choose
the right solution for your needs.
Note that the StART package is a solution for MPW users and that it assumes a lot
about how MPW currently works. It's possible that you may not be able to use the
StART package to develop standalone code that uses globals with future versions of
MPW, although code already created with StART will, of course, continue to work.
WHAT IS STANDALONE CODE?
Standalone code is merely executable code that receives little to no runtime support
from the Macintosh Operating System. The advantage of standalone code resources is
that they can be quickly loaded into memory, executed, and dismissed without the
overhead of setting up a full-fledged runtime environment for them. In addition,
standalone code can execute without affecting the currently running application or
relying on it for any services. This makes such resources ideal for easily extending the
system's or your application's functionality. By creating the right kinds of standalone
code resources, you can change how controls or windows appear, or you can
dynamically extend the capabilities of your application.
Table 1 shows a list of the most common system- and application-defined standalone
code resources.
Table 1Kinds of Standalone Code Resources
Resource Type Resource Function
ADBS* ADB device driver
adev* AppleTalk link access protocol
boot Boot blocks
CACH System RAM cache code
CDEF* Custom control definition
cdev* Control panel device
dcmd* Debugger extension
dcmp Resource decompressor
DRVR* Device driver
FKEY* Function key
FMTR 3.5-inch disk formatting
INIT* System extension
itl2 Localized sorting routines
itl4 Localized time/date routines
LDEF* Custom list display definition
MBDF* Custom menu bar definition
MDEF* Custom menu definition
mntr* Monitors control panel extension
PACK System package
PDEF* Printer driver
PTCH System patches
ptch System patches
rdev* Chooser device
ROvr ROM resource override
RSSC* Resource editor for ResEdit
SERD Serial driver
snth* Sound Manager synthesizer
WDEF* Custom window definition
XCMD* HyperCard external command
XFCN* HyperCard external function
Note: Items marked with an asterisk are ones that you might create for your own
application, extension, driver, or whatever. The rest are reserved for the system.
Standalone code differs from the executable code that makes up an application, which
has a rich environment set up for it by the Segment Loader. Let's take a look at an
application's runtime environment so that we can better understand the limitations we
must overcome to implement standalone code.
An application runs in a section of memory referred to as its partition. Figure 1 shows
the layout of an application partition. A partition consists of three major sections. At
the top of the partition is the application'sA5 world , consisting of the application's
global variables, the jump table used for intersegment function calls, and 32 bytes of
application parameters (see "Application Parameters"). This area of memory is
called the A5 world because the microprocessor's A5 register points into this data and
is used for all access to it. Immediately below the A5 world is thestack , the area of
memory used to contain local variables and return addresses. The stack grows
downward toward theheap , which occupies the rest of the partition. The heap is used
for all dynamic memory allocation, such as blocks created by NewHandle and NewPtr.
Everything we see in Figure 1 -- the heap (with a valid zone header and trailer), the
stack, and the filled-out global variables and initialized jump table -- is created by
the Segment Loader when an application is launched.
Figure 1 An Application Partition
This is the application's domain, and none shall trespass against it. And therein lies the
conflict between applications and standalone code: Executing code needs to use the A5
register to access its global variables, but an application's use of A5 prevents any
standalone code from using it with impunity. Additionally, the A5 world is created by
the Segment Loader when an application is launched. Since standalone code is not
"launched" (instead, it's usually just loaded into memory and JSRed to), it doesn't get
an A5 world, even if A5 were available. We must solve these two problems
-- the contention for A5 and the need to set up some sort of global variable space -- in
order to use globals in standalone code.
APPLICATION PARAMETERS
Not much is known about the mysterious 32 bytes directly above A5 known as
application parameters. Figures 9 and 10 on pages 19 and 21 of Inside Macintosh
Volume II indicate their existence, but the description simply says that "they're
reserved for use by the system." We know that the first four bytes contain a pointer
into the QuickDraw globals, but that's about it. Some MPW glue routines use some of
the other bytes, but that use is undocumented. In any case, the application parameters
seem pretty important. As you'll see later, we make sure our standalone code
resources support them.
THE THINK SOLUTION
For years, users of THINK C and THINK Pascal have been able to use global variables in
their CDEFs, LDEFs, drivers, and other types of standalone code. THINK has solved the
problem of A5 contention by compiling standalone code to use the A4 register for
accessing globals, leaving A5 untouched. Their solution to the need to set up global
variable space is simply to attach the globals to the end of the standalone code, again
leaving the application's A5 world untouched.
Figure 2 shows how standalone code created by a THINK compiler looks, both on disk
and in memory. If the code was created with the C compiler, which allows
preinitialized global variables, the global variable section contains the initial values.
If the code was generated by the Pascal compiler, which sets all global variables to
zero, the entire global section simply consists of a bunch of zeros (kind of like some
guys I used to know in high school).
This is in contrast to the way globals are stored on disk for applications. MPW, for
instance, uses a compressed data format to represent an application's globals on disk.
When the application is launched, a small bit of initialization code is executed to read
the globals from disk, expand them, and write them into the application global variable
space in its A5 world.
Standalone code created by a THINK compiler accesses global variables by using
A4-relative instructions. Because the use of the A4 register is ungoverned, such
standalone code must manually set up A4 so that it can be used to reference its global
variables. This setup is done by some macros provided by the THINK headers:
RememberA0 and SetupA4. (It's called RememberA0, and not RememberA4, because
the macro has to store the value in the A0 register temporarily.) When the standalone
code is finished and is about to return to its caller, it must call RestoreA4 to restore
the value that was in A4 before the standalone code was called.
Figure 2 Format of a Standalone Code Resource Created by a THINK Compiler
The solution provided by THINK offers many advantages:
• It's simple to use. Making sure you surround the entry point of your
standalone code with the appropriate macros is easy, and the macros don't
require any tricky parameters. Just type them in and you're done.
• The THINK development systems automatically insert a little bit of magic
code at the beginning of standalone code resources that make the setting up of
A4 as transparent as possible.
• THINK's use of A4 means that A5 is totally undisturbed, and hence A5
continues to point to a valid A5 world with, presumably, an initialized set of
QuickDraw globals. This means that standalone code can make Toolbox calls
without a second thought (or even much of a first thought, for that matter).
• Because the globals are attached to the standalone code, when the memory
allocated to the standalone code resource is disposed of (for example, when the
process that loaded it calls ReleaseResource on the segment), the globals are
removed as well.
There are at least three disadvantages to THINK's approach, however:
• Since A4 is now pulling duty as the global variable reference base, fewer
registers are available for calculating expressions, caching pointers, and so
on. This means that the code generated is less efficient than if A5 were used for
referencing globals.
• The globals are stored on disk in an uncompressed format, a fact you
should be aware of before cavalierly declaring those empty 20K arrays.
• The resources holding the standalone code must not be marked as
purgeable, or the global variables will be set back to their original values
when the resource is reloaded.
A fourth disadvantage could be that the combined size of the executable code and the
global variables must be less than 32K. However, this is somewhat ameliorated by
THINK's support of multisegmented standalone code.
THE TECH NOTE SOLUTION
Users of THINK development systems have their solution for accessing global
variables in standalone code. MPW users, however, don't have an immediately obvious
solution. First, MPW's compilers don't have the option of specifying that A4 should be
used to access global variables. Second, the MPW linker is written to create a
compressed block of data representing the global variables and to place that block of
data off in its own segment. Because A4 can't be used to access globals, and because the
globals aren't attached to the end of the standalone code resource, MPW users don't
have the slick solution that THINK users do.
A possible alternative was presented to MPW users a couple of years ago with the
publication of the Technical Note "Stand-Alone Code,ad nauseam ." Let's take a quick
look at that approach, and then compare it with THINK's solution.
Let's start by examining the format of a simple application, shown in Figure 3. This is
the format that MPW is designed to create, with any deviance from the standard
formula being cumbersome to handle.
This application has three segments. CODE 0 contains the information used by the
Segment Loader to create the jump table, the upper part of an application's A5 world.
CODE 1 contains executable code, and usually contains the application's entry point.
CODE 2 contains the compressed data used to initialize the global variable section of the
application's A5 world, along with a little bit of executable code that does the actual
decompressing. This decompression code is automatically called by some runtime setup
routines linked in with the application. The purpose of the call to
UnloadSeg(@_DataInit) in MPW programs is to unload the decompression code along
with the compressed data that's no longer needed.
The solution proposed in the Tech Note is to use a linker option that combines
segments 1 and 2. At the same time, the Note provides a couple of utility routines that
create a buffer to hold the global variables and that decompress the variables into the
buffer. Figure 4 shows what standalone code looks like when it's running in memory.