Channels ▼
RSS

Parallel

Porting to the PowerMac

Source Code Accompanies This Article. Download It Now.


SP95: Porting to the PowerMac

Paul is a staff engineer with Symantec's Development Tools Group and works on Macintosh and Windows development tools. He can be contacted at pkaplan@symantec.com.


Apple's new generation of Macs is based on the Motorola PowerPC RISC processor. The PowerMac offers extremely high performance for applications that are compiled and linked for it. However, to preserve the investment users may have in existing software, the PowerMac supports legacy 68K applications. This support is accomplished through software emulation of the 68K instruction set and operating-system support for the 68K run-time model. In addition, new and old code (as well as run-time architectures) can be mixed within an application. Apple developed the PowerMac OS this way--some of System 7.5 is still 68K code.

In this article, I'll describe the main similarities and differences between the old and new OSs and the process of porting Macintosh 68K applications to the PowerMac. I'll also present an application that illustrates using code resources to mix new and old code within an application.

68K versus PowerMac

On a PowerMac, two operating systems coexist in parallel --the original 68K system and the new PowerMac system. They run on top of a "nanokernel," which provides the lowest-level services such as memory management and interrupt handling. The magic of coexisting 68K and PowerPC software is worked by the Mixed Mode Manager.

When an application is launched, the PowerMac OS looks for the special Code Fragment Resource, type cfrg, which specifies a PowerMac application. If a valid cfrg resource exists, the application is handed to the Code Fragment Manager (CFM). This subsystem manages the loading and execution of applications and shared libraries. In addition to handling the default load format, the CFM allows the use of custom loaders. A 68K application has no cfrg resource and is therefore handed to the 68K Segment Manager.

After an application has been launched as either 68K or PowerMac, it can switch modes while running. To switch modes and run unmodified, 68K applications call the Mixed Mode Manager implicitly; PowerMac applications can call it implicitly or explicitly.

In order to run 68K applications, the PowerMac OS has retained a number of components of the 68K OS. In fact, the PowerMac toolbox calls are a superset of the 68K system. The file system is the same, so "well-behaved" applications can be ported with little more than recompiling and linking--the development system will take care of run-time details. The System 7 MacOS, on the other hand, retains a single address space for all running applications, and the multitasking model is still cooperative and non-preemptive. Future releases of the MacOS, beginning with System 8 (code named "Copland") will provide multiple, virtual address space, preemptive multitasking, memory-mapped I/O, and object-oriented user-interface components.

The run-time model for PowerMac applications is completely new. Now, only one code and one data segment are required, and the segment manager is no longer used.

The code segment has no relocations, which makes it sharable, and all the relocations are in the data segment. Each application has a Table Of Contents (TOC) that serves the same function as the 68K "A5 world" and greatly simplifies access to global data. The TOC is created by your development system and is transparent to C or C++ code. Also, the new OS supports, and depends heavily on, shared libraries. In fact, the PowerMac toolbox is a shared library. Finally, the application file format has been completely reorganized.

Porting Your Application

Porting to the PowerPC can be as simple as recompiling if your source code meets the requirements listed in the next few paragraphs. For example, Symantec C++ 8.0 automatically converts your existing 7.0 (68K) project; recompile and link it, and it's ready to go. On the other hand, legacy applications that take shortcuts to system features will need some porting work.

The first step in porting any application is to ensure that your code runs under 68K System 7. Such an application should use only "32-bit clean" addresses. Older Mac applications sometimes used the high byte of an address for purposes other than the address. PowerPC addresses use all 32 bits. In compiling your code, use ANSI C or C++, which will force stronger type checking and function prototypes. Also, compile with Apple's Universal Headers, which are shipped with your development system. Universal Headers are appropriate for both 68K and PowerPC applications and will make your code portable between them. In addition, either rewrite inline assembly in C, or place the inline code in separate assembler files. If you insist on keeping the 68K code, it should be isolated in a separate code resource.

Don't make assumptions about registers, especially passing parameters, as they are all different. And try to use data types with 4-byte alignment. Although the PowerPC processor allows alignment anywhere, 4-byte alignment produces more- efficient code. However, if you're writing structures to a file, using 4-byte alignment can waste disk space.

Beyond these steps, you can use #pragmas to force 68K alignment where it is necessary for toolbox routines. Check that the alignment is correct when reading data from an existing disk file. Also, use int and long data types. On the PowerPC, int and long are 32 bits, and short is 16 bits. The 32-bit integer is the most efficient data type.

