Technote 1120Opening Resource Files Twice Considered Hard?Apple Developer Technical Support |
CONTENTSDescribing the
Problem |
Most Mac OS
programmers do not consider This Note describes the exact
behavior of All Mac OS programmers who use the Resource Manager should read the Summary section of this Note, just to familiarize themselves with the problem. In addition, programmers who are writing non-application code should carefully read the entire Note to ensure maximum compatibility for their code. |
Describing the ProblemInside
Macintosh: More Macintosh Toolbox (p1-60) has the
following to say about the behavior of
This statement is mostly true, but it certainly is not the whole truth. The exact situation is a lot more complex. Complicating FactorsThe complicating factors when opening a resource file include:
These complicating factors combine to make opening a resource file potentially much more complicated than it seems on first examination. Simplifying the ProblemFortunately, not all of the above factors actually complicate the problem. We can simplify the analysis by noting the following:
These points combine to make it much easier to describe the exact situation. What remains is to describe the behavior in each of the remaining cases. |
|
Permissions Used |
Permissions Used |
Same |
Different | |
|
|
|
|
|
|
|
Success R-O |
Success R-O |
Failure -54 |
|
|
Success R-O |
Success R-O |
Failure -54 |
|
|
Partial R/W |
Failure -49 |
Failure -54 |
|
|
|
|
|
|
|
Success R-O |
Success R-O |
Success R-O |
|
|
Success R-O |
Success R-O |
Success R-O |
|
|
Partial R-O |
Failure -49 |
Success R-O |
|
|
|
|
|
|
|
Success R-O |
Success R-O |
Failure -54 |
|
|
Success R-O |
Success R-O |
Failure -54 |
|
|
Partial R/W |
Failure -49 |
Failure -54 |
The first column gives the permission value used the
first time the file was opened. The second column gives the
permission value for this call to
FSpOpenResFile
.
The remaining three columns describe the behavior in each of the three important cases:
Each cell is labelled with a pair of items. The first item is the outcome of the call, as defined above. The second item is either the permissions associated with the returned resource file reference number (for outcomes Success and Partial ) or the error code returned (for outcome Failure ).
There are a number of important observations to be made about the above table:
FSpOpenResFile
will return the resource file
reference number of an existing resource map. If you're
not prepared for this (and you close the resource map
after using it, say), you will find yourself in a world
of pain. For example, it's common for developers to
accidentally close the resource map of their own
application, or the system resource map.
fsRdPerm
and you open it with
fsRdWrPerm
, the resource file reference
number returned has read-only permissions. You can check
for this situation using the
code shown below.
fsRdWrPerm
permissions to a
file which has already been opened with
fsRdPerm
by another machine will succeed
(!), but will give you a read-only resource file
reference number.
The last problem with opening the same resource file twice is intrinsic to the design of the Resource Manager and very hard to guard against. When you open a resource file, the Resource Manager loads a catalog of all the resources (the resource map) into your heap, and uses that map to locate the data for each resource resides in the resource file.
When changing the resource map, the Resource Manager does not coordinate between the various processes that might have the resource file open. While the Resource Manager prevents you from opening two read/write resource file reference numbers to the same resource file, it does not stop you from having a read-only and a read/write resource file reference number simultaneously.
This can cause serious problems in the following situation:
UpdateResFile
to write those changes back to
the disk. This can cause the position of various
resources in the resource file to change quite
dramatically.
The restriction is described quite well in the Special Considerations section of the description of FSpOpenResFile in Inside Macintosh: More Macintosh Toolbox, but that description is worth reiterating while we're on the subject of opening resource files twice.
Cookbook SolutionsThis section describes some useful techniques you can
employ to ensure that the weirdnesses of
Open Resource Files OnceIf you're writing normal application-level code, it's easy to remember whether your process has already opened a resource file and avoid opening it twice. The following snippet shows a simple example of this.
Extending this technique for more than one resource file is left as an exercise to the developer. Open Resource Files Read OnlyIn situations where you don't know whether a resource
file has already been opened by the current process (in
system extension code, for example), the easiest solution is
to always open the resource file read-only, i.e. using
A further refinement of this solution is to open, read, and close the resource file quickly, without yielding time to other processes in between. This helps prevent another process from modifying the file while you're reading it, and minimizes your vulnerability to the trickiest gotcha described above. This refinement is only useful in certain situations, but is definitely one to keep in your 'cookbook'. Check
|
static void SafeOpenResFileReadWrite(ConstFSSpecPtr fss) { OSErr err; SInt16 oldResFile; Handle oldTopMap; SInt16 resFile; Boolean shouldClose; oldResFile = CurResFile(); oldTopMap = LMGetTopMapHndl(); resFile = FSpOpenResFile(fss, fsRdWrPerm); err = ResError(); if (err == noErr) { shouldClose = (LMGetTopMapHndl() != oldTopMap); // do the stuff with the resource file if (shouldClose) { CloseResFile(resFile); } } UseResFile(oldResFile); } |
CurResFile
Regardless of which of above techniques you use, it's
always a good idea to bracket your calls to
FSpOpenResFile
with calls to
CurResFile
and UseResFile
to
ensure that your code does not accidentally change the
current resource map. The above snippets also illustrates
this technique.
If you need to write to a resource file and you are not sure whether that file has already been opened, it pays to examine the resource file reference number to ensure that it supports read/write access. While having a read/write resource file reference number is not a guarantee that writing to the file will succeed, it's a good idea to check this as the first step.
You can check whether a resource file reference number is
read/write by calling the File Manager routine
PBGetFCBInfoSync
and looking at bit 8 of
ioFCBFlags
. The following snippet demonstrates
this technique.
static Boolean IsResourceFileRefNumWritable(SInt16 rsrcRefNum) { Boolean result; FCBPBRec fcbPB; fcbPB.ioNamePtr = nil; fcbPB.ioVRefNum = 0; fcbPB.ioRefNum = rsrcRefNum; fcbPB.ioFCBIndx = 0; if ( PBGetFCBInfoSync(&fcbPB) == noErr ) { result = ((fcbPB.ioFCBFlags & (1 << 8)) != 0); } else { result = false; } return result; } |
SummaryIf you open the same resource file twice, you are vulnerable to a number of strange behaviors of the Resource Manager, including:
The best way to guard against these problems is to avoid opening a resource file twice. If this is unavoidable, this Note suggests a number of approaches you can use to minimize your vulnerability. |
Thanks to Brian Bechtel, Pete Gontier, and Jim Luther.