TweetFollow Us on Twitter

C++ ExceptionsMac OS Code

Volume Number: 15 (1999)
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 communicate with AppleEvents.

In a past life I've written about how using AppleEvents is a convenient way to make your application multi-threaded by using an AppleEvent to pass data from the user interface to a server thread. [MacTech Dec '94]

What you may not know (thanks to the fact that it's relatively hidden in the AppleScript release notes, rather than in Inside Macintosh or a Tech Note) is that AppleScript provides a relatively robust error reporting mechanism in the form of a set of optional parameters in the reply of an AppleEvent which can specify, among other things, the error code, an explanatory string, the (AEOM) object that caused the error, and a bunch of other stuff.

Further, you may know that the AppleEvent manager provides a data structure that can hold an arbitrary collection of data (AERecord).

Putting this all together, if we define a C++ exception class which can export itself to an AERecord, we can both return extremely explicit error information a user of AppleScript (or any OSA language) and provide a standard format for exporting exception data across a library boundary. Also, since an AERecord can contain an arbitrary amount of data in any format, the programmer is free to include any information he wants in the exception - anything the recipient doesn't understand will be ignored.

Implementation Details

Following are some excerpts from an exception class and support code which do just this. Full source for a simple program using this code is provided on the conference CD. The exception mechanism is actually implemented as a pair of classes: Exception and LocationInCode and a series of macros which provide a reasonably efficient mechanism for reporting exactly where an error occurred and returning this information in the reply to an AppleEvent.

Using this mechanism, it is not only possible to throw an error across library boundaries, but also between processes or even machines.

Detection and Throwing Errors

The implementation of the Exception classes is divided between two source files: Exception.cp and LocationInCode.cp. The class Exception is the abstract representation of an exception. It has 2 subclasses: StdException and SilentException.

If you look at these two files, you'll notice that most of the functions that are involved in failure handling are implemented as macros in Exception.h which evaluate to methods of another class, LocationInCode - for instance, FailOSErr() is implemented as:

#define FailOSErr        GetLocationInCode().FailOSErr

#define GetLocationInCode()    LocationInCode(__LINE__, __FILE__)

class LocationInCode
{
LocationInCode(long line, const char* file) ...
void Throw(OSStatus err);
inline void    FailOSErr(OSErr err) const
    {
if (err != noErr)
        {     // CW Seems not to be sign extending w/o cast
            Throw((OSStatus) err);
        }
    }
}

So that the expression:

FailOSErr(MyFunc());

Evaluates to:

LocationInCode(__LINE__, __FILE__).FailOSErr(MyFunc());

While this seems needlessly complex, there is a good reason for it, involving tradeoffs between speed, code size, and some "features" of the C++ specification.

Specifically, the obvious way to implement FailOSErr() is:

#define FailOSErr(err) if (err) Throw(err)

The problem here is that the macro FailOSErr() evaluates its argument twice. This means that, in the case of an error, MyFunc() will be called twice - clearly not what we want.

Here is one place that C++ can help us out - we can implement FailOSErr() as an inline function:

inline void FailOSErr(err)
{
if (err != noErr)
    {
Throw(err, __LINE__, __FILE__);
    }
}

Since C++ inline functions are guaranteed to evaluate their arguments exactly once, this solves our problem. Further, it makes it possible to have overloaded versions of FailOSErr which take different arguments, for instance a string to pass to the user, so you can write:

FailOSErr(MyFunc(), "Some Error message")

The problem is that, once you implement this and try to access the file and line information, you will discover that, thanks to the way __FILE__ and __LINE__ are defined to work, all errors are reported as occurring in Exception.h - which is clearly less than useful. You would think that, in their infinite wisdom, the C++ standards committee would have updated the way that these macros work or provided a more robust mechanism for reporting the location of an error in code, but they didn't.

The solution presented here is a compromise-by instantiating the LocationInCode class from a macro, we insure that __FILE__ and __LINE__ evaluate to a useful location in the user's code, rather than in the exceptions library. Also, by using a class, we can reduce code size by allowing the methods of TLocationInCode to call each other without losing the actual location of the error.

An added benefit of this approach is that, in the future, we could replace the implementation of LocationInCode with one using MacsBug symbols or traceback tables in the code instead of relying upon the compiler macros.

Also, note that FailOSErr() and the constructor for LocationInCode are declared inline to maximize speed, but then call an out-of-line function (Throw) to minimize code size in the failure case.

Adding Information

At any point in handling an error you can add information to an Exception by calling Exception::PutErrorParamPtr or Exception::PutErrorParamDesc. For instance, if you were in an AppleEvent handler and wanted to set the offending object displayed to the user, you could write:

try
{
// whatever
}
catch (Exception& exc)
{
exc.PutErrorParamDesc(kAEOffendingObject, whatever, false);
throw;
}

These routines also take a parameter to tell whether to overwrite data already in the record - this is useful to ensure that the first error that occurred is the one reported to the user.

Insuring Errors are Caught

Because it''s not safe to throw C++ exceptions across a library boundary, we need a mechanism to insure that all errors are trapped and properly reported. Unfortunately, unlike Object Pascal, we can't just call CatchFailures() to set up a handler - the code which might fail must be called from within a try block.

Also, because C++ effectively requires catch blocks to switch off of the class of the object thrown, and doesn't support the concept of 'finally' like Java, this master exception handler can end up containing quite a lot of duplicated code.

In order to minimize code size, the static method Exception::vStandardizeExceptions() provides a way to have a function called from within a block that will catch all errors and convert them to a subclass of Exception. If you plan to support other exception classes, such as the ones in the C++ standard library, you would modify this function to do the right thing.

OSStatus Exception::vStandardizeExceptions
                          (VAProc proc, va_list arg)
{
StdException exc(GetLocationInCode());
try                        // Call the proc
    {
return (*proc)(arg);
    }
catch (Exception& err)     // Exceptions are OK
    {
throw /*err*/;
    }
catch (char* msg)
    {
exc.PutErrorParamPtr(
keyErrorString, typeChar, msg, strlen(msg));
    }
catch (long num)
    {
exc.SetStatus(num);
    }
catch (...)
    {
    }

if (LogExceptions())
    {
exc.Log();
    }

exc.AboutToThrow();
throw exc;
return 0;
}

There are several other convenience routines, all of which call through Exception::vStandardizeExceptions(), which capture all exceptions and convert them to an OSErr or write them into an AppleEvent. For instance, the following can be used by an AppleEvent handler to catch all errors and return them in the event:

OSErr Exception::CatchAEErrors(AppleEvent* event,
                                                                            VAProc proc, ...)
{
va_list arg; va_start(arg, proc);
OSStatus status; 
try
    {
status = vStandardizeExceptions(proc, arg);
    }
catch (Exception& exc)
    {
status = exc.GetOSErr();
if (event && event->dataHandle != nil)
        {
if (status != errAEEventNotHandled)
            {
// AppleScript has an undocumented "feature"
// where if we put an error parameter in an
// unhandled event, it reports an error rather
// than trying the system handlers.
GetLocationInCode().LogIfErr(
exc.GetAEParams(*event, false));
            }
        }
    }

va_end(arg);
if (status <= SHRT_MAX && status >= SHRT_MIN)
    {
return (OSErr) status;
    }
else
    {
return eGeneralErr;
    }
}

This pair of functions reports all errors to the user. (The Exceptions library allows the programmer to install a callback to report exceptions to the user. Not that here we use vStandardizeExceptions to insure that all exceptions are converted to a subclass of Exception().)

static OSStatus report_exception(va_list arg)
    {
VA_ARG(Exception*, exc, arg);
exc->Report();
return 0;
    }

void Exception::ReportExceptions(VAProc proc, ...)
{
va_list arg; va_start(arg, proc);
try
    {
GetLocationInCode().FailOSStatus(
vStandardizeExceptions(proc, arg));
va_end(arg);
    }
catch (Exception& exc)
    {
va_end(arg);
try
        {
StandardizeExceptions(report_exception, &exc);
        }
catch(Exception& exc1)
        {
            exc1.Log();     // don't throw errors in reporting
        }
    }
}

Conclusion

Exception handling is both useful and practically required in robust code. However, C++ exceptions have a number of limitations which you must be aware of when you are developing code which uses operating system features not supported by the language. However, using the techniques described here, these limitations 'are not insurmountable.

Bibliography

  • Bjarne Stroustrup, The C++ Programming Language (Third Edition), Addison-Wesley, 1997, ISBN 2-201-88954-4
  • Scott Meyers, Effective C++ (Second Edition), Addison-Wesley, 1997, ISBN 0-201-92488-9
  • Scott Meyers, More Effective C++, Addison-Wesley, 1996, ISBN 0-201-63371-X
  • P.J. Plauger, The Draft Standard C++ Library, Prentis-Hall, 1995, ISBN 0-13-117003-1
  • James O. Coplien, Advanced C++ Programming Styles and Idioms, Addison-Wesley, 1992, ISBN 0-201-54855-0

Thanks to Miro Jurisic, Elizabeth Rehfeld, and Brett Doehr for reviewing this article.


Steve Sisak lives in Cambridge, MA, with two neurotic cats, and ten Macintoshes. Steve referees Lacrosse, plays hockey, and enjoys good beer and spicy food. Products he has worked on include The American Heritage Electronic Dictionary, PowerSecretary, Mailsmith, MacTech's Sprocket, and several others. He currently makes his living making applications scriptable and developing MacOS USB drivers.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Vivaldi 1.0.118.19 - Lightweight browser...
Vivaldi browser. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind that users are... Read more
Stacks 2.6.11 - New way to create pages...
Stacks is a new way to create pages in RapidWeaver. It's a plugin designed to combine drag-and-drop simplicity with the power of fluid layout. Features: Fluid Layout: Stacks lets you build pages... Read more
xScope 4.1.3 - Onscreen graphic measurem...
xScope is powerful set of tools that are ideal for measuring, inspecting, and testing on-screen graphics and layouts. Its tools float above your desktop windows and can be accessed via a toolbar,... Read more
Cyberduck 4.7 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Labels & Addresses 1.7 - Powerful la...
Labels & Addresses is a home and office tool for printing all sorts of labels, envelopes, inventory labels, and price tags. Merge-printing capability makes the program a great tool for holiday... Read more
teleport 1.2.1 - Use one mouse/keyboard...
teleport is a simple utility to let you use one single mouse and keyboard to control several of your Macs. Simply reach the edge of your screen, and your mouse teleports to your other Mac! The... Read more
Apple iMovie 10.0.8 - Edit personal vide...
With an all-new design, Apple iMovie lets you enjoy your videos like never before. Browse your clips more easily, instantly share your favorite moments, and create beautiful HD movies and Hollywood-... Read more
Box Sync 4.0.6233 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Fantastical 2.0.3 - Create calendar even...
Fantastical 2 is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more
The Hit List 1.1.14 - Advanced reminder...
The Hit List manages the daily chaos of your modern life. It's easy to learn - it's as easy as making lists. And it's powerful enough to let you plan, then forget, then act when the time is right.... Read more

SoundHound + LiveLyrics is Making its De...
SoundHound Inc. has announced that SoundHound + LiveLyrics, will be one of the first third-party apps to hit the Apple Watch. With  SoundHound you'll be able to tap on your watch and have the app recognize the music you are listening to, then have... | Read more »
Adobe Joins the Apple Watch Lineup With...
A whole tidal wave of apps are headed for the Apple Watch, and Adobe has joined in with 3 new ways to enhance your creativity and collaborate with others. The watch apps pair with iPad/iPhone apps to give you total control over your Adobe projects... | Read more »
Z Steel Soldiers, Sequel to Kavcom'...
Kavcom has released Z Steel Soldiers, which continues the story of the comedic RTS originally created by the Bitmap Brothers. [Read more] | Read more »
Seene Lets You Create 3D Images With You...
Seene, by Obvious Engineering, is a 3D capture app that's meant to allow you to create visually stunning 3D images with a tap of your finger, and then share them as a 3D photo, video or gif. [Read more] | Read more »
Lost Within - Tips, Tricks, and Strategi...
Have you just downloaded Lost Within and are you in need of a guiding hand? While it’s not the toughest of games out there you might still want some helpful tips to get you started. [Read more] | Read more »
Entertain Your Pet With Your Watch With...
The Petcube Camera is a device that lets you use live video to check in on your pet, talk to them, and play with them using a laser pointer - all while you're away. And the Petcube app is coming to the Apple Watch, so you'll be able to hang out with... | Read more »
Now You Can Manage Your Line2 Calls With...
You'll be able to get your Line2 cloud phone service on the Apple Watch very soon. The watch app can send and receive messages using hands-free voice dictation, or by selecting from a list of provided responses. [Read more] | Read more »
R.B.I. Baseball 15 (Games)
R.B.I. Baseball 15 1.01 Device: iOS Universal Category: Games Price: $4.99, Version: 1.01 (iTunes) Description: The legendary Major League Baseball franchise returns to the diamond. Make History. ** ALL iPOD Touch, the iPad 2 and the... | Read more »
Here's How You Can Tell if an App W...
The Apple Watch is pretty much here, and that means a whole lot of compatible apps and games are going to be updated or released onto the App Store. That's okay though, beacause Apple has quietly updated their app description pages to make things... | Read more »
Forgotten Memories : Alternate Realities...
Forgotten Memories : Alternate Realities 1.0.1 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.1 (iTunes) Description: + REDUCED PRICE ONLY THE LAUNCHING WEEK + "The most exciting horror game of 2015." - AppSpy... | Read more »

Price Scanner via MacPrices.net

Intel Compute Stick: A New Mini-Computing For...
The Intel Compute Stick, a new pocket-sized computer based on a quad-core Intel Atom processor running Windows 8.1 with Bing, is available now through Intel Authorized Dealers across much of the... Read more
Heal to Launch First One-Touch House Call Doc...
Santa Monica, California based Heal, a pioneer in on-demand personal health care services — will offer the first one-touch, on-demand house call doctor app for the Apple Watch. Heal’s Watch app,... Read more
Mac Notebooks: Avoiding MagSafe Power Adapter...
Apple Support says proper usage, care, and maintenance of Your Mac notebook’s MagSafe power adapter can substantially increase the the adapter’s service life. Of course, MagSafe itself is an Apple... Read more
12″ Retina MacBook In Shootout With Air And P...
BareFeats’ rob-ART morgan has posted another comparison of the 12″ MacBook with other Mac laptops, noting that the general goodness of all Mac laptops can make which one to purchase a tough decision... Read more
FileMaker Go for iPad and iPhone: Over 1.5 Mi...
FileMaker has announced that its FileMaker Go for iPad and iPhone app has surpassed 1.5 million downloads from the iTunes App Store. The milestone confirms the continued popularity of the FileMaker... Read more
Sale! 13-inch 2.7GHz Retina MacBook Pro for $...
 Best Buy has the new 2015 13″ 2.7GHz/128GB Retina MacBook Pro on sale for $1099 – $200 off MSRP. Choose free shipping or free local store pickup (if available). Price for online orders only, in-... Read more
Minimalist MacBook Confirms Death of Steve Jo...
ReadWrite’s Adriana Lee has posted a eulogy for the “Digital Hub” concept Steve Jobs first proposed back in 2001, declaring the new 12-inch MacBook with its single, over-subscribed USB-C port to be... Read more
13-inch 2.7GHz Retina MacBook Pro for $1234 w...
Adorama has the 13″ 2.7GHz/128GB Retina MacBook Pro in stock for $1234.99 ($65 off MSRP) including free shipping plus a free LG external DVD/CD optical drive. Adorama charges sales tax in NY & NJ... Read more
13-inch 2.5GHz MacBook Pro available for $999...
 Adorama has the 13-inch 2.5GHz MacBook Pro on sale for $999 including free shipping plus NY & NJ sales tax only. Their price is $100 off MSRP. Read more
Save up to $600 with Apple refurbished Mac Pr...
The Apple Store is offering Apple Certified Refurbished Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Watch SW Application Project Manager...
**Job Summary** The Apple Watch software team is looking for an Application Engineering Project Manager to work on new projects for Apple . The successful candidate Read more
Map Services Frameworks Manager - *Apple* M...
Job Description: Apple 's Maps Evaluation Organization is seeking a Senior Manager to lead the Map services assertions and performance frameworks group. Key Read more
Shuttle Driver - APPLE EAST, APPLE EAST (Unit...
HERE WE GROW AGAIN!! We are adding to our team! Red Lion Chevrolet of Apple Automotive is growing and we are looking for a PART TIME SHUTTLE DRIVER/ PORTER FOR SERVICE Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.