MACINTOSH C CARBON: A Hobbyist's Guide To Programming the Macintosh in C
Version 1.0
© 2001 K. J. Bricknell
CHAPTER 1
SYSTEM SOFTWARE, POWERPC RUN-TIME ENVIRONMENT, MANAGING MEMORY, AND RESOURCES
System Software
All Macintosh applications make many calls, for many purposes, to system software functions. Such purposes include, for example, the creation of standard user interface elements such as windows and menus, the drawing of text and graphics, and the coordination of the application's actions with other open applications.
|
The main other open application that an application needs to work with is the Finder. The Finder is not really part of the system software, although it is sometimes difficult to tell where the Finder ends and the system software begins.
|
The entire collection of system software functions is divided into functional groups known as managers. In Carbon, each manager is part of a higher-level grouping called a manager group. The manager groups and their constituent managers are shown in the following, in which those managers of particular relevance to this book are underlined:
Application Utilities
|
Facilities or extending human interface features and data-sharing capabilities beyond those provided by the Human Interface Toolbox. |
AltiVec Utilities |
Find By Content |
Translation Manager |
|
Carbon Core |
Essential services that are generally devoid of a user interface, including memory and process management. |
Alias Manager |
Collection Manager |
Component Manager |
File Manager |
Folder Manager |
Low Memory Accessors |
Memory Management Utilities |
Memory Manager |
Mixed Mode Manager |
Multiprocessing Services |
Notification Manager |
Pascal String Utilities |
Resource Manager |
Thread Manager |
Time Manager |
|
Common OS Services |
Display Manager |
Gestalt Manager |
Power Manager |
Process Manager |
Speech Recognition Manager |
Speech Synthesis Manager |
|
|
Core Foundation |
Abstracts several operating system utitlities fundamental to both Mac OS 8/9 and Mac OS X. |
Base Services |
Bundle Services |
Collection Services |
Plug-in Services |
Preferences Services |
Property List Services |
String Services |
URL Services |
Utility Services |
XML Services |
|
|
Graphics Services |
Allows applications to construct and render images. |
Carbon Printing Manager |
Color Picker Manager |
ColorSync Manager |
HTML Rendering Library |
Palette Manager |
QuickDraw Manager |
|
|
Hardware Interfaces |
Device Manager |
PCI Card Services |
|
|
Human Interface Toolbox |
Comprises groups of libraries that implement the Mac OS human interface. |
Appearance Manager |
Apple Help |
Carbon Event Manager |
Carbon Help Manager |
Control Manager |
Dialog Manager |
Drag Manager |
Event Manager |
Finder Interface |
Icon Utilities |
List Manager |
Menu Services |
Navigation Services |
Picture Utilities |
Scrap Manager |
Window Manager |
Networking and Communication Services |
Internet Config |
NSL Manager |
Open Transport |
URL Access Manager |
QuickTime |
Assist your application in combining multiple forms of communication, such as text, pictures, video, and music. |
Image Compression Manager |
Media Handlers |
Movie Toolbox |
QuickTime Components |
QuickTime Media Layer |
QuickTime Music |
QuickTime Streaming |
QuickTime VR |
Sound Manager |
|
|
|
Runtime Services |
Provides programming interfaces that prepare code for execution and control how functions call each other. |
Code Fragment Manager |
Debugger Services |
Math & Logical Utilities |
|
Scripting and Apple Events |
Apple Event Manager |
Open Scripting Architecture |
|
|
Text and Other International Services |
Assistance in creating applications for world-wide markets. |
Apple Type Services for Unicode Imaging |
Date, Time, and Measurement Utilities |
Font Manager |
FontSync |
Multilingual Text Engine |
QuickDraw Text |
Script Manager |
Text Encoding Conversion Manager |
Text Services Manager |
Text Utilities |
TextEdit |
Unicode Utilities |
PowerPC Run-Time Environment
A run-time environment is a set of conventions which determine how code is to be loaded into memory, where it is to be stored, how it is to be addressed, and how functions call other functions and system software routines. The system software and your development system jointly determine the run-time environment.
Fragments
The PowerPC run-time model is based on the use of fragments as the standard way of organising executable code and data in memory. A fragment is any block of executable PowerPC code and its associated data. Fragments can be of any size, and are complete, executable entities.
There are three broad categories of fragments, namely, applications, import libraries, and extensions. (Import libraries and system extensions are sometimes called shared libraries or dynamically-linked libraries.) An application is a fragment that can be launched by the user from the Finder.
Code Fragment Resource
You use a code fragment ('cfrg') resource (see Resources, below) to specify some characteristics of a code fragment. For an application, the code fragment resource indicates to the Process Manager that the application's data fork contains an executable code fragment container.
You do not have to create the 'cfrg' resource yourself because CodeWarrior does this for you when you compile your application.
Organisation of Memory
The basic organisation of memory in the PowerPC run-time environment is as follows:
- A system partition, which is reserved for the exclusive use of the system, occupies the lowest memory address. Part of the system partition is occupied by the system global variables, most of which contain information of use to the system software, for example:
- Ticks, which contains the number of ticks since system startup. (A tick is 1/60th of a second.)
- MBarHeight, which contains the height of the menu bar.
- Most of the remaining space is allocated to the Process Manager, which creates an application partition for each open application. Application partitions are loaded into the top part of RAM first.
The Application Partition
In each application partition, there is a stack and a heap, as well as space for the application's global variables (see Fig 1).
The Stack
The stack is used for memory allocation associated with the execution of functions. When an application calls a function, space is allocated on the stack for a stack frame, which contains the function's parameters, local variables and return address. The local variables and function parameters are popped off the stack when the function has executed,. The C compiler generates the code that creates and deletes stack frames for each function call.
It is important to understand that the Memory Manager has no way of preventing the stack from encroaching on, and corrupting, the heap. Ordinarily, unless your application uses heavy recursion (one function repeatedly calling itself), you almost certainly will never need to worry about the possibility of stack overflow.
|
The reason that recursion increases the risk is that, each time the function calls itself, a new copy of that function's parameters and variables is pushed onto the stack.
|
The Heap
The application heap is that part of the application partition in which memory is dynamically allocated and released on demand. Space within the heap is allocated, in blocks, by both direct or indirect calls to the Memory Manager. An indirect allocation arises from a call to a function which itself calls a Memory Manager memory allocation function.
Managing Memory - Mac OS 8/9
Inside the Application Heap - Nonrelocatable and Relocatable Memory Blocks
An application may use the Memory Manager to allocate two different types of memory blocks: a nonrelocatable block and a relocatable block.
Nonrelocatable Blocks
Nonrelocatable blocks are blocks whose location in the heap is fixed. In its attempts to avoid heap fragmentation (see below), the Memory Manager allocates nonrelocatable blocks as low in the heap as possible, where necessary moving relocatable blocks upward to make space. Nonrelocatable blocks are referenced using a pointer variable of data type Ptr. Ptr is defined as follows:
typedef char *Ptr; // A pointer to a signed char.
A pointer is simply the address of a byte in memory. Thus a pointer to a nonrelocatable block is simply the address of the first byte in that block of memory. Note that, if a copy is made of the pointer variable after the block is created, and since the block cannot be moved, that copy will correctly reference the block until it is disposed of.
The Memory Manager function NewPtr allocates a nonrelocatable block, for example:
Ptr myPointer;
myPointer = NewPtr(sizeof(myDataStructure));
Nonrelocatable blocks are disposed of by a call to DisposePtr.
Unlike relocatable blocks, there are only five things that your application can do with a nonrelocatable block: create it; obtain its size; resize it; find which heap zone owns it; dispose of it.
Relocatable Blocks
Relocatable blocks are blocks which can be moved within the heap - for example, during heap compaction operations (see below). To reference relocatable blocks, the Memory Manager uses double indirection, that is, the Memory Manager keeps track of a relocatable block with a master pointer, which is itself part of a nonrelocatable master pointer block in the application heap. When the Memory Manager moves a relocatable block, it updates the master pointer so as to ensure that the master pointer always contains the current address of the relocatable block.
One master pointer block, which contains 64 master pointers, is allocated for your application by the Memory Manager at application launch. This block is located at the very bottom of the application heap (see Fig 1). The function MoreMasterPointers may be called by your application to allocate additional master pointers. To ensure that these additional (nonrelocatable) blocks are allocated as low in the heap as possible, the call to MoreMasterPointers should be made at the beginning of your program.
|
If these calls are not made, the Memory Manager will nonetheless automatically allocate additional blocks during application execution if required. However, since master pointer blocks are nonrelocatable, such allocation, which will not be at the bottom of the heap, is a possible cause of heap fragmentation. Your call to MoreMasterPointers should thus allocate sufficient master pointers to ensure that the Memory Manager never needs to create additional master pointer blocks for you. (You can empirically determine how many master pointers to allocate using a low-level debugger.)
|
Relocatable blocks are referenced using a handle variable of data type Handle. A handle contains the address of a master pointer, as illustrated at Fig 2. Handle is defined as follows:
typedef Ptr *Handle; // A pointer to a master pointer.
The Memory Manager function NewHandle allocates a relocatable block, for example:
Handle myHandle;
myHandle = NewHandle(sizeof(myDataStructure));
A relocatable block can be disposed of by a call to DisposeHandle. Note, however, that the Memory Manager does not change the value of any handle variables that previously referenced that deallocated block. Instead, those variables still hold the address of what was once the relocatable block's master pointer. If you inadvertently use a handle to a block you have already disposed of, your application could crash. You can avoid these problems by assigning the value NULL to the handle variable after you dispose of the relocatable block.
Heap Fragmentation, Compaction, and Purging
The continuous allocation and release of memory blocks which occurs during an application's execution can result in a condition called heap fragmentation. The heap is said to be fragmented when nonrelocatable blocks or locked relocatable blocks (see below) are scattered about the heap, leaving "holes" of memory between those blocks.
The Memory Manager continuously attempts to create more contiguous free memory space through an operation known as heap compaction, which involves moving all relocatable blocks as low in the heap as possible. However, because the Memory Manager cannot move relocatable blocks "around" nonrelocatable blocks and locked relocatable blocks, such blocks act like log-jams if there is free space below them. In this situation, the Memory Manager may not be able to satisfy a new memory allocation request because, although there may be enough total free memory space, that space is broken up into small non-contiguous blocks.
Heap fragmentation would not occur if all memory blocks allocated by the application were free to move during heap compaction. However, there are two types of memory block which are not free to move: nonrelocatable blocks and relocatable blocks which have been temporarily locked in place.
Locking and Unlocking Relocatable Blocks
Despite the potential of such action to inhibit the Memory Manager's heap compaction activities, it is nonetheless necessary to lock relocatable blocks in place in certain circumstances.
For example, suppose you dereference a handle to obtain a pointer (that is, a copy of the master pointer) to a relocatable block and, for the sake of increased speed, use that pointer within a loop to read or write data to or from the block.
|
Accessing a relocatable block by double indirection (that is, through its handle) instead of by single indirection (ie, through its master pointer) requires an extra memory reference.
|
If, within that loop, you call a function that has the potential to move memory, and if that function actually causes the relocatable block to be moved, the master pointer will be correctly updated but your copy (the pointer) will not. The net result is that your pointer no longer points to the data and becomes what is known as a dangling pointer. This situation is illustrated at Fig 3.
The documentation for system software functions indicates whether a particular function has the potential to move memory. Generally, any function that allocates space from the application heap has this potential. If such a function is not called in a section of code, you can safely assume that all blocks will remain stationary while that code executes.
Relocatable blocks may be locked and unlocked using HLock and HUnlock. The following example illustrates the use of these functions.
typedef struct
{
short intArray[1000];
char ch;
} Structure, *StructurePointer, **StructureHandle;
void myFunction(void)
{
StructureHandle theHdl;
StructurePointer thePtr;
short count;
theHdl = (StructureHandle) NewHandle(sizeof(Structure));
HLock(theHdl); // Lock the relocatable block ...
thePtr = *theHdl; // because the handle has been dereferenced ...
for(count=0;count>1000;count++)
{
(*thePtr).intArray[count] = 0; // and used in this loop ...
DrawChar((char)'A'); // which calls a function which could cause
} // the relocatable block to be moved.
HUnlock(theHdl); // On loop exit, unlock the relocatable block.
}
Moving Relocatable Blocks High
The potential for a locked relocatable block to contribute to heap fragmentation may be avoided by moving the block to the top of the heap before locking it. This should be done if new nonrelocatable blocks are to be allocated while the relocatable block in question is locked.
MoveHHi is used to move relocatable blocks to the top of the heap. HLockHi is used to move relocatable blocks to the top of the heap and then lock them. Be aware, however, that MoveHHi and HLockHi cannot move a block to the top of the heap if a nonrelocatable block or locked relocatable block is located between its current location and the top of the heap. In this situation, the block will be moved to a location immediately below the nonrelocatable block or locked relocatable block.
Purging and Reallocating Relocatable Blocks
In addition to compacting the heap in order to satisfy a memory allocation request, the Memory Manager may purge unlocked relocatable memory blocks that have been made purgeable.
HPurge and HNoPurge change a relocatable block from unpurgeable to purgeable and vice versa. When you make a relocatable block purgeable, your program should subsequently check the handle to that block before using it if calls are made to functions which could move or purge memory.
If the handle's master pointer is set to NULL, then the Operating System has purged its block. To use the information formerly in the block, space must then be reallocated for it and its contents must be reconstructed.
Effect of a Relocatable Block's Attributes
Two attributes of a relocatable block are whether the block is currently locked/unlocked or purgeable/ non-purgeable. These attributes are stored in bits in the block's master pointer tag byte.
|
The tag byte is the high byte of a master pointer. If Bit 5 of the tag byte is set, the block is a resource block (see below). If Bit 6 is set, the block is purgeable. If Bit 7 is set, the block is locked. |
The following summarises the effect of these attributes on the Memory Manager's ability to move and/or purge a relocatable block:
Tag Byte Indicates Block Is: |
The Memory Manager Can: |
Locked |
Purgeable |
Move The Block |
Purge the Block |
NO |
NO |
YES |
NO |
NO |
YES |
YES |
YES |
YES |
NO |
NO |
NO |
YES |
YES |
NO |
NO |
Note that a relocatable block created by a call to NewHandle is created initially unlocked and unpurgeable, and that locking a relocatable block will also make it unpurgeable if it is currently purgeable.
Avoiding Heap Fragmentation
The ideal heap is one with all nonrelocatable blocks at the bottom of the heap, all unlocked relocatable blocks above that, free space above that, and all relocatable blocks which must be locked for significant periods at the top of the heap. This ideal can be approached, and significant heap fragmentation avoided, by adherence to the following rules:
- At the beginning of the program, call MoreMasterPointers to allocate at least as many (nonrelocatable) master pointers as are required during program execution.
- Allocate all other required nonrelocatable blocks at the beginning of the application's execution.
- Avoid disposing of, and subsequently reallocating, nonrelocatable blocks during the application's execution.
- If you need to allocate a relocatable block that will need to be locked for a long period of time, call ReserveMem at the beginning of the program to reserve memory for the block as close to the bottom of the heap as is possible, and then lock the block immediately after allocating it.
- If a relocatable block is to be locked for a short period of time and nonrelocatable blocks are to be allocated while it is locked, call MoveHHi to move the relocatable block to the top of the heap and then lock it. Unlock the block when it no longer needs to be locked.
Also bear in mind that, in memory management terms, a relocatable block that is always locked is worse than a nonrelocatable block in that nonrelocatable blocks are always allocated as low in the heap as possible, whereas a relocatable block is allocated wherever the Memory Manager finds it convenient.
Master Pointer Tag Byte - HGetState and HSetState
There are certain circumstances where you will want to save, and later restore, the current value of a relocatable block's master pointer tag byte. Consider the following example, which involves three of an imaginary application's functions, namely, Function A, Function B, and Function C:
- Function A creates a relocatable block. For reasons of its own, Function A locks the block before executing a few lines of code. Function A then calls Function C, passing the handle to that function as a formal parameter.
- Function B also calls Function C at some point, passing the relocatable block's handle to it as a formal parameter. The difference in this instance is that, due to certain machinations in other areas of the application, the block is unlocked when the call to Function C is made.
- Function C, for reasons of its own, needs to ensure that the block is locked before executing a few lines of code, so it makes a call to HLock. Those lines executed, Function C then unlocks the block before returning to the calling function. This will not be of great concern if the return is to Function B, which expects the block to be still unlocked. However, if the return is to Function A, and if Function A now executes some lines of code which assume that the block is still locked, disaster could strike.
This is where the Memory Manager functions HGetState and HSetState come in. The sequence of events in Function C should have been as follows:
SInt8 theTagByte;
...
theTagByte = HGetState(myHandle); // Whatever the current state is, save it.
HLock(myHandle); // Redundant if Function A is calling, but no harm.
(Bulk of the Function C code, which requires handle to be locked.)
HSetState(myHandle,theTagByte) // Leave it the way it was found. (It could have
// been locked. It could have been unlocked.)
return;
This is an example of a of what might be called a "well-mannered function". It is an example of a rule that you may wish to apply whenever you write a function that takes a handle to a relocatable block as a formal parameter: If that function calls HLock, make sure that it leaves the block's tag byte (and thus the locked/unlocked bit) in the condition in which it found it.
|
Of course, this save/restore precaution will not really be necessary if you are absolutely certain that the block in question will be in a particular state (locked or unlocked) every time Function C is called. But there is nothing wrong with a little coding overkill to protect yourself from, for example, some future source code modifications which may add other functions which call Function C, and which may assume that the block's attributes will be handed back in the condition in which Function C found them.
|
Memory Leaks
When you have no further use for a block of memory, you ordinarily return that memory to the Memory Manager by calling DisposePtr or DisposeHandle (or ReleaseResource (see below)). In certain circumstances, not disposing of a block which is no longer required can result in what is known as a memory leak.
Memory leaks can have unfortunate consequences for your application. For example, consider the following function:
void theFunction(void)
{
Ptr thePointer;
OSErr osError;
thePointer = NewPtr(10000);
if(MemError() == memFullErr)
doErrorAlert(eOutOfMemory);
// The nonrelocatable block is used for some temporary purpose here, but is not
// disposed of before the function returns.
}
When theFunction returns, the 10000-byte nonrelocatable block will still exist (even though, incidentally, the local variable which previously pointed to it will not). Thus a large nonrelocatable block for which you have no further use remains in memory (at what is now, incidentally, an unknown location). If theFunction is called several more times, a new nonrelocatable block will be created by each call and the size of the memory leak will grow, perhaps eventually causing MemErr to return memFullErr. In this way, memory leaks can bring you application to a standstill and may, in some circumstances, cause it to crash.
|
The dynamic memory inspection tool ZoneRanger, which is included with Metrowerks CodeWarrior, can be used to check your application for memory leaks.
|
Memory Manager Errors
The error code resulting from the last call to a Memory Manager function.may be retrieved by calling the function MemError. Some of the error codes which may be returned by MemError are as follows:
Value |
Constant |
Description |
0 |
noErr |
No error occurred. |
-108 |
memFullErr |
No room in heap. |
-109 |
nilHandleErr |
Illegal operation on a NULL handle. |
-117 |
memLockedErr |
Trying to move a locked block (MoveHHi). |
Managing Memory - Mac OS X Considerations
Preamble - Memory in Mac OS X
In Mac OS X, each application runs in its own address space, meaning that an application cannot reference memory locations outside its assigned address space.
Compared with Mac OS 8/9, Mac OS X uses an entirely different heap structure and allocation behaviour.
Mac OS X uses a dynamic and highly efficient virtual memory system, which is always enabled. Your Carbon application must therefore assume that virtual memory is always on.
Memory Management Considerations
Allocating Memory
The functions FreeMem (get the amount of free memory in the current heap zone), PurgeMem (purge blocks without compacting the heap), and MaxMem (compact the heap and purge all the purgeable blocks from the current heap zone) are all included in Carbon, and behave as expected under Mac OS 8/9. When your application is running on Mac OS X, however, these functions are more or less meaningless because, in Mac OS X, the system provides virtually unlimited memory.
Stack Size
A Carbon application may have different stack sizes under Mac OS 8/9 and Mac OS X; accordingly, the StackSpace function (which returns the amount of unused space on the stack at the time of the call) is no longer very useful.
Master Pointer Allocation
Master pointers do not need to be pre-allocated, or their number optimised, in the Mac OS X memory model. Accordingly, the effect of the MoreMasterPointers function only applies when your application is run on Mac OS 8/9.
Resources
In order to meet various requirements of the system software, your application must provide its own resources, for example, resources which describe the application's user interface elements such as menus, windows, controls, dialog boxes and icons. In addition, the system software itself provides a number of resources (for example, fonts, patterns, icons, etc.) which may be used by your application.
The concept of resources reflects the fact that, in the Macintosh environment, inter-mixing code and data in a program is not encouraged. For example, it is usual practise to separate changeable data, such as message strings which may need to be changed in a foreign-language version of the application, from the application's code. All that is required in such a case is to create a resource containing the foreign language version of the message strings. There is thus no necessity to change and re-compile the source code in order to produce a foreign-language version of the application.
The subject of resources is closely related to the subject of files. A brief digression into the world of files is thus necessary.
About Files - The Data Fork and the Resource Fork
On the Macintosh, a file is a named, ordered sequence of bytes stored on a volume and divided into two forks:
- The Data Fork. The data fork typically contains data created by the user.
- The Resource Fork. The resource fork of a file contains resources, which are collections of data of a defined structure and type.
|
Before the introduction of Mac OS X and Carbon, application and document resources were invariably stored in the resource fork of, respectively, application and document files. Mac OS X and Carbon introduced an alternative scheme whereby resources can be placed in the data fork of a separate resource file. The main reason for this alternative scheme is that it prevents application and document files from losing resource data when moved around different file systems or between Macintosh and non-Macintosh systems.
This alternative scheme is, however, not addressed in this book. The content of this book assumes that application and document resources are stored in the resource fork of application and document files, and the associated demonstration program files reflect that arrangement.
|
All Macintosh files contain both a resource fork and a data fork, even though one or both of these forks may be empty. Note that the resource fork of a file is also called a resource file, because in some respects you can treat it as if it were a separate file.
The resource fork of a document file contains resources specific to the document, such as the size and location of the document's window when the document was last closed. The resource fork of an application file includes, typically, resources which describe the application's windows, menus, etc. Fig 4 illustrates the typical contents of the data and resource forks of an application file and a document file.
The data fork can contain any kind of data organised in any fashion. Your application can store data in the data fork of a document file in whatever way it deems appropriate, but it needs to keep track of the exact byte location of each particular piece of saved data in order to be able to access that data when required. The resource fork, on the other hand, is highly structured. As will be seen, all resource forks contain a map which, amongst other things, lists the location of all resources in the resource fork. This greatly simplifies the task of accessing those resources.
Resources and the Application
During its execution, an application may read resources from:
- The application's resource file, which is opened automatically when the application is launched.
- The System file, which is opened by the Operating System at startup and which contains resources which are shared by all applications (for example, fonts, icons, sounds, etc.) and resources which applications may use to help present the standard user interface.
- Other resource files, such as a preferences file in the Preferences folder holding the user's application-specific preferences, or the resource fork of a document file, which might define certain document-specific preferences.
The Resource Manager provides functions which allow your application to read in these resources and, in addition, to create, delete, open, modify and write resources in, from and to any Macintosh file. The following, however, is concerned only with creating resources for the application's resource file and with reading in standard resources from the application and System files. Other aspects of resources, including custom resources and resources in files other than the application and System files, are addressed at Chapter 19.
Resource Types and Resource IDs
An application refers to a resource by passing the Resource Manager a resource specification, which consists of the resource type and a resource ID:
Creating a Resource
At the very least, you may need to create resources for the standard user interface elements used by your application. You typically define the user interface elements in resources and then use Menu Manager, Window Manager, Dialog Manager or Control Manager functions to create these elements, based on their resource descriptions, as needed.
You can create resource descriptions using a resource editor such as Resorcerer (which uses the familiar point-and-click approach), or you can provide a textual, formal description of resources in a file and then use a resource compiler, such as Rez, to compile the description into a resource.
|
This book assumes the use of Resorcerer, and all demonstration program resources were created using Resorcerer.
|
An example of a resource definition for a window in Rez input format is as follows:
resource 'WIND' (128, preload, purgeable)
{
{64,60,314,460}, /* Window rectangle. (Initial window size and location.) */
kWindowDocumentProc, /* Window definition ID. */
invisible, /* Window is initially invisible. */
goAway, /* Window has a close box. */
0x0, /* Reference constant. */
"untitled", /* Window title. */
staggerParentWindowScreen /* Optional positioning specification. */
};
The structure of the compiled 'WIND' resource is shown at Fig 5.
Resource Attributes
Note the words preload and purgeable in the preceding 'WIND' resource definition. These are constants representing resource attributes, which are flags which tell the Resource Manager how to handle the resource. Resource attributes are described by bits in the low-order byte of an integer value:
Bit |
Constant |
Description |
1 |
resChanged |
Resource has been changed. |
2 |
resPreload |
Resource is to be read into memory immediately after the resource fork is opened. |
3 |
resProtected |
Application cannot change the resource ID, modify the resource's contents or remove the resource from the resource fork. |
4 |
resLocked |
Relocatable block occupied by the resource is to be locked. (Overrides the resPurgeable attribute.) |
5 |
resPurgeable |
Relocatable block occupied by the resource is to be purgeable. |
Note that, if both the resPreload and the resLocked attributes are set, the Resource Manager loads the resource as low as possible in the heap.
Resources Which Must Be Unpurgeable. Some resources must not be made purgeable. For example, the Menu Manager expects menu resources to remain in memory at all times.
Resources Which May Be Purgeable. Other resources, such as those relating to windows, controls, and dialog boxes, do not have to remain in memory once the corresponding user interface element has been created. You may therefore set the purgeable attribute for those kinds of resources if you so desire. The following considerations apply to the decision as to whether to make a resource purgeable or unpurgeable:
- The concept of purgeable resources dates back to the time when RAM was limited and programmers had to be very careful about allowing resources which were not in use to continue to occupy precious memory. Nowadays, however, RAM is not so limited.
- Some resources (for example, large 'PICT' resources and 'snd ' resources) do require a lot of memory, even by today's standards. Accordingly, such resources should generally be made purgeable.
- As will be seen, there are certain hazards associated with the use of purgeable resources. These hazards must be negated by careful programming involving additional lines of code.
Given these considerations, a sound policy would be to make all small and basic resources unpurgeable and set the resPurgeable attribute only in the case of comparatively large resources which are not required to remain permanently in memory.
Template Resources and Definition Resources
The 'WIND' resource defined above is an example of a template resource. A template resource defines the characteristics of a desktop object, in this case a window's size, location, etc., and the window definition function (specified by the constant kWindowDocumentProc) to be used to draw it. Definition functions, which determine the look and behaviour of a desktop object, are executable code segments contained within another kind of resource called a definition resource.
Resources in Action
The Resource Map
Your application file's resource fork contains, in addition to the resources you have created for your application, an automatically created resource map. The resource map contains entries for each resource in the resource fork.
When your application is launched, the system first gets the Memory Manager to create the application heap and allocate a block of master pointers at the bottom of the heap. The Resource Manager then opens your application file's resource fork and reads in the resource map, followed by those resources which have the resPreload attribute set.
The handles to the resources which have been loaded are stored in the resource map in memory. The following is a diagrammatic representation of a simple resource map in memory immediately after the resource map, together with those resources with the preload attribute set, have been loaded.
Type |
ID |
Attributes |
Handle |
Preload |
Lock |
Purgeable |
MENU |
128 |
X |
|
|
123C |
WIND |
128 |
|
|
X |
NULL |
PICT |
128 |
|
|
X |
NULL |
PICT |
129 |
|
|
X |
NULL |
Note that the handle entry in the resource map contains NULL for those resources that have not yet been loaded. Note also that this handle entry is filled in only when a resource is loaded for the first time, and that that entry remains even if a purgeable resource is later purged by the Memory Manager.
Reading in Non-Preloaded Resources
Some system software managers use the Resource Manager to read in resources for you. Using the 'WIND' resource listed in the above resource map as an example, when the Window Manager function GetNewCWindow is called to create a new window (specifying 128 as the resource ID), GetNewCWindow, in turn, calls the Resource Manager function GetResource. GetResource loads the resource (assuming that it is not currently in memory), returns the handle to GetNewCWindow, and copies the handle to the appropriate entry in the resource map. This is an example of an indirect call to the Resource Manager.
Other resources are read in by direct calls to the Resource Manager. For example, the 'PICT' resources listed in the above example resource map would be read in by calling another of the Get... family of resource-getting functions directly, for example:
#define rPicture1 128
#define rPicture2 129
...
PicHandle pic1Hdl;
PicHandle pic2Hdl;
...
pic1Hdl = GetPicture(rPicture1);
pic2Hdl = GetPicture(rPicture2);
Once again, and assuming that the resources have not previously been loaded, the handle returned by each GetPicture call is copied to the appropriate entry in the resource map.
Purgeable Resources
When a resource which has the resPurgeable attribute set has been loaded for the first time, the handle to that resource is copied to the appropriate entry in the resource map in the normal way. If the Memory Manager later purges the resource, the master pointer pointing to that resource is set to NULL by the Memory Manager but the handle entry in the resource map remains. This creates what is known as an empty handle.
If the application subsequently calls up the resource, the Resource Manager first checks the resource map handle entry to determine whether the resource has ever been loaded (and thus whether a master pointer exists for the resource). If the resource map indicates that the resource has never been loaded, the Resource Manager loads the resource, returns its handle to the calling function, and copies the handle to the resource map.
If, on the other hand, the resource map indicates that the resource has previously been loaded (that is, the handle entry in the resource map contains the address of a master pointer), the Resource Manager checks the master pointer. If the master pointer contains NULL, the Resource Manager knows that the resource has been purged, so it reloads the resource and updates the master pointer. Having satisfied itself that the resource is in memory, the Resource Manager returns the resource's handle to the application.
Problems with Purgeable Resources
Using purgeable resources optimises heap space; however, misuse of purgeable resources can crash an application. For example, consider the following code example, which loads two purgeable 'PICT' resources and then uses the drawing instructions contained in those resources to draw each picture.
pic1Hdl = GetPicture(rPicture1); // Load first 'PICT' resource.
pic2Hdl = GetPicture(rPicture2); // Load second 'PICT' resource.
if(pic1Hdl) // If the handle to first resource is not NULL ...
DrawPicture(pic1Hdl,&destRect); // ... draw the first picture.
if(pic2Hdl) // If the handle to second resource is not NULL
DrawPicture(pic2Hdl,&destRect); // ... draw the second picture.
GetPicture is one of the many functions that can cause memory to move. When memory is moved, the Memory Manager may purge memory to obtain more heap space. If heap space is extremely limited at the time of the second call to GetPicture, the first resource will be purged by the Memory Manager, which will set the master pointer to the first resource to NULL to reflect this condition. The variable pic1Hdl will now contain an empty handle. Passing an empty handle to DrawPicture just about guarantees a system crash.
There is a second problem with this code. Like GetPicture, DrawPicture also has the potential to move memory blocks. If the second call to GetPicture did not result in the first resource being purged, the possibility remains that it will be purged while it is being used (that is, during the execution of the DrawPicture function).
To avoid such problems when using purgeable resources, you should observe these steps:
- Get (that is, load) the resource only when it is needed.
- Immediately make the resource unpurgeable.
- Use the resource immediately after making it unpurgeable.
- Immediately after using the resource, make it purgeable.
The following revised version of the above code demonstrates this approach:
pic1Hdl = GetPicture(rPicture1); // Load first 'PICT' resource.
if(pic1Hdl) // If the resource was successfully loaded ...
{
HNoPurge((Handle) pic1Hdl); // make the resource unpurgeable ...
DrawPicture(pic1Hdl,&destRect); // draw the first picture ...
HPurge((Handle) pic1Hdl); // and make the resource purgeable again.
}
pic2Hdl = GetResource(rPicture2); // Repeat for the second 'PICT' resource.
if(pic2Hdl)
{
HNoPurge((Handle) pic2Hdl );
DrawPicture(pic2Hdl,&destRect);
HPurge((Handle) pic2Hdl );
}
Note that this procedure only applies when you use functions which get resources directly (for example GetResource, GetPicture, etc.). It is not required when you call GetResource indirectly (for example, when you call the Window Manager function GetNewCWindow) because functions like GetNewWindow know how to treat purgeable resources properly.
Note also that LoadResource may be used to ensure that a previously loaded, but purgeable, resource is in memory before an attempt is made to use it. If the specified resource is not in memory, LoadResource will load it. The essential difference between LoadResource and the Get... family of resource-getting functions is that the latter return a handle to the resource (loading the resource if necessary), whereas LoadResource takes a handle to a resource as a parameter and loads the resource if necessary.
Releasing Resources
When you have finished using a resource loaded by a function which gets resources directly, you should call the appropriate function to release the memory associated with that resource. For example, ReleaseResource is used in the case of generic handles obtained with the GetResource function. ReleaseResource frees up all the memory occupied by the resource and sets the resource's handle in the resource map to NULL.
You do not need to be concerned with explicitly releasing resources loaded indirectly (for example, by a call to GetNewCWindow). Using the case of a window resource template as an example, the sequence of events following a call to GetNewCWindow is as follows:
- GetNewCWindow calls GetResource to read in the window resource template whose ID is specified in the GetNewCWindow call.
- A relocatable block is created for the template resource and marked as purgeable, as specified by the resource's attributes. (You should always specify window template resources as purgeable.)
- The window template's block is then temporarily marked as unpurgeable while:
- A block is created for an opaque data structure known as a window object.
- Data is copied from the resource template into the window object.
- The window template's block is then marked as purgeable.
Resource Manager Errors
The error code resulting from the last call to a Resource Manager function may be retrieved by calling the function ResError. Some of the error codes which may be returned by ResError are as follows:
Value |
Constant |
Description |
0 |
noErr |
No error occurred. |
-192 |
resNotFound |
Resource not found. |
-193 |
resFNotFound |
Resource file not found. |
Main Memory Manager Data Types and Functions
Data Types
typedef char *Ptr; // Pointer to nonrelocatable block.
typedef Ptr *Handle; // Handle to relocatable block.
typedef long Size; // Size of a block in bytes.
Functions
Allocating and Releasing NonRelocatable Blocks of Memory
Ptr NewPtr(Size byteCount);
Ptr NewPtrClear(Size byteCount);
void DisposePtr(Ptr p);
Allocating and Releasing Relocatable Blocks of Memory
Handle NewHandle(Size byteCount);
Handle NewHandleClear(Size byteCount);
Handle NewEmptyHandle(void);
Handle NewEmptyHandleSys(void);
void DisposeHandle(Handle h);
Changing the Sizes of Nonrelocatable and Relocatable Blocks
Size GetPtrSize(Ptr p);
void SetPtrSize(Ptr p,Size newSize);
Size GetHandleSize(Handle h);
void SetHandleSize(Handle h,Size newSize);
Setting the Properties of Relocatable Blocks
void HLock(Handle h);
void HUnlock(Handle h);
void HPurge(Handle h);
void HNoPurge(Handle h);
SInt8 HGetState(Handle h);
void HSetState(Handle h,SInt8 flags);
Managing Relocatable Blocks
void EmptyHandle(Handle h);
void ReallocateHandle(Handle h,Size byteCount);
Handle RecoverHandle(Ptr p);
void ReserveMem(Size cbNeeded);
void MoveHHi(Handle h);
void HLockHi(Handle h);
Manipulating Blocks of Memory
void BlockMove(const void *srcPtr,void *destPtr,Size byteCount);
void BlockMoveData(const void *srcPtr,void *destPtr,Size byteCount);
OSErr PtrToHand(const void *srcPtr,Handle *dstHndl,long size);
OSErr PtrToXHand(const void *srcPtr,Handle dstHndl,long size);
OSErr HandToHand(Handle *theHndl);
OSErr HandAndHand(Handle hand1,Handle hand2);
OSErr PtrAndHand(const void *ptr1,Handle hand2,long size);
Allocating Master Pointers
void MoreMasterPointers(UInt32 inCount);
Accessing Memory Conditions and Freeing Memory
long FreeMem(void); // Useful under Mac OS 8/9,
// almost meaningless under Mac OS X.
void PurgeMem(Size cbNeeded); // Useful under Mac OS 8/9,
// almost meaningless under Mac OS X.
Size MaxMem(size *grow); // Useful under Mac OS 8/9,
// almost meaningless under Mac OS X.
long MaxBlock(void);
void PurgeSpace(long *total,long *contig);
long StackSpace(void); // Not very useful in Carbon.
Size CompactMem(Size cbNeeded);
Checking for Errors
OSErr MemError(void);
Main Resource Manager Constants, Data Types, and Functions
Constants
Resource Attributes
resSysHeap = 64 System or application heap?
resPurgeable = 32 Purgeable resource?
resLocked = 16 Load it in locked?
resProtected = 8 Protected?
resPreload = 4 Load in on OpenResFile?
resChanged = 2 Resource changed?
Data Types
typedef unsigned long FourCharCode;
typedef FourCharCode ResType;
Functions
Reading Resources Into Memory
Handle GetResource(ResType theType,short theID);
Handle Get1Resource(ResType theType,short theID);
void LoadResource(Handle theResource);
Disposing of Resources
void ReleaseResource(Handle theResource);
Checking for Errors
short ResError(void);
|