MIT Information Systems

Macintosh Development

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


Porting to the Power Macintosh

Porting to the Power Mac requires some initial decisions. The first one is what development environment to use. There is a PPC compiler for MPW, but now is the chance to switch to CodeWarrior from Metrowerks. The reasons for using MPW for PPC development are few:
  1. You're already using it (reason by inertia)
  2. The makefiles allow for more control of the build process
The reasons for using CodeWarrior:
  1. Precompiled headers
  2. Speed: I compiled commandMgr.c about 8 times using CodeWarrior on the Power Mac in the time MPW took to compile it once on the IIci. Linking is extremely fast compared to MPW.
  3. CodeWarrior is easier to use and has a better editor than MPW. If you want to use all of the MPW tools and scripts use ToolServer. The only MPW command I use regularly is search. CodeWarrior has a multifile search that supports regexps. The only MPW menu items that I use regularly are Build and Save. MW handles both of those well.
outweigh the reasons not to:
  1. Lacks MPW's predefined constants (macintosh, applec, etc)
  2. Can't define constants at compile time (e.g. -d KERBEROS)
This document discusses porting a program written in MPW C to CodeWarrior C 68K and PPC. Most of the examples are derived from TechInfo. The process is composed of two major steps: Porting to CodeWarrior is by far the biggest job. If porting to CodeWarrior takes more work than PowerPC specific changes it may not seem worth it to undertake the effort. However, porting to CodeWarrior requires you to clean up your C code and bring it up to the ANSI standard. CodeWarrior is a stricter compiler than MPW. Hence porting to CodeWarrior is an important process because making the code ANSI standard is one of the requirements for porting to the PowerPC. Making your code stricter will ensure that it behaves as expected and will avoid possible bugs.

CodeWarrior comes with the Universal Header files from Apple. This is a new set of header files that provide access to the toolbox in a way that is compatible with both the 68K and PPC. Converting your program to use the Universal Header files is another step that CodeWarrior makes you do that is ultimately necessary to port your program to the Power Macintosh.

Porting to CodeWarrior

Modifying your code to compile with CodeWarrior 68K requires many of the changes needed to compile with CodeWarrior PPC. The changes can be broken down into several type:

ANSI Compliance

The first changes to make to your code are to make it ANSI compliant. MPW is much more lax about what it allows than CodeWarrior is. You can tell CodeWarrior to give you many types of warnings. If your code is old or was compiled with MPW you may need to add function prototypes. I suggest turning on the "Require Function Prototypes" option in the "Language" section of the preferences. This will require that every function has a prototype. Here is an example of a correct function prototype:

int mk_ticket(KTEXT ktext, char *server_name);

There are a few features to note about this prototype. First it explicity returns int. Always declare the return type. Do not assume that if you don't then the procedure will return int. Many of the procedures in TechInfo that didn't have an explicitly returned type didn't return anything. CodeWarrior complained because they were supposed to return ints but didn't return anything. This was a warning that MPW didn't catch. I changed these to return void. For example, I changed this prototype (and the corresponding function call:

cmd_open_document(void);

to:

void cmd_open_document(void);

This prevents statements such as the following (imaginary) one:

result = cmd_open_document();

The other important feature of the prototype is that it uses new-style argument declarations. By putting KTEXT ktext and char *server_name in the argument list, the compiler can do better type checking and register allocation. It may even uncover errors.

Prototyping all of your functions may cause additional complications. The first argument to mk_ticket is of type KTEXT, which is defined in krb.h. Consequently krb.h must be #included before the function prototype. In TechInfo all of the prototypes are in one header, prototype.h. krb.h must be #included before prototype.h or inside of it. This may uncover other conflicts between newly #included headers or headers and source files.

Besides declaring all of your functions before using them, remove illegal code. CodeWarrior will catch this code. An example of code that CodeWarrior disagrees with is:

(long)select_node = GetWRefCon(window);

