C++ ExceptionsMac OS Code
Volume Number: 15
Issue Number: 1
Column Tag: Programming Techniques
C++ Exceptions in Mac OS Code
by Steve Sisak <sgs@codewell.com>
Modern C++ offers many powerful features that make code more reusable and reliable.
Unfortunately, due to its UNIX roots, these often conflict with equally important
features of commercial quality Mac OS code, like toolbox callbacks, multi-threading
and asynchronous I/O. C++ Exception Handling is definitely an example of this. In
this article, we will describe some techniques for using C++ Exceptions in
commercial quality MacOS C++ code, including issues related to toolbox callbacks,
library boundaries, AppleEvents, and multi-threading.
What are Exceptions?
Exception Handling isa formal mechanism for reporting and handling errors which
separates the error handling code from the normal execution path. If you are
unfamiliar with how C++ exceptions work, you may want to check out Chapter 14 of
"The C++ Programming Language" by Bjarne Stroustrup or any of the other excellent
texts on the topic.
Why are exceptions necessary?
"Exceptions cannot be ignored" - Scott Meyers
One of the problems in designing reusable code is deciding how to communicate an
error that occurs deep within a library function back to someone who can handle it.
There are several conventional ways for library code to report an error, including:
• Terminating the program
• Returning an error result
• Setting a global error flag
• Calling an error function
• Performing a non-local goto (i.e. longjmp)
While terminating a program as the result of an error in input may be considered
acceptable in the UNIX world, it is generally not a good idea in software you plan to
ship to human users.
Returning an error or setting a flag are somewhat better, but suffer from the fact that
error returns can be (and often are) ignored, either because the programmer was lazy
or because a function that returns an error is called by another which has no way to
report it. Both of these methods are also limited in the amount of information they can
return. Return values must be meticulously passed back up the calling stack and global
flags are inherently unsafe in a threaded environment because they can be modified by
an error in a second thread before the first thread has had a chance to look at the
error.
Calling an error handler function is reliable, but while the function may be able to log
the error, it must still resort to one of the other mechanisms to handle or recover
from the error.
This leaves non-local goto, which is basically how exceptions are implemented -
except with formal support from the compiler. C++ exceptions extend
setjmp/longjmp by guaranteeing that local variables in registers are handled
properly and destructors for any local objects on the stack are called as the stack
unwinds.
Because an exception is an object, it is possible for a library developer to return far
more information than just an error code.
What's wrong with C++ exceptions?
In a nutshell: Lack of Standardization.
Like many aspects of C and C++, the implementation of exceptions has been left as an
implementation detail to be defined by compiler vendors as they see fit. As a result, it
is never safe to throw a C++ exception from a library that might be used by code
compiled with a different compiler (or a different version of the same compiler, or
even the same version of a compiler with different compile options).
As a result of this:
• Exceptions must not be thrown out of a library.
• Exceptions must not be thrown out of a toolbox callback.
• Exceptions must not be thrown out of a thread.
Each of these cases can fail in subtly different ways:
In the first case, there is no guarantee that both compilers use compatible
representations for exceptions. The C++ standard does not define a format for
exceptions that is supported across multiple compilers-C++ exceptions are objects
and there is no standard representation for C++ objects that is enforced across
compilers. This is also why it's not feasible to export C++ classes from a shared
library.
IBM's System Object Model (SOM), used in OpenDoc and Apple's Contextual Menu
Manager, solves this problem for objects quite robustly (even to the extent that it is
possible to mix objects and classes implemented in different languages like C++ and
SmallTalk), however, there are still additional issues which would require a ""System
Exception Model" "as well.
As a platform vendor, Apple could have saved us a lot of work here by specifying a
"System Exception Model" that all compiler vendors would agree to implement. In fact
they began to implement an Exceptions Manager as part of the PowerPC ABI but it was
left unfinished the last time the developer tools group was killed and Metrowerks took
over as the dominant development environment'-so we're stuck with the current state
of incompatibility. Hopefully, now that Apple is working on developer tools again, we
might finally see a standard.
Also, many Mac OS routines allow the programmer to specify callback routines which
will be called by the toolbox during lengthy operations or to give the programmer
more control than could be encoded in routine parameters. Unfortunately, because of
the above limitations, it is not possible to throw an error from a callback and catch it
in the code that called the original Toolbox routine.
This is because there is no way for the toolbox to clean up resources that may have
been allocated before calling your function. In this case it is necessary to save off the
exception data (if possible), return an error to the toolbox, and then re-throw the
exception when the toolbox returns to our code. Of course, C++ provides no safe way to
save off the exception currently being thrown for this purpose and RTTI does not
provide enough access to extract all data from an object of unknown type, so again, we
must roll our own.
There are a few toolbox managers that provide for error callback function that are not
required to return. While you should be able to throw an exception from these
callbacks, there are issues that you should be aware of. Specifically, some compilers
implement so-called ""zero-overhead "exceptions" which use elaborate schemes of
tables and tracing up the stack to restore program state without needing to explicitly
save state at the beginning of a try block. Often this code gets confused by having stack
frames in the calling sequence that the compiler did not generate, causing it to call
terminate() on your behalf. (CodeWarrior's exceptions code also does this if you try to
step over a throw from Jasik's Debugger-you can work around this by installing an
empty terminate() handler.)
C and C++ have no notion of threading or accommodation for it. For instance, the C++
standard allows you to install a handler to be called if an exception is thrown and would
not be caught, however you can only install one such handler per application. Further,
it is technically illegal for this routine to return to its caller. So, there is no easy way
to insure that an uncaught C++ exception will terminate only the thread it was thrown
from rather than the entire program. (It is possible with globals and custom thread
switching routines, but tricky to implement - I hope to have an example in the sample
code by the time this is published)
Interactions between threads and the runtime can also rear up and bite developers in
even more interesting and subtle ways: For instance, in earlier versions of
CodeWarrior's runtime, the exception handler stack was kept in a linked list, the head
of which was in a global variable. As a result, if exceptions were mixed with threads
and the programmer did not add code to explicitly manage this compiler-generated
global, the exception stacks of multiple threads would become intermingled, resulting
in Really Bad Things[TM] happening if anyone actually threw an exception.
What we need is a standard way to package an exception so it can be passed across any
of these boundaries and handled or re-thrown without losing information.
How did AppleEvents get in here?
As any Real Programmer[TM] knows, good Macintosh programs should be scriptable
(so users can do stuff the programmer didn't think of), and recordable (so that users
don't have to have intimate knowledge of AppleScript to record some actions, clean up
the result and save it off for future use).
You may also know that if you want to write a scriptable and recordable application and
you're starting from scratch, the easiest way to do it is to write a "factored
application - where the application is split into user interface and a server which