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

Lyn 1.8.5 - Lightweight image browser an...
Lyn is a fast, lightweight image browser and viewer designed for photographers, graphic artists, and Web designers. Featuring an extremely versatile and aesthetically pleasing interface, it delivers... Read more
Apple iOS 10.2.1 - The latest version of...
iOS 10 is the biggest release of iOS ever. A massive update to Messages brings the power of the App Store to your conversations and makes messaging more personal than ever. Find your route with... Read more
Apple Security Update 2016-003 Supplemen...
Apple Security Update is recommended for all users and improves the security of OS X. For detailed information about the security content of this update, please visit: http://support.apple.com/kb/... Read more
Apple macOS Sierra 10.12.3 - The latest...
With Apple macOS Sierra, Siri makes its debut on Mac, with new features designed just for the desktop. Your Mac works with iCloud and your Apple devices in smart new ways, and intelligent... Read more
BetterTouchTool 1.992 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Viber 6.5.5 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Opera 42.0.2393.137 - High-performance W...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
iClock Pro 3.4.7 - Customize your menuba...
iClock Pro is a menu bar replacement clock for Apple's default clock. iClock Pro is an update, total rewrite and improvement to the popular iClock. Have the day, date and time in different fonts and... Read more
PhotoDesk 4.1.5 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos. (NO Uploads! / Image Posting! Instagram forbids that! AND you need an existing Instagram account). But you can do so... Read more
Capo 3.5.1 - Slow down and learn to play...
Capo lets you slow down your favorite songs so you can hear the notes and learn how they are played. With Capo, you can quickly tab out your songs atop a highly-detailed OpenCL-powered spectrogram... Read more

Collect pets and sling arrows in Arcane...
Mobile gaming is a crowded market, but regular updates are a good way to keep us attention-short players keen. The brand new content in Arcane Online is a prime example. Published by Japanese developer Gala, Arcane Online is a fantasy MMO that... | Read more »
Super Mario Run dashes onto Android in M...
Super Mario Run was one of the biggest mobile launches in 2016 before it was met with a lukewarm response by many. While the game itself plays a treat, it's pretty hard to swallow the steep price for the full game. With that said, Android users... | Read more »
WarFriends Beginner's Guide: How to...
Chillingo's new game, WarFriends, is finally available world wide, and so far it's a refreshing change from common mobile game trends. The game's a mix of tower defense, third person shooter, and collectible card game. There's a lot to unpack here... | Read more »
Super Gridland (Entertainment)
Super Gridland 1.0 Device: iOS Universal Category: Entertainment Price: $1.99, Version: 1.0 (iTunes) Description: Match. Build. Survive. "exquisitely tuned" - Rock Paper Shotgun No in-app purches, and no ads! | Read more »
Red's Kingdom (Games)
Red's Kingdom 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Mad King Mac has kidnapped your father and stolen your golden nut! Solve puzzles and battle goons as you explore and battle your... | Read more »
Turbo League Guide: How to tame the cont...
| Read more »
Fire Emblem: Heroes coming to Google Pla...
Nintendo gave us our first look at Fire Emblem: Heroes, the upcoming mobile Fire Emblem game the company hinted at last year. Revealed at the Fire Emblem Direct event held today, the game will condense the series' tactical RPG combat into bite-... | Read more »
ReSlice (Music)
ReSlice 1.0 Device: iOS Universal Category: Music Price: $9.99, Version: 1.0 (iTunes) Description: Audio Slice Machine Slice your audio samples with ReSlice and create flexible musical atoms which can be triggered by MIDI notes or... | Read more »
Stickman Surfer rides in with the tide t...
Stickson is back and this time he's taken up yet another extreme sport - surfing. Stickman Surfer is out this Thursday on both iOS and Android, so if you've been following the other Stickman adventures, you might be interested in picking this one... | Read more »
Z-Exemplar (Games)
Z-Exemplar 1.4 Device: iOS Universal Category: Games Price: $3.99, Version: 1.4 (iTunes) Description: | Read more »

Price Scanner via MacPrices.net

Deal alert! 13-inch 2.0GHz MacBook Pros for $...
B&H Photo has the new 2016 13″ 2.0GHz non-Touch Bar MacBook Pros in stock today and on sale for $225 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz MacBook Pro... Read more
Free LibreOffice Portable 5.2.4 Complete Offi...
PortableApps.com and The Document Foundation have announce the release of LibreOffice Portable 5.2.4. LibreOffice Portable is an Open Source full-featured office suite — including a word processor,... Read more
Apple Planning Three New Tablets For 2017 – D...
Digitimes’ Rebecca Kuo and Joseph Tsai say that unnamed insider sources report Apple having three new tablets in the pipeline for 2017 release: a 9.7-inch model with a friendly price range, a new mid... Read more
Roundup of 15-inch Touch Bar MacBook Pro sale...
B&H Photo has the new 2016 15″ Apple Touch Bar MacBook Pros in stock today and on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.7GHz Touch Bar... Read more
Apple refurbished iPad Pros available for up...
Apple has Certified Refurbished 9″ and 12″ Apple iPad Pros available for up to $160 off the cost of new iPads. An Apple one-year warranty is included with each model, and shipping is free: - 32GB 9″... Read more
16GB iPad Air 2, Apple refurbished, available...
Apple has Certified Refurbished 16GB iPad Air 2s available for $319 including free shipping. A standard Apple one-year is included. Their price is $60 off original MSRP for this model. Read more
Apple iMacs on sale for up to $120 off MSRP
B&H Photo has 21″ and 27″ Apple iMacs on sale for up to $120 off MSRP, each including free shipping plus NY sales tax only: - 27″ 3.3GHz iMac 5K: $2199 $100 off MSRP - 27″ 3.2GHz/1TB Fusion iMac... Read more
Apple refurbished Apple TVs available for up...
Apple has Certified Refurbished 32GB and 64GB Apple TVs available for up to $30 off the cost of new models. Apple’s standard one-year warranty is included with each model, and shipping is free: -... Read more
Save up to $350 with Apple Certified Refurbis...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more
2015 12-inch Retina MacBooks, Apple refurbish...
Apple has Certified Refurbished 2015 12″ Retina MacBooks available for up to $410 off original MSRP. Apple will include a standard one-year warranty with each MacBook, and shipping is free. The... Read more

Jobs Board

*Apple* & PC Desktop Support Technician...
Apple & PC Desktop Support Technician job in Manhattan, NY Introduction: We have immediate job openings for several Desktop Support Technicians with one of our most Read more
*Apple* & PC Desktop Support Technician...
Apple & PC Desktop Support Technician job in Stamford, CT We have immediate job openings for several Desktop Support Technicians with one of our most well-known Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Site Security Manager - Apple (Unite...
# Apple Site Security Manager Job Number: 54692472 Culver City, California, United States Posted: Jan. 19, 2017 Weekly Hours: 40.00 **Job Summary** The Apple Read more
*Apple* macOS Systems Integration Administra...
…most exceptional support available in the industry. SCI is seeking an Junior Apple macOS systems integration administrator that will be responsible for providing Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.