ADC Home > Reference Library > Technical Notes > Legacy Documents > Carbon >
Important: This document is part of the Legacy section of the ADC Reference Library. This information should not be used for new development.
Current information on this Reference Library topic can be found here:
|
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.
This section describes some useful techniques you can
employ to ensure that the weirdnesses of
FSpOpenResFile
does not bite your software. These
are listed with the most recommended (and easiest to
implement) first.
If 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.
static SInt16 gResourceResFile = 0; static UInt32 gResourceUsageCount = 0; static OSErr StartUsingResources(ConstFSSpecPtr fss) { OSErr err; SInt16 tmpResFile; err = noErr; if (gResourceUsageCount == 0) { tmpResFile = FSpOpenResFile(fss, fsRdWrPerm); err = ResError(); if (err == noErr) { gResourceResFile = tmpResFile; } } if (err == noErr) { gResourceUsageCount += 1; } return err; } static void StopUsingResources(void) { gResourceUsageCount -= 1; if (gResourceUsageCount == 0) { CloseResFile(gResourceResFile); gResourceResFile = 0; } } |
Extending this technique for more than one resource file is left as an exercise to the developer.
In 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
fsRdPerm
. If you do this, you will always get a
new resource reference number that you can safely close.
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'.
TopMapHndl
In situations where you don't know whether a resource
file has already been opened by the current process and you
must open the resource file read/write, the best technique
is to monitor the TopMapHndl
low memory global
to see if it changes around your call to
FSpOpenResFile
. If this global changes, a new
resource map has been added to the top of the resource
chain, and you are responsible for closing it. If the global
does not change, an existing resource map reference number
was returned and you should not close it.
The following snippet illustrates this technique.
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); } |
It's important to remember that this technique is only necessary if you need to open the file read/write and you don't know whether the file is already open by the current process. As such, this technique is needed most by non-application code -- such as system extensions, shared libraries, application plug-ins, etc -- but it may also be useful for application code is running in strange environments, such as a Standard File filter function.
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; } |
If you open the same resource file twice, you are vulnerable to a number of strange behaviors of the Resource Manager, including:
FSpOpenResFile
returning an existing
resource file reference number rather than opening a new
resource file reference number.FSpOpenResFile
returning a read-only
resource file reference number, even though you
explicitly asked for read/write access.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.
Inside Macintosh: More Macintosh Toolbox, Chapter 1 Resource Manager
DTS Technote FL 37 You Want Permission to do What?!!
Acrobat version of this Note (72K). |
|