Macintosh Development

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:

You're already using it (reason by inertia)

The makefiles allow for more control of the build process

The reasons for using CodeWarrior:

Precompiled headers

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.

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:

Lacks MPW's predefined constants (macintosh, applec, etc)

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.

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.

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.

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.

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:

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.

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:

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.

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:

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.

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.

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.

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:

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.

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.

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:

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.

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: