OpenDoc has its own memory management system, but OpenDoc part editors also need
to interact with the Macintosh Toolbox, which often does memory management using its
own system. This column will point out potential pitfalls resulting from the
interaction of these two systems, and suggest strategies to avoid them.
OpenDoc has adopted the well-tested Memory Manager from the MacApp and Bedrock
frameworks for its own use. This memory manager was designed to provide fast and
efficient memory allocation to the framework, and since OpenDoc's memory
requirements are similar to those of a framework, it's natural for OpenDoc to reuse
the code that has served the framework so well. The OpenDoc Memory Manager (as
we'll call it here) is installed with OpenDoc as a shared library and handles most of the
memory allocation and deallocation in an OpenDoc process.
There are several reasons for OpenDoc to have its own memory manager:
The OpenDoc Memory Manager defines a low-level procedural API, which is described
in the "Memory Management" section of Appendix A in the OpenDoc Cookbook. An
OpenDoc utility library named ODMemory (provided as source code) organizes the
low-level API into high-level functions. The major difference between the underlying
API and the "wrapper" ODMemory API is that the latter signals an exception when an
error condition is encountered. For more on exception handling in OpenDoc, see
"OpenDoc Exception Handling."
______________________________
OPENDOC EXCEPTION HANDLING
Handling exceptions in OpenDoc is a large topic, which will only be touched on
here. Readers should refer to the "Exception Handling" section of Appendix A in
the OpenDoc Cookbook for more details.
In a nutshell, OpenDoc allows developers to choose their own exception
mechanisms while providing a convenient utility to enable these exception
mechanisms to work with SOMobjects(TM) for Mac OS (the Apple
implementation of the IBM SOM(TM) technology), which underlies OpenDoc.
SOM propagates exceptions through the environment parameter (commonly
known as the ev parameter). It's illegal to throw an exception out of a SOM
method. Instead, the exception code should be stuffed into the ev parameter and
returned to the caller. The caller should examine the ev parameter to see
whether an error has been signaled in the called function. Since this checking
needs to be done after every SOM method invocation, OpenDoc provides a
utility to automatically check the ev parameter. If an error has been signaled,
the utility will use the chosen exception mechanism (through the use of
macros) to propagate the exception.
The macros for the SOM exception handlers are prefixed with "SOM_":
SOM_TRY, SOM_CATCH_ALL, and SOM_ENDTRY. These macros should not be
confused with TRY, CATCH_ALL, and ENDTRY. The non-SOM exception handler
macros do not propagate the exception automatically unless RERAISE is called
explicitly in the catch block, and they can't be used to propagate an exception
across a SOM boundary.
______________________________
To ensure optimum usage of memory, part developers should use the OpenDoc Memory
Manager API or the ODMemory utility library to satisfy their memory needs. The only
exception is when the part editor is interacting with a Toolbox manager that requires
memory to be allocated in a certain memory heap. We'll go into more detail about this
in a moment.
The OpenDoc Memory Manager allocates fixed-sized, nonrelocatable blocks of memory
and organizes them into heaps. The memory allocated for these heaps may come from
the application heap, temporary memory, or the system heap. These memory blocks
(called allocation segments) are subdivided into smaller memory blocks as memory
requests are made by OpenDoc objects and part editors. When MMAllocate or ODNewPtr
is called to allocate a block of memory, the OpenDoc Memory Manager returns a
pointer to one of these memory blocks in an allocation segment.
When an OpenDoc process is started up, the OpenDoc Memory Manager allocates a
small amount of memory for a heap, which becomes the default heap. Clients of the
OpenDoc Memory Manager can create extra heaps and make any of these the default
heap.
If a new block is requested and no allocation segments have enough free space to satisfy
the request, the OpenDoc Memory Manager will trigger the creation of another
allocation segment, which has the effect of growing the heap. Similarly, when all
blocks in an allocation segment are freed, the segment is freed as well, shrinking the
heap.
The OpenDoc Memory Manager and ODMemory utility also provide a way to allocate
relocatable blocks. These blocks are not suballocated from the allocation segments;
instead, the OpenDoc Memory Manager allocates them directly from the same operating
system heap zone that the OpenDoc heap allocates segments from.
Typically, several OpenDoc part editors run in the single process associated with a
document. There's no way to determine how many part editors are going to be used in a
document and how much memory each part editor requires. Therefore, it's impossible
to know how big the memory partition of the process should be before opening the
OpenDoc document. As described above, the OpenDoc Memory Manager has its own
allocation scheme, which is not limited by the application partition, so the memory
partition becomes less significant. (Currently the default heap is allocated from
temporary memory, but this may change in the future.) The elimination of the need
for the end user to understand the concept of application heap and adjust the memory
partition is one of the design goals of OpenDoc.
However, users familiar with OpenDoc might remember that the Document Info dialog
allows them to change the partition size of the process. You might ask, "If the OpenDoc
Memory Manager does what it claims to, why do I need to adjust the memory
partition?"
Even though most of the memory allocation is done through the OpenDoc Memory
Manager, Toolbox managers do allocate memory in the application heap, and the amount
required varies considerably depending on the size of the data manipulated and the
operations performed. Changing the memory partition is needed to accommodate these
cases.
When a document is created, it's opened into a process of a default size. Users can
change the default size for the document by using the Document Info dialog. There's also
a desktop utility called Infinity OpenDoc Sizer that's capable of changing either the
partition size of a particular document (without first having to open it) or the default
partition size used by all documents that don't already have custom partitions. It's
available in the Developer Release area of the OpenDoc Web site
(http://www.opendoc.apple.com) and accompanies this column on this issue's CD and
develop's Web site.
As your code makes calls into the Macintosh Toolbox, you'll find several places where
the Toolbox allocates memory for you. Generally, this memory is allocated out of the
current heap, which is usually the application heap. Since the size of an OpenDoc
document's application heap is limited (the default heap size leaves only about 100K of
space free after OpenDoc itself is loaded), you need to be careful when calling Toolbox
routines that allocate in that heap. With some care, you can control your allocations so
that your users won't have to increase your document's heap size in order to use your
part.
When you're dealing with resources in particular, there are a few techniques you can
use to handle memory allocations. Standard resource access routines such as
GetResource will generally cause the associated memory to be allocated in the
application heap. If you're using a resource for only a short period of time and it's
fairly small, you can continue to use these routines to access it and load it into the
application heap.
For larger resources, however, this won't work. Luckily, OpenDoc provides the utility
library UseRsrcM to help, as described in the "Resource Handling" section of Appendix
A in the OpenDoc Cookbook. The utility routine ODReadResource allows you to load
resources from your part's shared library file into temporary memory. This works
by determining the size of the resource, allocating a relocatable block of this size in
temporary memory, and using ReadPartialResource to load the resource directly into
that block. (Note, however, that resources read in by ODReadResource are detached;
you cannot, for instance, call ChangedResource to write out modifications to them.
Also, each call to ODReadResource will return a new copy of the resource.) If you need
to access large resources from files other than your part -- for instance, for a
sound-editing part that needs to load an 'snd ' resource from another file -- you can
use this same technique yourself. A part editor must also ensure that its resource file
is in the resource chain before accessing its resources (see "Resource File Access").
______________________________
RESOURCE FILE ACCESS
In an OpenDoc environment, parts must share access to system services that
applications normally own exclusively. This adds some complications to the
programming model you're probably used to.
One such shared service is the resource chain. Since there are potentially
many parts all working in the active document, the resource chain must be
shared between them. Also, because OpenDoc parts are shared libraries, the
resources in your part's file aren't automatically available like those in an
application.
The utility library UseRsrcM facilitates making your resource file available
and accessing resources from it. To open and initialize access to your shared
library's resource file, you call InitLibraryResources from your CFM
initialization routine. You also call CloseLibraryResources from your CFM
termination routine to close the resource file when you're done with it.
To access a resource from your part's shared library, you must first call
BeginUsingLibraryResources. This adds the part's resource file to the
resource chain and sets the top of the chain to that file (so that calls such as
Get1Resource will retrieve resources from the correct file). After reading or
writing the necessary resource, you call EndUsingLibraryResources to
remove the file from the resource chain. For C++ users, a stack-based class
named CUsingLibraryResources is provided to do this for you automatically.
There are a couple of implications about using this mechanism for handling the
resource chain. Because Resource Manager routines are available only inside a
Begin.../End... block, you must make sure your code and the Toolbox aren't
trying to manipulate resources at other times. You also have to be careful that
LoadResource isn't called on a purged resource from a file that's not in the
chain.
Other things, such as icons or Balloon Help in menus (which are loaded while
the menu is pulled down and the part isn't in control), can also cause
problems. If you understand the relationship between your resource file and
the resource chain, however, you can work around these potential pitfalls.
______________________________
There are times when the Toolbox loads resources for you. In these cases, you
generally can't get them to be loaded into temporary memory. In several cases, the
resources are small or are allocated for only a short time, so there isn't much to
worry about. For instance, when accessing resources such as string lists ('STR#'),
cursors, and icon families, you can continue to use the normal Toolbox routines. In
these cases, the resource is often accessed once and left around in memory. Therefore,
you'll want to make sure these resources are marked as purgeable so that they can be
deallocated automatically when more space is needed.
There are other times when larger resources (such as pictures) are being accessed
and problems can crop up where you might not expect them. For instance, if you have a
large picture that's referenced by a dialog item (like that 24-bit rendered image for
the background of your About box) and there isn't sufficient memory available when
the dialog is displayed, the picture won't be shown. One solution to this is to create
your own heap zone, as discussed later. Another is to create a user item procedure for
the picture, handling the memory allocation and spooling in of the picture yourself.
For resources such as menus and definition procedures (WDEFs, CDEFs, and MDEFs),
there is little you can safely do. Most of these types of resources, although they persist
for a long time, are fairly small, so having them allocated in the application heap isn't
terrible.
Another commonly used piece of memory allocated by the Toolbox, but in this case not
resource-based, is a region. If you're doing many region operations and the regions
aren't being kept around, you can continue to use NewRgn to allocate them in the
application heap. However, if you're keeping regions around for long periods of time,
there's an ODMemory utility routine, ODNewRgn, that you can call to allocate your
regions from the OpenDoc heap.
In some cases, the Toolbox allows you to specify that a memory allocation come from
temporary memory rather than the application heap. Probably the most common
example of this is a GWorld. Passing the useTempMem flag to NewGWorld or
UpdateGWorld will cause it to allocate the PixMap's buffer in temporary memory
rather than in the application heap. Use these opportunities when they present
themselves.
Also, if you're allowed to specify the location of memory used by the Toolbox, such as
for the WindowRecord in calls to NewWindow and the sound channel in SndNewChannel,
you should take these opportunities to allocate the memory with ODNewPtr rather than
allowing the Toolbox to do the allocation.
Another technique that you might consider for dealing with large or long-term
resources is loading them into the system heap. This can be done by setting the "system
heap" flag in the resources' attributes. This has the advantage of being easy to do and
working even for resources allocated by the Toolbox. The disadvantage is that
increasing the system heap's size can be bad for performance if virtual memory is
enabled. Since the system heap is always paged into real memory space in System 7, a
large system heap means there isn't much space in RAM available for paging in the
rest of memory.
Yet another technique that can sometimes be useful is to create your own heap zone. To
do this, create a block in temporary memory using ODNewHandle, lock it, and create a
heap inside it by calling InitZone. This technique shouldn't be used for just any
allocation, but only if other methods don't work and if the allocation is ephemeral. One
example we mentioned before where this might be useful is for a dialog box with large
picture items. Note that you'll have to make sure the heap is large enough to hold not
only the pictures but also the other dialog-related resources, and you'll want to leave
some room to spare. Also, you probably wouldn't want to have more than one of these
heaps allocated at a time. When locking any handle in temporary memory, make sure
you unlock it again as soon as possible.
Other parts of the Mac OS, such as QuickTime, will require you to use these and other
techniques to deal effectively with memory. (In the case of QuickTime, you can use
SetZone to switch to the system heap.) In such cases, it's a good idea to use a tool such
as Metrowerks' ZoneRanger to examine memory allocated in the application heap and,
if problems are found, look for ways to move allocations elsewhere.
In the future, a new system memory manager will allow for heaps that can grow. When
this becomes available, many of these contortions will become unnecessary. In the
meantime, however, it's important to remember that you're sharing the application
heap with other clients and to act accordingly.
RELATED READING
For more information on the memory manager in OpenDoc and the utility
libraries mentioned in this column, check out the following references:
TROY GAUL (tgaul@apple.com) has been writing OpenDoc parts -- er, Live Objects
-- since starting at Apple last May. Having created the Infinity Windoid WDEF in
1991, he has since appeared in more About boxes than Elvis.*
VINCENT LO is Apple's technical lead for OpenDoc. When he's not dealing with Live
Objects, he's frequently spotted at fine dining establishments in the San Francisco Bay
Area. One of his dreams is to open up a Chinese restaurant in Italy.*
Thanks to Jens Alfke, Dave Bice, and Steve Smith for reviewing this column.*