Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Memory /
Chapter 4 - Memory Management Utilities


Using the Memory Management Utilities

This section describes how you can

Accessing the A5 World in Completion Routines

Some Toolbox and Operating System routines require you to pass the address of an application-defined callback routine, usually in a variable of type ProcPtr. After a certain condition has been met, the Toolbox executes the specified routine. The exact time at which the Toolbox executes the routine varies. The timing of execution is determined by the Toolbox routine to which you passed the routine's address and the action that must be completed before the routine is called.

Callback routines are quite common in the Macintosh system software. A grow-zone function, for instance, is an application-defined callback routine that is called every time the Memory Manager cannot find enough space in your heap to honor a memory-allocation request. Similarly, if your application plays a sound asynchronously, you can have the Sound Manager execute a completion routine after the sound is played. The completion routine might release the sound channel used to play the sound or perform other cleanup operations.

In general, you cannot predict what your application will be doing when an asynchronous completion or callback routine is actually executed. The routine could be called while your application is executing code of its own or executing another Toolbox or Operating System routine.

Note
The completion or callback routine might even be called when your application is in the background. Before executing a completion or callback routine belonging to your application, the Process Manager checks whether your application is in the foreground. If not, the Process Manager performs a minor switch to give your application temporary control of the CPU.
Many Toolbox and Operating System routines do not need to access the calling application's global variables, QuickDraw global variables, or jump table. As a result, they sometimes use the A5 register for their own purposes. They save the current value of the register upon entry, modify the register as necessary, and then restore the original value on exit. As you can see, if one of these routines is executing when your callback routine is executed, your callback routine cannot depend on the value in the A5 register. This effectively prevents your callback routine from using any part of its A5 world.

To solve this problem, simply use the strategy that the Toolbox employs when it takes over the A5 register: save the current value in the A5 register at the start of your callback procedure, install your application's A5 value, and then restore the original value when you exit. Listing 4-1 illustrates a very simple grow-zone function that uses this technique. It uses the SetCurrentA5 and SetA5 utilities to manipulate the A5 register.

Listing 4-1 A sample grow-zone function

FUNCTION MyGrowZone (cbNeeded: Size): LongInt;
VAR
   theA5:   LongInt;             {value of A5 when function is called}
BEGIN
   theA5 := SetCurrentA5;        {remember current value of A5; install ours}
   IF (gEmergencyMemory^ <> NIL) & (gEmergencyMemory <> GZSaveHnd) THEN
      BEGIN
         EmptyHandle(gEmergencyMemory);
         MyGrowZone := kEmergencyMemorySize;
      END
   ELSE
      MyGrowZone := 0;           {no more memory to release}
   theA5 := SetA5(theA5);        {restore previous value of A5}
END;
The function SetCurrentA5 does two things: it returns the current value in the A5 register, and it sets the A5 register to the value of the CurrentA5 low-memory global variable. This global variable always contains a value that points to the boundary between the current application's parameters and its global variables. The MyGrowZone function defined in Listing 4-1 calls SetCurrentA5 on entry to make sure that it can read the value of the gEmergencyMemory global variable.

The function SetA5 also does two things: it returns the current value in the A5 register, and it sets the A5 register to whatever value you pass to the function. The MyGrowZone function calls SetA5 with the original value of the A5 register as the parameter. In this case, the value returned by SetA5 is ignored.

There is no way to test whether, at the time your callback routine is called, your application is executing a Toolbox routine that could change the A5 register. Therefore, to be safe, you should save and restore the A5 register in any callback routine that accesses any part of your A5 world. Such routines include

See the section of Inside Macintosh describing any particular completion or callback routine for details on whether you need to save and restore the A5 register in this way.

Accessing the A5 World in Interrupt Tasks

Sometimes, an application-defined routine executes at a time when you can't reliably call SetCurrentA5. For example, if your application is not the current application and you call SetCurrentA5 as illustrated in Listing 4-1, the function will not return your application's value of CurrentA5. The SetCurrentA5 function always returns the value of the low-memory global variable CurrentA5, which always belongs to the current application. You'll end up reading some other application's A5 world.

In general, you cannot reliably call SetCurrentA5 in any code that is executed in response to an interrupt, including the following:

Instead of calling SetCurrentA5 at interrupt time, you can call it at noninterrupt time when yours is the current application. Then store the returned value where you can read it at interrupt time. For example, the Notification Manager allows you to store information in the notification record passed to NMInstall. When you set up a notification record, you can use the nmRefCon field to hold the value in the A5 register. Listing 4-2 illustrates how to save the current value in the A5 register and pass that value to a response procedure.

