Macintosh Development |
[Home]
[About Us]
[People]
[Information Systems]
[Kerberos for Macintosh]
[Applications]
[Miscellaneous Documentation]
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.
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 int
s 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 #include
d before the function prototype. In TechInfo
all of the prototypes are in one header, prototype.h. krb.h must be
#include
d before prototype.h or inside of it. This may
uncover other conflicts between newly #include
d 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
int
s in the "Processor" section of the preferences. Make
sure you tell CodeWarrior that int
s are 4 bytes if you
depend on this. Ideally you should only use int
s when you
don't care how big int
is and long
and
short
when you do.
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 #define
d 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
.
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.
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.
#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 #include
d by every
source file. One difference between MPW and CodeWarrior is that MPW
allows constants to be #define
d on the command line. MPW
also #define
s constants that aren't #define
d
by CodeWarrior. To deal with this, I created a header for TechInfo,
TI.includes.pch, that #define
s constants,
#include
s MacHeaders, and #include
s 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
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.
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 } #endifYou 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.
Unlike MPW, you cannot specify the entrance procedure to the code
resource. You have to name it main
.
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.
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.
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 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.
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.
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 $