Use the double data type for floating-point variables. The PowerPC FPU supports only the IEEE 4-byte (float) and 8-byte (double) floating-point formats. Double is more efficient. The 10- and 12-byte doubles used on 68K are not supported by the processor. Long doubles are supported with two doubles. (Note that the Symantec compiler does not support long doubles.) Check all #pragmas and dependencies on #defines to ensure they still have meaning in the new environment. Do not put data in code. This would affect pipelined-instruction performance. And if you have Pascal code, convert it to C either by hand or with the MPW p2c Pascal-to-C converter (available on Apple's ETO #17 CD-ROM).

When porting the system interface portion of your application, you should generally use system calls instead of accessing the hardware directly. In addition, convert callbacks to universal procedure pointers. These are available in the Universal Headers. If you're passing a callback procedure's address to the operating system, you must create a UniversalProcPtr with the NewRoutineDescriptor function (the actual data structure that describes the function is called a "routine descriptor"). You need to use UniversalProcPtrs because the OS makes no assumption about the callback's architecture. Strictly speaking, routine descriptors are not required for 68K builds (they are compiled into addresses), but using them will make your code completely portable between the two environments.

Another thing to watch for is direct access of low memory. Don't do it! Rather, use the LMSetxxx and LMGetxxx calls in LowMem.h. Finally, don't explicitly use the 68K run-time model. The 68K run-time-specific calls are not supported. For example, a call to the Segment Manager would return with no action.

Linking Your PowerMac Application

Your linker will create a "fragment," which is the atomic load unit and contains code and static data. Fragments are managed by the CFM. Most PowerMac applications and shared libraries use the Preferred Executable Format (PEF) to house fragments. PEF specifies the file header, segments for code and data, import- and export-symbol tables, and relocations. Normally, the application resides in the data fork of its file, although fragments can be resources as well. The linker in your development system will handle the details of fragments and the PEF.

Your linker should support the xcoff format, which is an extension of the coff format found in UNIX. This is important because the only stub libraries Apple supplies to link to the toolbox and shared-library extensions such as the Drag Manager are in xcoff format. The stubs are supplied with your development system. The xcoff format can also be used to link third-party static libraries and object modules from a single translation unit. Normally, the Symantec development environment skips the step of writing object files; the compiler passes them directly to the linker in memory.

Dividing applications into shared libraries will make your code reusable and smaller by eliminating redundantly loaded code. Your development environment will help you create and manage shared libraries.

Under the Application Hood

As mentioned, the run-time model of an application running on the PowerMac OS is quite different from that of the 68K. The PowerMac run-time model has one code and one data segment, which are normally loaded in memory. The code segment is read only, which makes it suitable to run in ROM, but unsuitable to store writable data. Code and data elements may be exported from the fragment, which means their symbols are made public and may be linked dynamically. With the Symantec environment, symbols are exported with a #pragma.

Within the data segment resides the TOC, which is like a personal address book. It provides linkage to symbols inside and outside the fragment. The TOC has linkage to imported routines, imported data, global variables, and the pool (or pools) of static variables. When loading the application and its shared libraries, CFM resolves imported symbols and fills in the appropriate TOC entries. The TOC is 64 KB, so there is a maximum of 16K TOC entries. Your development system will warn you of a TOC overflow.

Applications should have a main() entry point and may additionally have user-initialization and termination routines. CFM will call the main() entry point of an application after it is loaded. CFM may also call an initialization routine as part of loading the fragment, and it may call a termination routine when it unloads the fragment. Your development system will help you define these entry points.

Shared-Library Details

Although common in UNIX, shared libraries are probably best known as DLLs in Microsoft Windows. Originally, shared libraries were available as an add-on to older MacOS versions with Apple Shared Library Manager (ASLM), but they are now a standard feature and are in common use on the PowerMac. Shared libraries are similar to applications. The main differences are that the file type for a shared library is shlb, not APPL, and that there is no main() entry point. Initialization and termination routines are allowed.

When the PowerMac system starts up, its shared libraries are registered with CFM and made available to all calling applications. Other shared libraries can be loaded and called at application startup if specified in the PEF file, or loaded on request by the application. Shared libraries can be loaded automatically by specifying them as import libraries to your development environment. Your linker will resolve external symbols to a shared library as though they were part of a statically linked library. However, the linker knows they've been imported and will put them in the import list for the appropriate library. As CFM loads your application, it will also attempt to load shared libraries specified by the application.

Shared libraries can also be loaded explicitly with the toolbox call GetDiskFragment. In this case, imports should be specified as "weak" so the linker won't be unhappy with the unresolved references. If you load a shared library explicitly, your code should be able to handle a failed load or an unresolved import (which will have a null address at run time). Shared libraries also have version capabilities. CFM checks the version number of a shared library against the version number required by the application and fails on load if it is not compatible. Version numbers can be specified by your development system.

Code Resource Examples

MacOS System 7 code resources such as CDEF and MDEF do not need to be immediately ported to PowerPC. However, there are performance penalties for mixed-mode switching and for running 68K-emulated code. If the performance of a code resource is critical, you should convert that resource to a native, or "accelerated," resource.

To illustrate the process of gradually porting to the PowerMac, I've included a sample project and the required modifications. Listings One and Two show the project source files from a 68K program that calls a 68K resource. This project is a simple application that creates a window, has a standard event loop, and calls the main() routine in the code resource to handle the Update Event. The examples don't use any C++ features, although they were compiled with the Symantec C++ compiler. The InitToolboxStuff() and MouseDownProc() routines are standard Mac idioms and aren't shown. Also, the error checking that would be in commercial-grade code is omitted.

The first modification is the same project ported to the PowerMac. Note that the code-resource routine is still 68K and therefore unchanged. The main-project routine (see Listing Three) requires a few changes to call the code resource through the Mixed Mode Manager. Note the use of the Toolbox routine CallUniversalProc(), which has a varargs parameter list, and the two required parameters, ProcInfoType and UniversalProcPtr. ProcInfoType has been initialized to describe the interface of the routine so that CallUniversalProc() will use the parameters correctly.

The second modification illustrates the changes required to port the resource to the PowerMac. This time, the main project routine has not changed because it was ported in the first modification. Listing Four illustrates the accelerated resource code. There are new calls to __cplusrsrcinit() and __cplusterm(); the calls to RememberA0(), SetupA4(), and RestoreA4() have been deleted.

Normal, nonresource applications always follow the main(argc, argv) convention. The standard run-time library contains hidden code to set up any arguments to main(), and initialize static constructors and destructors. Code resources, by tradition, do not necessarily conform to an entry-point standard.

The Symantec solution for code resources in C++ requires explicit calls to the run-time routines __cplusrsrcinit() and __cplusterm() within the main() routine of the code resource. The run-time routines call any static constructors and destructors, and make the QuickDraw globals available to the code resource. Code resources also require routine descriptors, which play a similar role to the ProcInfoType parameter used in CallUniversalProc.

Another feature of the PowerMac is support for a "fat application"--a single Mac app that contains a 68K version in the resource fork and a PowerMac version in the data fork. Many of the resources, such as menus and icons, can be directly shared. With a little work, code resources can be shared as well. A fat application is backward compatible with 68K, System-7 machines. Although they take up more disk space, fat applications neatly solve the packaging problem for some vendors.

Conclusion

Porting standard applications from 68K to PowerPC is relatively simple. The tools you have to work with--the Mixed Mode Manager, CFM, and your development system--will allow you to gradually port your application, develop an application that will run on both the PowerMac and 68K systems, and create an application exclusively for the PowerMac.

Acknowledgments

I'd like to thank Jim Laskey, Yuen Li, John Micco, and Susan Rona, all from Symantec, for their help with this article.

Listing One

// Macintosh application to create a very simple window and do basic 
// event handling. Paul Kaplan - Symantec Corporation
#include "InitToolboxStuff.h"
#include "MouseDownProc.h"
#include "UpdateWinProc.h"
#define WIN_RESID 128
#define CODE_RESID 128
void main()
    {
    static WindowPtr theWindow, foundWindow;
    static EventRecord theEvent;
    Handle UpdateWinProcHandle;
    InitToolboxStuff();
    // Setup Window and mouse tracking region
    theWindow = GetNewWindow(WIN_RESID, nil, (GrafPtr)-1);
    RgnHandle mouseRgn = NewRgn();
    // Get code resource and lock its handle
    UpdateWinProcHandle = GetResource('CODE', CODE_RESID);
    HLock(UpdateWinProcHandle);
    Boolean more2do = TRUE;
    while (more2do)     // Standard event loop processing
        {
        if (WaitNextEvent(everyEvent, &theEvent, 0xffffffff, mouseRgn))
            {
            switch(theEvent.what)
                {
                case updateEvt: // Call the code resource !!
                
                (*(UpdateWinProcPtr)(*UpdateWinProcHandle))(theWindow);
                break;
                case mouseDown: // Standard Mac Toolbox handling of Mouse Down
                    more2do = MouseDownProc(&theEvent, &foundWindow);
                default:
                    break;
                }
            }       
        }
    // Free all allocated memory
    HUnlock(UpdateWinProcHandle);
    ReleaseResource(UpdateWinProcHandle);
    DisposeRgn(mouseRgn);
    DisposeWindow(theWindow);
}