Listing 4-2 Passing A5 to a notification response procedure

VAR
   gMyNotification:  NMRec;         {a notification record}

BEGIN
   WITH gMyNotification DO
   BEGIN
      qType := ORD(nmType);         {set queue type}
      nmMark := 1;                  {put mark in Application menu}
      nmIcon := NIL;                {no alternating icon}
      nmSound := Handle(-1);        {play system alert sound}
      nmStr := NIL;                 {no alert box}
      nmResp := @SampleResponse;    {set response procedure}
      nmRefCon := SetCurrentA5;     {pass A5 to notification task}
   END;
END;
The key step is to save the value of CurrentA5 where the response procedure can find it--in this case, in the nmRefCon field. You must call SetCurrentA5 at noninterrupt time; otherwise, you cannot be certain that it will return the correct value.

When the notification response procedure is executed, its first task should be to call the SetA5 function, which sets register A5 to the value stored in the nmRefCon field. At the end of the routine, the notification response procedure should call the SetA5 function again to restore the previous value of register A5. Listing 4-3 shows a simple response procedure that sets up the A5 register, modifies a global variable, and then restores the A5 register.

Listing 4-3 Setting up and restoring the A5 register at interrupt time

PROCEDURE SampleResponse (nmReqPtr: NMRecPtr);
VAR
   oldA5:      LongInt;       {A5 when procedure is called}
BEGIN
   oldA5 := SetA5(nmReqPtr^.nmRefCon);
                              {set A5 to the application's A5}
   gNotifReceived := TRUE;    {set an application global }
                              { to show alert was received}
   oldA5 := SetA5(oldA5);     {restore A5 to original value}
END;
Note
Many optimizing compilers (including MPW) might put the address of a global variable used by the interrupt routine into a register before the call to SetA5, thereby possibly generating incorrect references to global data. To avoid this problem, you can divide your completion routine into two separate routines, one to set up and restore A5 and one to do the actual completion work. Check the documentation for your development system to see if this division is necessary, or contact Macintosh Developer Technical Support.
Several of the other managers that you can use to install interrupt code--including the Deferred Task Manager, the Time Manager, and the Vertical Retrace Manager--do not include a reference constant field in their task records. Therefore, if you wish to access global variables from within one of these tasks, you must use another mechanism to attach the value of the A5 register to the task record.

To do this, you can define a new record that contains the task record and your own reference constant field. You can initialize the task record as you normally would and then copy the value of your application's A5 register into the reference constant field you created. Then, when you obtain a pointer to the task record at interrupt time, you can
use your knowledge of the size of the task record to compute the location of your reference constant field. See the chapters "Time Manager" and "Vertical Retrace Manager" in Inside Macintosh: Processes for detailed illustrations of these techniques.

Using QuickDraw Global Variables in Stand-Alone Code

If you are writing a stand-alone code segment such as a definition procedure for a window, menu, or control, you might want routines in that segment to examine the QuickDraw global variables of the current application. For example, you might want a control definition function to reference some of the QuickDraw global variables, such as thePort, screenBits, or the predefined patterns. Stand-alone segments, however, have no A5 world; if you try to link a stand-alone code segment that references your application's global variables, the linker may be unable to resolve those references.

To solve this problem, you can have the definition function find the value of the application's A5 register (by calling the SetCurrentA5 function) and then use that information to copy all of the application's QuickDraw global variables into a record in the function's own private storage. Listing 4-4 defines a record type with the same structure as the QuickDraw global variables. Note that randSeed is stored lowest in memory and thePort is stored highest in memory.

Listing 4-4 Structure of the QuickDraw global variables

TYPE
   QDVarRecPtr = ^QDVarRec;
   QDVarRec =
   RECORD
      randSeed:   LongInt;          {for random-number generator}
      screenBits: BitMap;           {rectangle enclosing screen}
      arrow:      Cursor;           {standard arrow cursor}
      dkGray:     Pattern;          {75% gray pattern}
      ltGray:     Pattern;          {25% gray pattern}
      gray:       Pattern;          {50% gray pattern}
      black:      Pattern;          {all-black pattern}
      white:      Pattern;          {all-white pattern}
      thePort:    GrafPtr;          {pointer to current GrafPort}
   END;
The location of these variables is linker-dependent. However, the A5 register always points to the last of these global variables, thePort. The Operating System references all other QuickDraw global variables as negative offsets from thePort. Therefore, you must dereference the value in A5 (to obtain the address of thePort), and then subtract the combined size of the other QuickDraw global variables from that address. The difference is a pointer to the first of the QuickDraw global variables, randSeed.
You can copy the entire record into a local variable simply by dereferencing that pointer, as illustrated in Listing 4-5.

Listing 4-5 Copying the QuickDraw global variables into a record

PROCEDURE GetQDVars (VAR qdVars: QDVarRec);
TYPE
   LongPtr = ^LongInt;
BEGIN
   qdVars := QDVarRecPtr(LongPtr(SetCurrentA5)^ -
                         (SizeOf(QDVarRec) - SizeOf(thePort)))^;
END;
Thereafter, your stand-alone code segment can read QuickDraw global variables through the structure returned by GetQDVars. Listing 4-6 defines a very simple draw routine for a control definition function. After reading the calling application's QuickDraw global variables, the draw routine paints a rectangle with a pattern.

Listing 4-6 A control's draw routine using the calling application's QuickDraw patterns

PROCEDURE DoDraw (varCode: Integer; myControl: ControlHandle;
                  flag: Integer);
VAR
   cRect: Rect;
   qdVars: QDVarRec;
   origPenState: PenState;
CONST
   kDraw = 1;                       {constant to specify drawing}
BEGIN
   GetPenState(origPenState);       {get original pen state}
   cRect := myControl^^.contrlRect; {get control's rectangle}
   IF flag = kDraw THEN
      BEGIN
         GetQDVars(qdVars);         {patterns are QD globals}
         PenPat(qdVars.gray);       {install desired pattern}
         PaintRect(cRect);          {paint the control}
      END;
   SetPenState(origPenState);       {restore original pen state}
END;
The DoDraw drawing routine defined in Listing 4-6 retrieves the calling application's QuickDraw global variables and paints the control rectangle with a light gray pattern. It also saves and restores the pen state, because the PenPat procedure changes that state.

Switching Addressing Modes

If you are writing a driver for a slot-card device, you can use the SwapMMUMode procedure to change to 32-bit address-translation mode temporarily, as follows:

myMode := true32b;               {specify switch to 32-bit mode}
SwapMMUMode(myMode);             {perform switch}
The parameter passed to SwapMMUMode must be a variable that is equal to the constant false32b or the constant true32b.

CONST
   false32b    = 0;              {24-bit addressing mode}
   true32b     = 1;              {32-bit addressing mode}
The SwapMMUMode procedure switches to the specified mode and then changes the parameter to indicate the mode previously in use. Thereafter, you can restore the previous address-translation mode by again calling

SwapMMUMode(myMode);
Note
You should switch to 32-bit mode only if the computer supports 32-bit addressing. To find out whether a system supports 32-bit mode and whether a system started up in 32-bit mode, use the Gestalt function, described in the chapter "Gestalt Manager" in Inside Macintosh: Operating System Utilities. To determine the current address-translation mode, call the GetMMUMode function.
If you do call SwapMMUMode, be careful to avoid situations that can cause the system to read an invalid address from the program counter. When the system is in 24-bit mode and you load a code resource into a block of memory (for example, by calling GetResource), the high byte of that block's master pointer contains Memory Manager flag bits. If you try to execute that code by performing an assembly-language JSR instruction (typically JSR (A0), with the master pointer in register A0), the entire master pointer is translated directly into the program counter. This, however, is not a valid 32-bit address. As soon as you switch to 32-bit mode, the program counter contains an invalid value. This is virtually certain to cause the system to crash.

Note
This problem can arise when you change to 32-bit mode in code loaded from a resource or placed into a block of memory that was allocated by calls to Memory Manager routines. It does not arise with standard 'CODE' resources because the Segment Manager fixes the program counter.
To avoid this problem, simply call StripAddress on the address in the program counter before you call SwapMMUMode. Listing 4-7 shows one way to do this.

Listing 4-7 Stripping the program counter

PROCEDURE FixPC;
   INLINE   $41FA, $000A,  {LEA *+$000C,A0}
            $2008,         {MOVE.L A0,D0}
            $A055,         {_StripAddress}
            $2040,         {MOVEA.L D0,A0}
            $4ED0;         {JMP (A0); jump to next instruction}
For these same reasons, you also need to call StripAddress on any address you pass to the _SetTrapAddress trap, if the address references a block in your application heap.

Stripping Flag Bits From Memory Addresses

If your code runs on a system that might have started up with the 24-bit Memory Manager, you sometimes need to strip the flag bits from a memory address before you use it. The Operating System provides the StripAddress function for this purpose.

The StripAddress function takes an address as a parameter and returns the value of the address's low-order 3 bytes if the computer started up in 24-bit mode. If the system started up in 32-bit mode, StripAddress returns the address unchanged (because it must already be a valid 32-bit address). Note that if a system starts up in 32-bit mode, you cannot switch it to 24-bit mode.

WARNING
If you pass a valid 32-bit address to StripAddress and the computer started in 24-bit mode, the function still strips off the high byte of the address, thus probably rendering the address invalid. You can pass 32-bit addresses to StripAddress if the system started up in 32-bit mode, but then the function does nothing to the address. Therefore, you should ordinarily pass only 24-bit addresses to the StripAddress function.
You need to use StripAddress primarily in device drivers or other software that communicates heap addresses to external hardware (such as a NuBus card). Because the external hardware might interpret the flag bits of a master pointer as part of the address, you need to call StripAddress to clear those flag bits.

There is nothing inherently dangerous about 24-bit addresses. They cause problems only when you try to use them in 32-bit mode. So, unless you are switching addressing modes (by calling SwapMMUMode), you generally don't need to call StripAddress.

You might, however, need to call StripAddress in these special cases, even if you are not designing a driver:

In virtually all other cases, you don't need to call StripAddress before using a
valid 24-bit address. In particular, you don't need to call StripAddress before dereferencing a pointer or handle in 24-bit mode, unless you subsequently switch
to 32-bit mode by calling SwapMMUMode. Also, you don't need to call StripAddress when checking pointers and handles for equality or when performing address arithmetic.

Because you need to call StripAddress rarely (if ever), the additional processing time required to call StripAddress shouldn't adversely affect the execution of your software. In some cases, however, you might want to avoid the overhead of calling the trap dispatcher every time you need to call StripAddress. (A good example might be a time-critical loop in an interrupt task.) You can use the QuickStrip function defined in Listing 4-8 in place of StripAddress when speed is a real concern.

Listing 4-8 Stripping addresses in time-critical code

FUNCTION QuickStrip (thePtr: Ptr): Ptr;
BEGIN
   QuickStrip := Ptr(BAND(LongInt(thePtr), gStripAddressMask));
END;
The QuickStrip function defined in Listing 4-8 simply masks the address it is passed with the same mask StripAddress uses. You can calculate that mask by executing the lines of code in Listing 4-9 early in the execution of your software:

Listing 4-9 Calculating the StripAddress mask

VAR
   gStripAddressMask:   LongInt;    {global mask variable}

   gStripAddressMask := $FFFFFFFF;
   gStripAddressMask :=
                  LongInt(StripAddress(Ptr(gStripAddressMask)));
Unless you are calling StripAddress repeatedly at interrupt time, you probably don't need to use this technique.

Translating Memory Addresses

As explained earlier in "Address Translation" on page 4-8, you sometimes need to override the Operating System's standard translation of 24-bit addresses into their 32-bit equivalents. This is necessary because the Virtual Memory Manager might have programmed the MMU to map unused NuBus slot addresses into the address space reserved for RAM. If you try to use a 24-bit address when the system switches to 32-bit mode, the standard translation might result in a 32-bit address that points to the space reserved for expansion cards. In that case, you are virtually guaranteed to obtain
invalid results.

To prevent this problem, you can use the Translate24To32 function to get the
32-bit equivalent of a 24-bit address. In general, you should test for the presence of
the _Translate24To32 trap before you use any 24-bit addresses in 32-bit mode.
If it is available, you should use it in place of the static translation process performed automatically by the Operating System while running in 32-bit mode.

Note
You need to use the Translate24To32 function only when the computer is running in 32-bit mode, it was booted in 24-bit mode, and you are communicating with external hardware. Most applications do not need to use it.
Listing 4-10 illustrates how to use Translate24To32. The DoRoutine procedure defined there calls the application-defined routine MyRoutine to process a block of
data while in 32-bit mode. It checks whether the _Translate24To32 trap is available, and if so, makes sure that the address to be read is a valid 32-bit address.

Listing 4-10 Translating 24-bit to 32-bit addresses

PROCEDURE DoRoutine (oldAddr: Ptr; length: LongInt);
BEGIN
   IF TrapAvailable(_Translate24To32) THEN
      MyRoutine(Translate24To32(oldAddr), length);
   ELSE
      MyRoutine(oldAddr, length);
END;
Note that you don't need to call StripAddress before calling Translate24To32, because the Translate24To32 function automatically ignores the high-order byte of the 24-bit address you pass it. (For a definition of the TrapAvailable function, see the chapter "Gestalt Manager" in Inside Macintosh: Operating System Utilities.)


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996