Macintosh Programming Pitfalls

Rev 2
8/30/95

#1 -- SetPort() pitfalls.

Author - Andrew Welch

When you call SetPort(), QuickDraw stashes the port into the QuickDraw global qd.thePort. Until the next call to SetPort(), all QuickDraw drawing will take place in the port stashed in qd.thePort.

How can this cause problems? Consider this scenerio: you're drawing in a particular port (window), then you dispose of it. If you issue a QuickDraw command before another call to SetPort(), QuickDraw will be drawing into a port that no longer exists.

The solution is simple: bracket all of your drawing code as follows:

GrafPtr oldPort;

GetPort(&oldPort);
SetPort(myPort);

// -- Do whatever drawing you desire

SetPort(oldPort);
This way, you are not only being safe by saving/restoring the old port, but you are also explicitly setting the port before you do any drawing in a window.


#2 -- Trashing resource files.

Author - Andrew Welch

The Resource Manager is a handy beast, but unless you take care to feed it properly, it can turn around and bite you. If you are modifying any resource files (using the calls AddResource(), WriteResource(), or RmveResource()), there are some caveats to be aware of.

First, a little background. The Resource Manager file format is as follows:
+----------------------+
| Resource file header |
+----------------------+
|                      |
|   Resource data      |
|                      |
+----------------------+
|    Resource map      |
+----------------------+
Normally you needn't be concerned with the format of resource files, however it's important to know the overall scheme that resources file use.

When you open a resource file, the resource file's map is read into memory, as are any resources that have the preload bit set. The Resource Manager uses the resource map as you might expect -- it contains a table of what resources are in the file and where they dwell in the resource data area.

Let's say you have a particular resource loaded into memory, you resize the resource handle to make it bigger, than call WriteResource() to write the changes out to your file. Sound fine, right? Nope.

When you call WriteResource(), only the resource data is written out to disk -- the resource file's map is only changed in memory. Sure, the resource map will be written out to disk when the file is closed, but if you crash before that happens, BLAMMO! The resource data in the file and the resource map are out of synch, and you'll have yourself a trashed resource file.

The solution is to call UpdateResFile() after any call to AddResource(), WriteResource(), or RmveResource(). If you are doing a number of resource manipulations in a row, you don't need to go hog wild and call UpdateResFile() after each one -- it'd slow performance down quite a bit.

But after you're done with your resource manipulations, call UpdateResFile() to make sure the resource data *and* the map are written out to disk. Absolutely do this before the next call to WaitNextEvent(), because you don't want any other misbehaven process to crash, and possibly trash your resource files!


#3 -- Properly handling resources.

Author - Andrew Welch

When you load in a resource -- either directly by calling GetResource(), or indirectly by calling a Toolbox routine like GetPicture(), which in turn calls GetResource() -- the Resource Manager "owns" that Handle. Do not dispose of it yourself by calling DisposeHandle().

The Resource Manager keeps a table of all of the resources that it loaded into memory. If you call GetResource() on a resource Handle that is already in memory, the Resource Manager simply returns the Handle to you. When you call CloseResFile(), all of the resource Handle's "owned" by that resource file are automatically disposed for you.

Therefor, do *not* call DisposeHandle() on any Handle that was loaded in by the Resource Manager. Doing so wil result in the Resource Manager referencing a handle that has already been disposed of. Can you say "mysterious random crashes"?

Now let's look at how to properly handle resources in memory. We'll look at some example code first, and then an explanation:
Handle myResource;

myResource = GetResource(MY_RESOURCE_TYPE, MY_RESOURCE_ID);
if (myResource != nil)
    {
    HNoPurge(myResource);

// -- Manipulate the resource as you please...

    HPurge(myResource);
    }
The first thing you'll notice is that we don't call ResError() to see if the resource was loaded in successfully -- the proper way to check to see if a resource was loaded in OK is to check for nil.

Next you'll notice that the first thing we do if the resource could be loaded in OK is we make the resource unpurgable. Any Handle that is purgable may disappear out from underneath us if memory is needed elsewhere, and we shouldn't assume that that the resource will be unpurgable when it is loaded in.

Finally, instead of disposing of the memory that the resource Handle occupies, we simply mark it as purgable. As we already outlined earlier, you should not call DisposeHandle() on any resource Handle. If you will never look at the resource again, you can call ReleaseResource() to have the Resource Manager dispose of the resource Handle in memory, and remove it from the Resource Manager's internal data structures.