An expression with a cast is not an lvalue. However all assignment expressions require an lvalue as the left operand. This expression can be fixed by type casting the result of GetWRefCon to match select_node's type. The proper expression is:

select_node = (Handle)GetWRefCon(window);

Another problem that might occur when you move to CodeWarrior is redefined macros. MPW prior to version 3.3 allowed macros to be redefined. Version 3.3 allows macros to be redefined if they are being redefined to the same thing. CodeWarrior doesn't allow macros to be redefined. TRUE and FALSE are often defined. I wrapped these in #ifndef's to prevent redefinition.

It's quite possible you will encounter other irregularities in the code you are porting. There are some subtle implementation differences between MPW and CodeWarrior.

MPW swaps the ASCII value of '\r' and '\n'. It does this because the Mac uses '\r' to break lines but '\n' is commonly used for new lines in standard C library routines such as printf. CodeWarrior's ANSI libraries translate '\n' as far as I can tell. You could swap all of your '\n's to '\r' but then your code wouldn't work after being compiled by MPW. There are two solutions to this problem. One is to turn on "MPW Newlines" in the "Languages" section of the preferences. The other is to #define macros, such as CR and LF with the ASCII values of '\n' and '\r'. Inside of strings use the octal value (e.g. "foo\012").

Another implementation difference is the size of int. In MPW and CodeWarrior PPC int is 4 bytes. In CodeWarrior 68K it is 2 bytes unless you tell CodeWarrior 68K to use 4 byte ints in the "Processor" section of the preferences. Make sure you tell CodeWarrior that ints are 4 bytes if you depend on this. Ideally you should only use ints when you don't care how big int is and long and short when you do.

Universal Headers

The Universal Headers require some changes to your code. The changes can be broken down into three major parts:

Obsolete Toolbox Calls

Several toolbox calls are obsolete. If you try to compile your program and a prototype is lacking for a toolbox call, it's quite possible that the call is now obsolete. TechInfo used to use GetAppParms to get its name and resource reference number. I had to replace this call with a call to get the low memory global that contains the application's name and a call to CurResFile. The call to CurResFile has to be made at the beginning of the program while the application is still the current resource file. Other obsolete calls are CountAppFiles and GetAppFiles. These calls are needed for System 6 applications but they are obsolete on the PowerPC. To maintain a common code base it is a good idea to remove these from the 68K code. If you really want to use obsolete calls you need to #define OBSOLETE 1. It is #defined as 1 if you are using CodeWarrior 68K, so obsolete calls won't be caught until you try to compile for the PPC unless you modify MacHeaders.c in the MacHeaders directory. There's a line in that file that defines OBSOLETE to be 1 if the compiler is not a PPC compiler. Comment this line out and re-precompile MacHeaders68K to have the obsolete calls caught by the 68K compiler. Other obsolete calls are GetTrapAddress, SetTrapAddress, and ClrAppFiles.

Low Memory Globals

You should not access low memory globals directly on the Power Mac. The Power Mac architecture is different from the 680x0 architecture so there is no guarantee that the low memory globals will even exist. Apple has removed the SysEqu.h header file, which contained the names of the globals, and replaced it with LowMem.h. LowMem.h contains accessor and setter functions for the low memory globals. For example, TechInfo used the WindowList low memory global but was modified to call LMGetWindowList(). If you use low memory globals the compiler will generate errors, first because SysEqu.h can't be found, and, once you remove the #include for it, because it won't recognize the globals. You'll need to #include LowMem.h and replace the globals with LMGet* and LMSet* calls.

Universal Procedure Pointers

