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.

 
AAPL
$519.01
Apple Inc.
+1.05
MSFT
$40.40
Microsoft Corpora
+0.65
GOOG
$556.54
Google Inc.
+20.10

MacTech Search:
Community Search:

Software Updates via MacUpdate

PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more
TurboTax 2013.r17.002 - Manage your 2013...
TurboTax guides you through your tax return step by step, does all the calculations, and checks your return for errors and overlooked deductions. It lets you file your return electronically to get... Read more
TrailRunner 3.8.769 - Route planning for...
Note: While the software is classified as freeware, it is actually donationware. Please consider making a donation to help support development. TrailRunner is the perfect companion for runners,... Read more

Latest Forum Discussions

See All

Unpossible (Games)
Unpossible 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: **Important** Requires at least iPad 2, iPhone 4S, iPod Touch (5th gen), or newer. | Read more »
Hitman GO (Games)
Hitman GO 1.2 Device: iOS Universal Category: Games Price: $4.99, Version: 1.2 (iTunes) Description: Get your daily fix of Agent 47 with this elegant, strategy-based Hitman game! | Read more »
The Seventh Sign: Hodappy Bird Released...
The Seventh Sign: Hodappy Bird Released on the App Store Posted by Jeff Scott on April 16th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Rogue Heroes (Games)
Rogue Heroes 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Rogue Heroes is awesome fast-paced 2D action-platforming roguelike game with RPG elements. - explore the depth of procedurally... | Read more »
Fourcast Review
Fourcast Review By Jennifer Allen on April 16th, 2014 Our Rating: :: VIDEO POTENTIALiPhone App - Designed for the iPhone, compatible with the iPad Fourcast is a great idea for combining video clips, but it does lack a number of... | Read more »
Hearthstone: Heroes of Warcraft is Avail...
Hearthstone: Heroes of Warcraft is Available on the U.S. App Store Right Now – Gogogogo! Posted by Rob Rich on April 16th, 2014 [ permalink ] | Read more »
Grammar Pop HD Review
Grammar Pop HD Review By Jennifer Allen on April 16th, 2014 Our Rating: :: FUN LEARNINGiPad Only App - Designed for the iPad Learning the different parts of speech isn’t always fun, but Grammar Pop HD makes it much more... | Read more »
Groundskeeper2 Review
Groundskeeper2 Review By Nadia Oxford on April 16th, 2014 Our Rating: :: SLICE THOSE ALIEN SLIMEUniversal App - Designed for iPhone and iPad Putting aside some minor control issues, Groundskeeper2 is a fun and furious action/... | Read more »
Pinnacle Studio for iPhone (Photography...
Pinnacle Studio for iPhone 5.0 Device: iOS iPhone Category: Photography Price: $9.99, Version: 5.0 (iTunes) Description: | Read more »
Season 3 of Zombies, Run! Starts Right N...
Season 3 of Zombies, Run! | Read more »

Price Scanner via MacPrices.net

Microsoft Blinks – Drops Microsoft Office 365...
Microsoft has dropped the annual subscription fee for Microsoft Office 365 Personal – which is needed in order to create and edit documents in Microsoft Office for iPad. However, Apple’s iOS and OS X... Read more
New AVG Vault Apps for iOS and Android Help K...
AVG Technologies N.V. an online security company for 177 million active users, has announced the launch of its latest mobile application, AVG Vault. The free app introduces an innovative user... Read more
Free Local Carrot iPhone App Helps Find Fresh...
I love fresh vegetables. I’m not a vegan, although I was for several years in the 1980s, but fresh vegetables and other whole foods are still my dietary mainstays as a matter of taste rather than... Read more
Apple refurbished iPad Airs available startin...
Apple is now offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. The following Airs are available today: -... Read more
21-inch 2.7GHz iMac on sale for $1179, save $...
B&H Photo has the 21″ 2.7GHz iMac on sale for $1179 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Add an iMac to your shopping cart, and B&H will offer an... Read more
Download our app, iTracx, for iOS and Android
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
Education discounts shave up to $300 off the...
Purchase a new Mac at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free, and all... Read more
Save $50 on Mac mini Server
B&H Photo has the 2012 Mac mini Server on sale for $949 including free shipping plus NY sales tax only. Their price is $50 off MSRP. Read more
PhatWare’s “Ultimate Writing App For iOS” Ren...
PhatWare Corp. has announced it has renamed its new WritePro word processing app for iPhone and iPad: WritePad Pro. The decision to change the app’s name to leverages the strong brand awareness and... Read more
Full Resolution Photo Editor Tint Mint 1.0 Re...
California based independent developer, Jeffrey Sun, creator of the iOS app Modern Editor, has released Tint Mint, a new photography app for editing enthusiasts. The app costs a dollar, and it packs... Read more

Jobs Board

*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Market Leader - Cincinnati...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
*Apple* Retail - Manager - SoHo - Apple (Uni...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.