However a better way to handle it is to simply set the resource Handle to purgable as was done in the above example. What this does is it keeps the resource Handle in memory, but if the Memory Manager needs more memory for some other operation, it'll automatically purge it.

By using this technique, your application will be fast because if memory is available, resources that you use will be kept in memory. Calls to GetResource() on handles that are already in memory will simply return the resource Handle, rather than go to the disk to load the data in. If for some reason one of your resource Handles was purged, subsequent calls to GetResource() will simply load it back into memory.

If you use this technique throughout your code, you won't have to worry about which Handles to call ReleaseResource() on, which to keep around, etc. It's one simple, effective, optimal technique for handling resources throughout your application. Let the Memory Manager and Resource Manager work for you.


#4 -- Interrupt nastiness.

Author - Andrew Welch

If you are writing any kind of code that can be called at Interrupt time, such as VBL Tasks, Time Manager Tasks, Sound Manager Callbacks, etc., there are a few things you need to keep in mind.

- You cannot depend on the contents of any unlocked Handle. The Memory Manager could be in the middle of moving it when your Interrupt-time code is called.

- You cannot depend on register A5 being set up properly, and since register A5 is used to reference global variables on 68K machines (and emulated 68K code on PPC Macs), you cannot therefor reference any global variables unless you properly setup A5.

- There are very few toolbox routines that you can successfully call, because for the most part, the toolbox isn't re-entrant (you could have interrupted the same routine you are attempting to call).

- You cannot call any toolbox routines that could move or purge memory.

Keep these in mind when you are writing interrupt-time code.


#5 - Memory Manager gotcha's.

Author - Andrew Welch

Improperly using the Memory Manager is the source of the vast majority of Macintosh programming errors. We'll take a look at a few of the most common type of Memory Manager-related programming errors.

Memory Basics

The Memory Manager can move Handle's around without you knowing about it, however there is no reason to be paranoid and lock every Handle you load into memory -- this will effectively cripple the Memory Manager. A better way is to understand when the Memory Manager can move memory around on you, when it makes a difference to you, and code safely around it.

Normally, you'll want the Memory Manager to move Handle's around in memory for you -- it allows the Memory Manager to make the most efficient use of the available memory. Only unlocked Handle's can be moved around, and since they are doubly-dereferenced, it all happens transparently to you.

"Handle's" are really just pointers to another pointer, called Master Pointers. When a block of memory is moved, only the Master Pointer is changed -- the Handle you have still refers to the same Master Pointer, and thus indirectly to the same chunk of memory no matter where it is moved to. Thus normally, you'll never have to worry about memory being moved behind your back: if it happens, it normally won't affect you in any way.

The only time that the block of memory a Handle refers to might be moved is when you explicitly tell the Memory Manager to move memory around (via a call to MoveHHi(), CompactMem(), MaxMem(), etc.) or when a memory allocation is requested that can't be fullfilled given the current state of memory. In the latter case, the Memory Manager shuffles the unlocked Handles around in memory in an effort to make more efficient use of the available space. Blindly locking down of course interfers with this process, which can result in a fragmented heap.

A fragmented heap has a decent amount of free memory in it, but it is fragmented into little chunks because of all of the locked handles that are strewn around in it. If you need to lock a Handle down for any length of time, at least call MoveHHi() on the Handle before you do lock it, so that the Memory Manager can attempt to move it out of the way of the rest of the heap.

So how can moving memory cause trouble? Consider the following code snippet:

void foo(void)
{
Handle myHandle;

myHandle = NewHandle(FOO_SIZE);
if (myHandle != nil)
    bar(*myHandle);
}// -- foo

void bar(Ptr dataPtr)
{
PicHandle myPict;

myPict = GetPicture(BAR_PICTURE_ID);
if (myPict != nil)
    BlockMove((Ptr)*myPict, dataPtr, 10);
}// -- bar
Let's see, in foo we allocate a new handle, check to see it was allocated OK, then pass the dereferenced handle off to the routine bar. The first thing bar does is load in a PicHandle, then it BlockMove's 10 bytes from the PicHandle into the dereferenced handle passed.

But wait -- what if the call to GetResource() causes the Memory Manager to move memory around? The Ptr that was passed to bar may now be invalid, because that block of memory it once pointed to may have moved.

Thus we find the only time you need to lock a Handle down: when you dereference it and pass it to a routine that can move/purge memory (any routine that allocates memory suffices).

More Memory Gotcha's


To be continued......