Diet For Fats
Volume Number: 11
Issue Number: 10
Column Tag: Powering Up
A Diet For Your Fat Applications 
How to create fat applications that can strip their own unneeded code.
By Blake Ward, Ph.D., Idaho Falls, Idaho
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
With well over a million Power Macintoshes sold, and no sign of slowing sales, it has
become critical to provide a PowerPC-native version of every application you write.
However, with a huge installed base of 680x0-based Macintoshes, you’ll also need to
ship a 680x0-native version of your application for the foreseeable future. This
presents a problem for the developer - how do you give customers a convenient choice
between the two versions without forcing them to live with the disk space overhead of
both.
Large complex commercial applications that require multiple floppies and an
installer can include separate 680x0 and PowerPC-native versions on the floppies and
automatically install the proper version. This is a relatively clean solution that avoids
the disk space overhead of providing both versions, but it still has it’s drawbacks. If
they are installing your application on an external hard disk, this approach can lead to
confusion and frustration if they move that hard disk to another Macintosh and discover
that your application either runs very slowly, or worse yet will not run at all. The
installer-based solution is also not always ideal for shareware or freeware
applications. Since these applications are normally sold for little or nothing and are
distributed over the Internet, the added complexity, overhead and cost of an installer
may not be an option.
One simple solution to the problem of mismatched machines and programs is the
“fat application”. A fat application is a single application file that includes both
680x0 and PowerPC-native versions of the application. When you launch a fat
application, the system figures out which version of the application to execute. Fat
applications are convenient for the end user since they don’t have to understand or
worry about the type of Macintosh they have - the application will do the right thing.
Fat applications do have one serious drawback though. Users with only one type of
Macintosh and no plans to buy the other pay a permanent disk space penalty for the
much larger file that essentially contains two copies of the application. There are
simple utilities that will strip the unneeded code from an application, but most users
aren’t likely to have them, and you certainly don’t want to ship another utility with
your product. The ideal solution would really be a fat application that knows how to
strip out its own unneeded code...
This article describes a simple technique for creating self-stripping fat
applications. Using the supplied source code (SlimApp), in a few hours you’ll be able
to modify your fat application so users may click a button and strip off the code
unneeded for the Macintosh they’re running on. The solution described here works
equally well whether you’re writing in C or C++ and whether you’re using no
framework or a framework like MacApp, PowerPlant or Sprocket. I’ve even included a
sample fat application and project files for CodeWarrior to show you how it’s done and
suggest a friendly user interface for this new feature. It should also work with little
or no change in other development environments.
Fat Binaries
Before jumping into an explanation of how we’ll strip a fat application, I’ll begin with
a begin with a brief description of what a fat application looks like on the inside. If
you’re an old pro with fat applications, just skip ahead to the next section.
Figure 1 shows the organization of a traditional Macintosh application. The
application file has two or more ‘CODE’ resources containing the 680x0 instructions
for the application, an assortment of ‘DLOG’, ‘MENU’, etc. resources, a ‘SIZE’ resource
and no data fork. Depending on the development environment that created the
application, it might also contain a ‘DATA’ resource (that holds initial values for the
application’s globals). Even if your application contains only one segment, there will
still be two ‘CODE’ resources since the first one (ID = 0) is actually the jump table
for the application.
Figure 1. A Typical 680x0 Application
A typical PowerPC-native application has the organization shown in Figure 2.
The same ‘DLOG’, ‘MENU’, etc. resources are present, but there is also a ‘cfrg’
resource and there are no ‘CODE’ resources. The actual PowerPC instructions are
stored in the data fork of the file. The important thing to notice is that the executable
code in each version of the application is stored in a different location, but non-code
resources are identical in both PowerPC and 680x0 versions. So we can create a fat
application by essentially just merging the 680x0 and PowerPC versions of the
application.
Figure 2. A Typical PowerPC-Native Application
When your application launches, the System can take advantage of the fact that
the two code types are stored in separate locations. If your application is launched on a
680x0-based Macintosh that knows nothing at all about fat applications, it works the
same as it always did - the data fork and extra resources are simply ignored. When
your application is launched on a Power Macintosh, the new Process Manager on these
systems first looks for a ‘cfrg’ resource. If one is present, it is used to find and load
the PowerPC instructions from the data fork, and the old ‘CODE’ resources are simply
ignored. If no ‘cfrg’ resource is present, then the Process Manager just falls back on
the old way of doing things, looks for the necessary ‘CODE’ resources and runs the
680x0 code in them using the Power Macintosh’s built-in 68LC040 emulator. The
‘DATA’ resource (if present) is only used by the 680x0 version of your application,
the PowerPC-native version uses the Code Fragment Manager which stores each code
fragment’s globals within the fragment.
Stripping Unneeded Code
Given the fat application organization just described, the process of stripping away
unnecessary code to reduce an application’s file size is fairly obvious:
• If the application will be used on a 680x0-based Macintosh, we can safely
eliminate the data fork of the application file since the old Process Manager
doesn’t even expect it to be there. The ‘cfrg’ resource is also no longer needed.
In fact, since the stripped application could be run on a Power Macintosh some
time in the future, we have to get rid of the ‘cfrg’ resource or the new Process
Manager will see it and assume that there’s some PowerPC code in the empty data
fork.
• If the application will be used on a Power Macintosh, the ‘CODE’ resources and
the ‘DATA’ resource are going to be ignored, so we can safely eliminate them.
Unfortunately, after removing the ‘CODE’ resources, we end up with an
application that will only run on a Power Macintosh. If it is ever moved to
680x0-base Macintosh and launched, the Finder will report a resource not found
error! Since this isn’t very user friendly, we will replace the application’s
‘CODE’ resources with a tiny stub application that will warn the user that they
have the wrong version and quit gracefully.
From this description, it’s obvious how a utility to strip unnecessary code from
an application would be written. It’s a little less obvious how we write an application
that can strip out its own unneeded code. However, with one simple trick, conditional
compilation, we can actually implement internal code stripping without worrying
about yanking running code out from under ourselves, and even without having to
explicitly figure out which processor we’re running on:
// Note that for the sake of brevity all of the error checking and some of the
// setup code and comments have been removed from the listings in this
// article. See the file “SlimApp.c” for all the details...
OSErr StripFatApplication(void)
short int currResFork, applicationResourceFork;
// Save away the current resource fork, make the application’s
// resource fork current
currResFork = CurResFile();
applicationResourceFork = GetApplicationResourceFork();
UseResFile(applicationResourceFork);
// Get the application’s file name
// Removed for brevity...
// Strip away the unneeded code
err = StripUnneededCode(applicationResourceFork,
appFileVRefNum, appFileDirID, appFileName);
// If we successfully stripped the unneeded code, we also want to try to
// change the application’s name and it’s long version string so that
// the user can tell months from now which version he/she has.
if (err == noErr)
RenameSlimApplication(appFileVRefNum, appFileDirID,
appFileName);
UseResFile(currResFork);
return err;
}
#ifdef powerc
// This version of the function will only be compiled into the PowerPC version
// of the application. Therefore if this PowerPC code is running we can safely
// remove the 680x0 code since it can’t possibly be in use.
OSErr StripUnneededCode(short int appResFork,
short int /*appVRefNum*/, short int /*appDirID*/,
Handle resourceHandle;
// Remove all of the ‘CODE’ resources from the application
n = Count1Resources('CODE');
SetResLoad(false);
resourceHandle = Get1IndResource('CODE', 1);
// Code resources start out protected, so we have to clear the
// protected flag before they can be removed
SetResAttrs(resourceHandle,
GetResAttrs(resourceHandle) & ~resProtected);
RemoveResource(resourceHandle);
DisposeHandle(resourceHandle);
}
// Do the same for the DATA resource if it exists
resourceHandle = Get1Resource('DATA', 0);
if ((err = ResError()) == noErr && resourceHandle) {
SetResAttrs(resourceHandle,
GetResAttrs(resourceHandle) & ~resProtected);
RemoveResource(resourceHandle);
DisposeHandle(resourceHandle);
}
SetResLoad(true);
// OK, now we want to move our tiny 68K stub into place so that this
// application will still run long enough to warn the user if ever moved to
// a 68K machine.
// It consists of two code resources and a new DATA resource that we stored
// using different resource types in the SlimApp.rsrc file.
resourceHandle = Get1Resource(kStubCODEType, kStubCodeID);
SetResAttrs(resourceHandle,
GetResAttrs(resourceHandle) & ~resProtected);
RemoveResource(resourceHandle);
AddResource(resourceHandle,'CODE',0,"\p");
WriteResource(resourceHandle);
ReleaseResource(resourceHandle);
resourceHandle = Get1Resource(kStubCODEType, kStubCodeID + 1);
SetResAttrs(resourceHandle,
GetResAttrs(resourceHandle) & ~resProtected);
RemoveResource(resourceHandle);
AddResource(resourceHandle,'CODE',1,"\p");
WriteResource(resourceHandle);
ReleaseResource(resourceHandle);
// Move our DATA resource that goes with the code resources we just moved
resourceHandle = Get1Resource(kStubDATAType, kStubDataID);
SetResAttrs(resourceHandle,
GetResAttrs(resourceHandle) & ~resProtected);
RemoveResource(resourceHandle);
AddResource(resourceHandle,'DATA',0,"\p");
WriteResource(resourceHandle);
ReleaseResource(resourceHandle);
// Write all of the changes
UpdateResFile(appResFork);
return noErr;
}
#else
// This version of the function will only be compiled into the 680x0 version
// of the application. Therefore if this 680x0 code is running we can safely
// remove the data fork and ‘cfrg’ resources since they can’t possibly be in use.
OSErr StripUnneededCode(short int appResFork,
short int appVRefNum, short int appDirID,
StringPtr appFileName)
short int n, refNum;
Handle resourceHandle;
// First, remove any ‘cfrg’ resources in the application resource fork
// If we don’t get rid of these and someone runs the application on a
// PowerPC, the finder will think there’s native PowerPC code available
// and won’t emulate the 68K version. There should be only one, but
// let’s be general.