The biggest change you will need to make are to ProcPtrs, the procedure pointers that are passed to toolbox calls. For instance, TechInfo calls ModalDialog with a ModalFilterProcPtr. If your program is running on a Power Mac, ModalDialog does not know if the filter procedure is 68K code or PPC code because the Power Mac can execute both. Consequently you cannot simply pass your procedure's pointer because you have to indicate what kind of instruction set it was compiled for. Apple has created a structure called RoutineDescriptor which describes the calling convention for a procedure and also the CPU it was compiled for. A pointer to a routine descriptor is called a Universal Procedure Pointer (UPP). Instead of passing a pointer to your callback routine, you need to pass a UPP. If you are compiling for the 68K a UPP is simply a ProcPtr. If you are compiling for the PPC it is a pointer to a routine descriptor. You do not have to worry about which it will be because that is abstracted away using macros. Always use UPPs and the compiler will do the right thing.

There are several ways to create routine descriptors. A routine descriptor requires about 32 bytes of nonrelocatable memory. If you allocate it in the heap during runtime you might fragment memory. One way to avoid this is to allocate your routine descriptors on the stack as global variables. There is a macro in MixedMode.h that will accomplish this. TechInfo has a procedure called dlog_login that prompts a provider for his/her username and password. It wants to pass the procedure GetIdFilter to ModalDialog. ModalDialog wants a UPP, in this case a UPP of type ModalDialogUPP. Here's how to create the routine descriptor on the stack:

RoutineDescriptor gGetIdFilterRD = BUILD_ROUTINE_DESCRIPTOR(uppModalDialogProcInfo, GetIdFilter);

BUILD_ROUTINE_DESCRIPTOR is simply a macro that expands into a RoutineDescriptor structure.

The other way to create a routine descriptor is dynamically on the heap. You should allocate your routine descriptors at application startup because they are not relocatable. Apple has provided a routine, NewRoutineDescriptor, that takes a procedure, procedure calling information, and the CPU type the procedure was compiled for. This routine returns a UniversalProcPtr, which is a pointer to a routine descriptor. The Universal Headers contain many macros to create specific UPPs. Instead of calling NewRoutineDescriptor, here is how TechInfo creates a UPP for GetIdFilter:

ModalFilterUPP gGetIdFilter;

gGetIdFilterUPP = NewModalFilterProc(GetIdFilter);

The macro calls NewRoutineDescriptor with the correct calling convention and CPU specified. You can look in the appropriate header to find the macro that will create the kind of UPP that you need. Notice that the UPP is a global variable. TechInfo has a function in main.c called InitRoutineDescriptors that is called at startup. InitRoutineDescriptors calls routines in other files, such as dialogMgr.c, that create UPPs for the routines in the respective files. The routine in dialogMgr.c, dlog_init_upps, sets gGetIdFilterUPP as shown above, so gGetIdFilterUPP needs to be global so that it can be used in dlog_login.

Now that the RoutineDescriptor has been created it can be passed to ModalDialog in place of the ModalFilterProcPtr. Here is how to use the RoutineDescriptor:

ModalDialog((ModalFilterUPP)&gGetIdFilterRD, &itemHit);

If you created a UPP, here is how to use it:

ModalDialog(gGetIdFilterUPP, &itemHit);

Sometimes you might not be able to create allocate a UPP dynamically at application startup. For example, when I ported the BSD library, there was no routine that I could count on being called at application startup time so I allocated the UPP local to the procedure that needed it. After I was done with it I disposed of it using the function DisposeRoutineDescriptor, which takes a UPP as its argument:

DisposeRoutineDescriptor(gGetIdFilterUPP);

You have to be careful not to dispose of a UPP prematurely, for example if you pass it to an asynchronous routine.

You cannot directly call a UniversalProcPtr like you would a ProcPtr because a UniversalProcPtr is not actually a pointer to a procedure. You won't usually need to call a UPP yourself since they are mostly used as callback routines, but there are times when you might. TechInfo needs to call one in PascalClikLoop, which is used by TextEdit. The reason why will be covered in the PowerPC section. CallUniversalProc in the Mixed Mode Manager handles mode switches and executes the routine associated with the UPP. CallUniversalProc takes the UPP, information about the calling convention for the procedure, and the arguments to the procedure. As in the case of NewRoutineDescriptor there are macros in the Universal Headers for each specific kind of UPP. TechInfo directly calls the TEClickLoop hook with the line

