Exception Handling (Except)
This section describes the OpenDoc exception-handling utility, which resides in the files Except.h and Except.cpp. You can use the exception-handling utility to generate and handle your own exceptions as well as respond to any exceptions generated as a result of calls to OpenDoc. The exception-handling utility implements a simple throw-and-catch exception-handling scheme, similar to those found in some application frameworks and development environments.Using the Exception-Handling Utility
The use of the exception-handling utility is optional. However, if you don't use it you must check the environment variable (ev) after every SOM call you make and handle it appropriately. The exception-handling utility facilitates this requirement, as described in "Handling SOM Exceptions".To use the exception-handling utility, you add the file Except.cpp to your project (if you use a project-based system) or makefile (if you use MPW) and include Except.h in your source files.
If you're building your project in debug mode (the symbol
- IMPORTANT
- You must include the header Except.h in your source files before including the headers of any SOM classes (.xh files in C++). If you precompile any SOM headers, you should also precompile Except.h and put it first among the headers that you precompile.
![]()
ODDebugis defined as1), then Except.cpp will call functions from Crawl.cpp, and you'll need to add that source file to your project or makefile. Crawl.cpp in turn depends on ToolLibs.o (68K) or PPCToolsLib.o (PowerPC), which are part of your development system.The Exception-Handling Scheme
In the kind of exception system implemented by the exception-handling utility, an error is signaled by being thrown, or made known to the system, by using the macro callTHROWor one of its variants. The stack then unwinds back to the point where a calling function has set up an error handler for this exception. The handler then catches, or responds to, the exception; it performs whatever recovery or cleanup is necessary. The error handler can then allow execution to continue or--more commonly--it can reraise the exception, throwing it back to the next exception handler on the stack.An exception handler in this scheme is defined as a block of code, delimited by the macro calls
TRY,CATCH_ALL, andENDTRY(or their variants). Only exceptions that are thrown within the scope of theTRY/ENDTRYpair of a handler can be handled by that handler.The following is an example of the most basic use of this kind of exception system. It shows the exception handling involved with the fail-safe allocation of a pair of handles:
ODHandle MyNewHandle(ODSize size) { OSErr err; ODHandle h = NewTempHandle(size,&err); THROW_IF_ERROR(err); return h; }This function (MyNewHandle) throws an exception if theNewTempHandlefunction returns a nonzero error.MyNewHandledoes not itself catch any exceptions. The next function (TwoHandles) usesMyNewHandleto allocate the pair of handles:
void TwoHandles( ) { Handle h1, h2; h1 = MyNewHandle(10000); TRY{ h2 = MyNewHandle(10000); }CATCH_ALL{ ODDisposeHandle(h1); RERAISE; }ENDTRY ... }InTwoHandles, if the first call toMyNewHandlefails, it throws an error. SinceTwoHandleshas not set up an exception handler around that call, the exception is thrown out ofTwoHandlesand into its caller, and so on up the stack until an exception handler is found. None of the code shown here handles that exception.If the first call succeeds, however, execution passes to the second call to
MyNewHandle, which is inside an exception handler. If this call fails and throws an exception, the exception is caught by the exception handler, and the block afterCATCH_ALLexecutes. This block cleans up by disposing of the first allocated handle (h1), preventing a memory leak. Thus, either both handles are allocated or neither is.After
h1is disposed of, the exception handler callsRERAISE, which re-throws the same exception up the stack until the next enclosing exception handler is found. (If the handler hadn't calledRERAISE, execution would have fallen out of the exception handler to the statement followingENDTRY.)If the second call to
MyNewHandlesucceeds, execution falls out of the entire exception handler, skipping theCATCH_ALLblock entirely (since there was no exception) and ending up at the statement immediately followingENDTRY.Throwing Exceptions
In the OpenDoc exception-handling utility, you throw an exception by calling theTHROWmacro or one of its variants. This causes execution to jump immediately to the closest exception handler below it on the stack. These are the variants ofTHROW:THROW
THROWthrows an exception with the error number that is supplied to it. The error can be a standard OpenDoc error or a platform-specific error. The error code must be a nonzero value; it doesn't make sense to throw an exception whose value iskODNoError.THROW_IF_NULL
THROW_IF_NULLthrows the exceptionkODErrOutOfMemoryif a null pointer is supplied to it. Call this macro after you call a memory-allocation function (such asSOMNeworMMNewPtr) that returns null when there is insufficient memory.Do not use
THROW_IF_NULLwith functions that can return null for other reasons. For example, the Mac OS Resource Manager routineGetResourcereturns null if the resource cannot be found; in that case, you should first callResErrorto find the actual error code, and then callTHROW.THROW_IF_ERROR
THROW_IF_ERRORthrows an exception if the error supplied to it is nonzero. If the error value iskODNoError, nothing happens. This is a useful call to use following a function call whose return value is an error code.For example, the Mac OS File Manager function
FSpOpenDFreturns zero if it succeeds, and otherwise a nonzeroOSErrcode. Passing the result toTHROW_IF_ERRORensures that the right exception is thrown if the call toFSpOpenDFfails.Exception Handlers
An exception handler consists of aTRYblock, zero or moreCATCH_ALLblocks, and anENDTRY:
TRY{ // statements }CATCH_ALL{ // statements }ENDTRYIt's perfectly legal lexically (and not uncommon) to nest exception handlers in a single function. Any error caught and reraised by the inner handler will be caught by the outer one.The rest of this section describes what actions each of the macro statements and its associated code block perform.
TRY
Following aTRYmacro, the immediately subsequent statements are executed. If one of the statements, or any function one of the statements calls, throws an exception that reaches this exception handler, then one of the followingCATCH_ALLblocks may be executed. Otherwise, after the last statement in theTRYblock finishes, control passes to the statement following theENDTRY.CATCH_ALL
If an exception is thrown to this handler, the statements following theCATCH_ALLmacro are executed. To tell what error code was thrown, use theErrorCodefunction.The flow of control for the
CATCH_ALLis the same as forCATCH. If no exception is raised or re-raised, control passes from the last statement in thisCATCH_ALLblock to the statement following theENDTRY.ENDTRY
TheENDTRYmacro statement indicates the end of the exception handler. After aTRYorCATCHblock finishes without throwing or re-raising an exception, the exception handler removes itself from the stack and control passes to the statement followingENDTRY.RERAISE
TheRERAISEmacro statement is called within aCATCH_ALLblock. It causes the exception to be thrown again, to the next active exception handler on the stack. This is the normal behavior for an exception handler--most of the time you don't want to hide the error; you want to propagate it so a higher level handler can deal with it.The SOM Environment Parameter
OpenDoc objects are SOM objects, which means that they follow the CORBA rules for handling exceptions. Every method call made to an OpenDoc object (including your part, as a subclass ofODPart) must therefore include an environment parameter (ev), a pointer to a value that can describe an error. For example, theCreateLinkSourcemethod ofODDrafthas the following prototype (in IDL):
ODLinkSource CreateLinkSource(in ODPart part);The method takes a single parameter, of typeODPart. To use this method, however, a caller in C++ must supply two parameters:
MyLinkSource = MyDraft->CreateLinkSource(ev, somSelf);If execution of the method results in an error condition, the receiver of the call (the draft object in this case) must place an exception code in the value pointed to byevand return. The caller must therefore examine theevparameter after every call to a SOM object, to see if an exception has been raised.All OpenDoc methods that you call, as well as all public methods of your part editor that you write, must return errors this way. What this means for your exception handling is that
The environment variable is passed along through a sequence of calls and can be used in calls to both SOM and C++ objects. For example, the environment variable is passed in these situations:
- you must supply an environment variable with all method calls to OpenDoc objects
- you must check the environment variable after the call returns
For more information on the environment parameter and exceptions, see SOMobjects Developer Toolkit Users Guide and SOMobjects Developer Toolkit Programmers Reference Manual from IBM.
- If your C++ method (that does not itself receive an environment parameter) calls a SOM method, it must use a SOM utility method to retrieve the environment variable.
- If your SOM method calls another SOM method, it can simply pass on the environment parameter it receives.
- If your SOM method calls a C++ method that may in turn call a SOM method, your SOM method can pass the environment parameter on to the C++ method (if the C++ method was designed to accept it; see next bullet).
- If your C++ method is called by a SOM method and in turn makes calls to SOM methods, it is best to design it to accept an environment parameter that it can then pass on.
Any exception-handling scheme that you use must support this method of passing exceptions. The OpenDoc utility described in this section helps you check the environment variable after each method call.
Handling SOM Exceptions
The exception-handling utility has some special features that simplify working with SOM. There are two reasons why these features are necessary:
This implies that the
- SOM has its own way of returning error codes, based on an environment variable, a pointer to which is passed into every method of a SOM object.
- You cannot throw an exception, or allow one to be thrown, out of a SOM method. SOM requires that a method return normally, and throwing an exception that is caught by a handler in some other function farther up the stack would violate this.
evparameter must be checked for an error value after every call to a SOM method and that an exception raised in a SOM method or any function it calls must be caught and its error code stored in theevparameter. The exception-handling utility includes functions to simplify these tasks, which are variants of the exception handling macros previously introduced:
SOM_TRY SOM_CATCH_ALL SOM_ENDTRYThese macros are identical toTRY,CATCH_ALL, andENDTRY, except that when they catch an exception, they store the exception value in the method'sevparameter where the caller can see it.Because you cannot throw an exception out of a SOM method, it is illegal to
RERAISEin theSOM_CATCH_ALLblock. You should exit the function normally by falling off the end or callingreturn(in C++ the former generates slightly better code).
- IMPORTANT
SOM_ENDTRYworks differently thanENDTRYin that its default behavior is to re-raise the exception by storing the error information in theEnvironmentvariable so it is propagated to the caller. (WithENDTRYyou must explicitly reraise in yourCATCH_ALLblock or the exception will disappear.) If you don't want to return the exception to the caller, you must callSetErrorCode(kODNoError)in theSOM_CATCHblock.![]()
Automatic Environment Checking
If you include the header Except.h in your source files, it defines a special preprocessor symbol that modifies the way SOM messages are sent. Any SOM headers (.xh files for development in C++) included after Except.h has been included are modified so that, after the message is sent and control returns to the caller, the environment variable (ev) is checked and an exception raised if the variable contains an error.For example, the following code fragment does not use automatic environment checking:
#include <ODWingDing.xh> ... long AFunction (Environment *ev, ODWingDing *wingDing) { long result = wingDing->Spin(ev); if(ev->_major) { // ODWingDing::Spin returns error result = 0; goto handle_error; } ... handle_error: return result; }In this example, Except.h is not included, so environment checking is not automatic, and the caller (AFunction) can and must check the environment variable after every SOM method call.Here is the same example with automatic environment checking:
#include <Except.h> // Enables automatic ev checking #include <ODWingDing.xh> ... long AFunction (Environment *ev, ODWingDing *wingDing) { long result; SOM_TRY long result = wingDing->Spin(ev); ... SOM_CATCH_ALL result = kODNULL; SOM_ENDTRY return result; }Since Except.h is included before ODWingDing.h, environment-checking code is added to the call to theSpinmethod ofODWingDing. IfSpinencounters an error and returns error status inev, an exception with that same error code is thrown, which will be caught by theSOM_CATCH_ALLexception handler and in its turn returned in theevparameter ofAFunction.There are two important precautions to keep in mind:
- You must include Except.h before including any headers that declare SOM classes if you want to use automatic environment checking for them.
- When using automatic environment checking, any SOM method call may throw an exception, so any SOM method that calls other SOM methods must be prepared to handle exceptions.
Coding Precautions
To achieve its results as a C++ library, the OpenDoc exception-handling utility relies on complex macros and sophisticated library functions. To some extent, it "fools" the compiler. Because of this, there are some precautions you have to take to avoid causing the compiler to generate incorrect code.Very few C++ compilers have intrinsic support for exceptions, so the OpenDoc exception-handling utility is based on the ANSI
setjmpandlongjmpcalls. Because of this basis, the compiler cannot always track the possible flow of control when exceptions are thrown and caught. The compiler can generate code that improperly fails to pop an exception handler off the stack or that makes incorrect assumptions about flow of execution.This section discusses the precautions you must follow to make sure that the complier makes no mistakes, even when you have nested exception handlers.
Make Variables That You Modify Volatile
The compiler's register allocator and optimizer can make incorrect assumptions and generate bad code unless you take this precaution: always declare as volatile any variable or parameter that you modify in aTRYblock and then use in aCATCHorCATCH_ALLblock.The reason for this is that the compiler doesn't understand that the
TRYblock can be executed on the way to aCATCHblock, and that therefore the variable may be modified before theCATCHblock is reached. It may therefore end up using an obsolete value for the variable while in theCATCHblock. To work around this, you have to tell the compiler not to store the variable's value in a register (because it may be out of date) but always to look it up from the stack frame.The C++
volatilekeyword in the variable declaration does this (tells the compiler not to store the variable's value in a register). Unfortunately, some compilers don't implement it properly, and it can be confusing to use properly with pointer variables. For this reason the exception system defines a macro,ODVolatile,that declares a variable to be volatile. All you have to do is put this after the variable declaration. Here's an example:
void *p = kODNULL; ODVolatile(p); TRY{ Zog1(); p = ODNewPtr(10000); Zog2(); }CATCH{ ODDisposePtr(p); RERAISE; }ENDTRYThe purpose of the exception handler is to make sure thatpis disposed of on the way out in case it was allocated byODNewPtr. Becausepis modified inside theTRYblock, it has to be marked as volatile. (Note that when theCATCHblock is called,pmight still beNULL--ifZog1orODNewPtrthrew the exception--or it might be a valid pointer, if it wasZog2that threw the exception. Fortunately, we pre-initializedptokODNULL, andODDisposePtrcan safely be passed a null pointer. If we hadn't initializedp, this code might crash.)