Important: The information in this document is obsolete and should not be used for new development.
Recipes--Failure Handling
The recipe and sample code in this section demonstrate how to avoid problems with register variables in failure-handling code.Recipe--Avoiding Register Variables in Failure Handlers
A C++ compiler may optimize code by storing local variables in registers. This practice can cause problems during failure handling because MacApp calls the C library routinelongjmp
, which restores registers to their values before the error occurred. A variable you wish to use in your recovery code may be set to a meaningless value when the previous value of its register is restored.To ensure that a variable is not stored in a register, you use the MacApp macros
MAVolatile
andMAVolatileInit
. These macros are introduced beginning on page 58. They can be used with variables of any type and are guaranteed to work on any compiler, even those that don't support thevolatile
keyword.For
MAVolatile
, you supply a variable and a type, as in the following line:
MAVolatile(long, raisinsPerBox);This guarantees that the variable raisinsPerBox will not be stored in a register.For
MAVolatileInit
, you supply a type, a variable, and an initial value. The following code sample, from theGetFilesList
method of MacApp'sTFilesCommand
class, usesMAVolatileInit
for both of the lists created in the code block. It also demonstrates embedded failure handling with an iterator (described in "Failure Handling Embedded in Objects," beginning on page 59).
MAVolatileInit(THandleList*, aHandleList, NULL); MAVolatileInit(TList*, aFileList, NULL); FailInfo outerFi; Try(outerFi) { aHandleList = new THandleList; aHandleList->IHandleList(); FailInfo innerFi; Try(innerFi) { aFileList = NewList(); . . . // Extra block to control scope of failure handler embedded in iterator. { CHandleIterator iter(aHandleList); for (Handle item = iter.FirstHandle(); iter.More(); item = iter.NextHandle()) { MAVolatileInit(TFile*, aFile, NULL); aFile = gApplication->DoMakeFile(fIdentifier); FailInfo oneMoreFi; Try(oneMoreFi) { FailOSErr(...); . . . oneMoreFi.Success(); // No error, so remove handler. } else { // For a failure here, just free the file, if // necessary, then resignal to do any added cleanup. aFile = (TFile*)FreeIfObject(aFile); oneMoreFi.ReSignal(); } aHandleList->Delete(item);// Delete item from the list. item = DisposeIfHandle(item); } } // End extra block. Iterator's destructor calls success // for its failure handler. innerFi.Success(); // No error, so remove handler. } else // Recover. { // If we failed here, we may need to free both lists, // then resignal, in case there is any additional cleanup. if (aHandleList) aHandleList->FreeList(); if (aFileList) aFileList->FreeList(); innerFi.ReSignal(); } outerFi.Success(); // No error, so remove handler. } else { // If we couldn't create a handle list, just free the command, // then resignal, in case there is any additional cleanup. this->Free(); outerFi.ReSignal(); }This code creates two lists within a failure-handling block. If an error occurs, the lists need to be freed. If the list variables were stored as register values, they could be corrupted when the failure handler restores the previous state of the registers--any attempt to use them in a recovery block could be disastrous. TheMAVolatileInit
macro eliminates this danger by preventing the list variables from being stored as register values.A further complication is added by the user of an iterator. An iterator has an embedded failure handler that is installed by the iterator's constructor method and removed by its destructor method. By declaring the iterator inside its own code block in the middle of this method, we can control when the iterator goes out of scope and has its failure handler removed. As a result, we can guarantee that the iterator's failure handler isn't called for an error occurring outside the loop in which the iterator is used.