CallTEClickLoopProc(oldClickLoop, *(doc->docTE));

When you compile a program that doesn't use the Universal Headers in CodeWarrior, CodeWarrior will catch most of the callback routines that need to be replaced with UniversalProcPtrs. One case that will not be caught is when you try to put a userItem into a dialog box using SetDItem. This case will not be caught because you have to typedef the ProcPtr for the userItem to a handle. You still need to pass a UniversalProcPtr instead of a ProcPtr.

CodeWarrior Specifics

CodeWarrior handles these topics differently than MPW:

Precompiled Headers

One reason MPW is so slow is that it has to compile thousands of lines of header files. CodeWarrior doesn't have to do this because it supports precompiled headers. The existence of the MacHeaders68K and MacHeadersPPC files is one of the reasons why CodeWarrior is significantly faster than MPW. MacHeaders* contains many of the commonly used headers so you will no longer need to #include the header files that are covered by MacHeaders*. You may still wish to if you are not using CodeWarrior, for instance if you want the code to compile in CodeWarrior and MPW. In this case you should #ifndef the #include lines out. The constant __MWERKS__ is defined in the CodeWarrior environment. You can either #include MacHeaders* in your code or set it as the "Prefix File" in the "Language" section of the preferences.

In the case of TechInfo I wanted to precompile other headers besides the universal ones. Many of TechInfo's source files #include standard C headers. TechInfo also has its own large headers files that need to be #included by every source file. One difference between MPW and CodeWarrior is that MPW allows constants to be #defined on the command line. MPW also #defines constants that aren't #defined by CodeWarrior. To deal with this, I created a header for TechInfo, TI.includes.pch, that #defines constants, #includes MacHeaders, and #includes other header files. This file is precompiled, and the result, TIFast*, is set to the Prefix File.

It is inconvenient to precompile TI.includes.pch every time I change one of the files that it depends on. CodeWarrior will automatically re-precompile any header that ends in .pch if it is added to the project file. The same .pch file can be used to generate 68K and PPC version of the precompiled header. TI.includes.pch has these lines in it for that purpose:

#ifdef powerc
 #pragma precompile_target "TIFastPPC"
#else
 #pragma precompile_target "TIFast68K"
#endif

Toolbox Glue

MPW has C glue code to ease the use of C strings with toolbox routines, which expect pascal strings. For example, MPW supports the numtostring routine, which is just like NumToString except that it takes a C string. CodeWarrior lacks this routine and a couple others of the same breed, so you will need to rewrite them if they don't already exist.

Assembly Code

MPW has an assembler that takes care of .asm files. The ClickLoop passed to TextEdit is traditionally written in assembly. The procedure usually saves registers, calls the TextEdit's default ClickLoop, calls the program's real ClickLoop, restores the registers, and puts a 1 in register D0. CodeWarrior doesn't assemble .asm files, but it will assemble asm procedures in C source files. Here is TechInfo's AsmClikLoop, which is in textMgr.c:
#if defined(__MWERKS__) && !(define(powerc) || defined(__powerc))

static asm Boolean AsmClikLoop(void)
{
        MOVEM.L    D1-D2/A1,-(SP)    // DO and A0 need not be saved
         ...
        RTS
}

#endif
You only want to assemble this code if you are using CodeWarrior because MPW will not understand it. Since this is 68K code, you don't want it assembled by the PowerPC compiler. In fact, CodeWarrior does not currently support PPC assembly.

Code Resources