Listing Two

// Code resource procedure to draw text in a window
#include <SetUpA4.h>
#define HORIZ 65
#define VERT 95
void main(WindowPtr myWin)
    {
    static char msg[] = "68K Code Resource";
    GrafPtr savedPort;
    RememberA0();               // Save value of A0 for next macro
    SetUpA4();                  // Set up A4 for resource globals
    GetPort(&savedPort);        // Save current GrafPort
    SetPort(myWin);             // Make mine the current GrafPort
    BeginUpdate(myWin); 
    MoveTo(HORIZ, VERT);            // Move cursor to position
    DrawText(msg, 0, sizeof(msg));  // Draw the string
    EndUpdate(myWin);
    SetPort(savedPort);         // restore current GrafPort
    RestoreA4();                // Restore A4
    }

Listing Three

// Macintosh application to create a simple window and do basic event handling.
// Paul Kaplan - Symantec Corporation
#include "InitToolboxStuff.h"
#include "MouseDownProc.h"
#include "UpdateWinProc.h"
#define WIN_RESID 128
#define CODE_RESID 128
void main()
    {
    static WindowPtr theWindow, foundWindow;
    static EventRecord theEvent;
    Handle UpdateWinProcHandle;
    // Variable to hold Universal Proc Pointer
    UniversalProcPtr theUPP;
    // Proc Info Type - describes the called procedure's interface
    ProcInfoType theProcInfo = kCStackBased | 
                                     STACK_ROUTINE_PARAMETER(1,kFourByteCode);
    InitToolboxStuff();
    // Setup Window and mouse tracking region
    theWindow = GetNewWindow(WIN_RESID, nil, (GrafPtr)-1);
    RgnHandle mouseRgn = NewRgn();
    // Get code resource and lock its handle
    UpdateWinProcHandle = GetResource('CODE', CODE_RESID);
    HLock(UpdateWinProcHandle);
    Boolean more2do = TRUE;
    while (more2do)     // Standard event loop processing
        {
        if (WaitNextEvent(everyEvent, &theEvent, 0xffffffff, mouseRgn))
            {
            switch(theEvent.what)
                {
                case updateEvt: // Call the code resource using 
                                // CallUniversalProc instead of 
                                // calling routine directly
                    theUPP = (UniversalProcPtr)*UpdateWinProcHandle;
                    // Convert dereferenced handle to UPP
                    CallUniversalProc(theUPP, theProcInfo, theWindow);
                    // Call MixedMode Manager
                    break;
                case mouseDown: // Standard Mac Toolbox handling of Mouse Down
                    more2do = MouseDownProc(&theEvent, &foundWindow);
                default:
                    break;
                }
            }       
        }
    // Free all allocated memory
    HUnlock(UpdateWinProcHandle);
    ReleaseResource(UpdateWinProcHandle);
    DisposeRgn(mouseRgn);
    DisposeWindow(theWindow);
}

Listing Four

// Code resource procedure to draw text in a window
#include <new.h>
#define HORIZ 65
#define VERT 95
void main(WindowPtr myWin)
    {
    static char msg[] = "PPC Code Resource";
    static GrafPtr savedPort;
// Call any static constructors in this link unit. Also make 
// QDGlobals available
    __cplusrsrcinit();
    GetPort(&savedPort);        // Save current GrafPort
    SetPort(myWin);             // Make mine the current GrafPort
    BeginUpdate(myWin); 
    MoveTo(HORIZ, VERT);                // Move cursor to position
    DrawText(msg, 0, sizeof(msg));      // Draw the string
    EndUpdate(myWin);
    SetPort(savedPort);         // restore current GrafPort
    __cplusrsrcterm();          // Call any destructors in this link unit
    }
End Listings


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video