You need to create a new project for each code resource. CodeWarrior can handle multisegment resources but I won't go into that because TechInfo doesn't use them. In the preference section "Project" set the Project Type to "Code Resource". Then give the file to put the code resource into, the type of code resource, and its resource ID. You can have CodeWarrior prompt you for the location to save the resource by selecting the "Display Dialog" check box. If you select the "Merge To File" check box the code resource will be added to the file instead of replacing it. TechInfo merges all of its code resources into one file. Make sure the Code Model in the "Processor" preferences is set to "Small." A code resource cannot use the large model. If you cannot fit the code resource into the small model you will need to create a multisegment one.

Unlike MPW, you cannot specify the entrance procedure to the code resource. You have to name it main.

Application Resources

The usual way to create resources for an application being created by MPW is to specify them in a .r file and run Rez on the .r file to create and merge the resource into the application. I create and modify resources using ResEdit, so I have to DeRez my changes and add them to the .r file. It is easier to skip the DeRez step and just merge the resources created or modified by ResEdit into the application.

It is much easier to use resource files in CodeWarrior than it is to use .r files for the reason stated above and because CodeWarrior does not automatically run Rez on .r files. You can simply add a file with resources in it to your project, and the resources will be added to your application when it is built. If you wanted to store your resources in .r files, you would have to use ToolServer, which CodeWarrior can control, to run Rez. If you choose to use a resource file and not use Rez, you will still need a .r file for MPW. Instead of having a .r file that duplicates the resource file, you can create a very simple .r file that will include the resource file.

Power Macintosh Specific Changes

If you have ported your application to CodeWarrior as described above, being sure to use UPPs, then you've done most of the work required to port an application to the PowerPC. There is very little difference between writing a 68K and a PPC program. When writing a PPC program, the biggest problem to worry about is mixed mode switches and that you don't pass PPC code to a procedure expecting 68K code or vice versa. This section will discuss these issues that you should be aware of when porting your CodeWarrior 68K program to CodeWarrior PPC:

Creating a CodeWarrior PPC Project

Once you have a 68K project, you probably don't want to recreate a PPC project. CodeWarrior PPC can open CodeWarrior 68K projects files, so you don't have to recreate it. Once you open your 68K project in CodeWarrior PPC, you still have to make some changes. First of all, the 68K and PPC projects need different libraries to interface the Macintosh toolbox. You will need to remove the 68K libraries from the project and replace them with PPC versions. Instead of MacOS.lib, the PPC project needs MWCRuntime.Lib, InterfaceLib, and MathLib.

You also need to change some of the preferences. You can set a number of PPC specific preferences in the "Processor" panel. The PowerPC likes to have data stored on natural boundaries. A char can start at any address. A short should start at an even address, and a long should start at an address that is an even multiple of 4. While this is not necessary it makes memory access faster. You can set structures to use this alignment in the "Struct Alignment" popup menu. If you do, be careful not to pass a PPC aligned struct to a toolbox call. Toolbox calls were written for the 68K and expect structures aligned for that processor. You don't need to worry about structures from the Universal Headers; those are aligned correctly. If you pass your own you should enclose the structure definition in #pragma options align=mac68k and #pragma options align=reset. The other options in the "Processor" panel let you set the level of optimization.

That is about all there is to porting to the Power Mac unless you have 68K assembly code in your program.

Assembly Code

CodeWarrior 5 doesn't have a PPC assembler. Apple says that you shouldn't need to write any PowerPC assembly code. The idea behind a RISC architecture is that the compiler can do a better job than you, and that you should leave assembly up to the compiler. Assembly code definitely makes it harder to port your application (even though there are only two Macintosh platforms right now). Instead of writing in assembly you should program in C. This not being a perfect world, that's not always as easy as it sounds. Assembly is needed to store values in specific registers. This amount of control isn't available in C. TechInfo has this problem. As stated above, TechInfo passes AsmClikLoop as its ClickLoop procedure. The reason TechInfo uses assembly is that TextEdit expects the ClickLoop procedure to put a 1 in register D0. When you are compiling for the PPC, you have to use a UPP for the ClickLoop. A UPP contains the calling convention for a procedure, including where the result of the procedure should be stored. Consequently assembly code isn't needed because the Mixed Mode Manager will do the right thing.

Code Resources

To create a code resource, set the project type to "Code Resources" in the "Project" panel of the preferences. You also have to select "Expand Uninitialized Data" in the "PEF" panel. In the "Linker" panel, set the "Main" entry point to "main."

Code resources are another case when you don't know if your code is being run on a 68K machine or a PPC one. You could just provide a 68K code resource, but then it wouldn't run at blazing speeds on a Power Mac. To get both version in one package you need to make a safe code resource. A safe code resource has code to detect which kind of machine the code resource is being executed on and runs the correct version. CodeWarrior 5 can't make safe code resources, but later versions should. Until they do you'll have to go through the contortions described here.

TechInfo puts all of its code resources into one file. The 68K version of its CDEF is resource type 'CDFm' and its PPC version is type 'CDFp'. There is a resource template in MixedMode.r (one of Rez's headers) for a resource type 'sdes', a safe code resource. The 'sdes' template contains the code for checking the platform and calling the right version of the code resource. This is part of the .r file that TechInfo uses to create a safe CDEF:

type 'CDEF' as 'sdes';

resource 'CDEF' (400, "Fat CDEF") {
        15280,                                // 68K ProcInfo
        15280,                                // PowerPC ProcInfo
        $$Resource("DEFs", 'CDFm', 400),      // 68K code
        $$Resource("DEFs", 'CDFp', 400)       // PEF container
};
The first two numbers are procedure information for the code resource. Use the ProcInfo project in the CodeWarrior Examples folder to determine the correct value for this field. Then comes the 68K and PPC versions. This code needs to be compiled with Rez, which will create the safe code resource.

Fat Binaries

Now that you have a 68K version of your program, a PPC version of your program, and fat code resources, you probably want to merge them into one and create a fat binary. A fat binary will run native on either a 68K Macintosh or a Power Macintosh because it has both versions of the program. 68K applications don't use the data fork. All of the code is stored in 'CODE' resources. PPC code, which is handled by the Code Fragment Manager, can be stored in either the data or the resource fork. PPC applications are stored in the data fork. To make a fat binary all you have to do is put the PPC program in the data fork, the 68K code in the resource fork, and the program's resources in the resource fork. Both versions of the program will share the same set of application resources.

CodeWarrior makes it easy to create fat binaries. You can include the 68K application and the application resources in the PPC version's project. When CodeWarrior links the PPC version it will copy the resources from the application and resource files, creating a fat binary.

References and Further Reading

Macintosh on RISC SDK has a checklist for porting code to the Power Macintosh.

For information on the PowerPC architecture, the Mixed Mode Manager, the Code Fragment Manager, UniversalProcPtrs, and more, see:

"Making the Leap to PowerPC," by Dave Radcliffe, develop Issue 16.

Inside Macintosh: PowerPC System Software by Apple Computer, Inc. (Addison-Wesley, 1994).

For information on standalone code and fat resources see:

"Standalone Code on PowerPC," by Tim Nichols, develop Issue 17.

To learn how to speed up your Power Mac code see

"Exploiting Graphics Speed on the Power Macintosh," by Konstantin Othmer, Shannon Holland, and Brian Cox, develop Issue 18.

"Balance of Power: Enhancing PowerPC Native Speed," by Dave Evans, develop Issue 18.

And other "Balance of Power" columns in develop Issue 19, develop Issue 20, and develop Issue 21 on memory usage, branch prediction, and PPC assembly.

For information on a wide variety of topics concerning the PowerPC, see the PowerPC feature of MacTech Magazine from February 1994 until at least September 1994.

The source for the entire TechInfo project, including project files and source code may serve as an example of what was talked about in this document. You may also want to read about building TechInfo to see how the fat version of TechInfo is created

Of course, to learn about the real guts of the PowerPC see the PowerPC Technical Library.


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