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

Macs Fan Control 1.3.1.0 - Monitor and c...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
NetShade 6.3.1 - Browse privately using...
NetShade is an anonymous proxy and VPN app+service for Mac. Unblock your Internet through NetShade's high-speed proxy and VPN servers spanning seven countries. NetShade masks your IP address as you... Read more
Dragon Dictate 4.0.7 - Premium voice-rec...
With Dragon Dictate speech recognition software, you can use your voice to create and edit text or interact with your favorite Mac applications. Far more than just speech-to-text, Dragon Dictate lets... Read more
Persecond 1.0.2 - Timelapse video made e...
Persecond is the easy, fun way to create a beautiful timelapse video. Import an image sequence from any camera, trim the length of your video, adjust the speed and playback direction, and you’re done... Read more
GIMP 2.8.14p2 - Powerful, free image edi...
GIMP is a multi-platform photo manipulation tool. GIMP is an acronym for GNU Image Manipulation Program. The GIMP is suitable for a variety of image manipulation tasks, including photo retouching,... Read more
Sandvox 2.10.2 - Easily build eye-catchi...
Sandvox is for Mac users who want to create a professional looking website quickly and easily. With Sandvox, you don't need to be a Web genius to build a stylish, feature-rich, standards-compliant... Read more
LibreOffice 5.0.1.2 - Free, open-source...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
f.lux 36.1 - Adjusts the color of your d...
f.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day. Ever notice how people texting at night have that eerie blue glow? Or wake... Read more
VirtualBox 5.0.2 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
File Juicer 4.43 - Extract images, video...
File Juicer is a drag-and-drop can opener and data archaeologist. Its specialty is to find and extract images, video, audio, or text from files which are hard to open in other ways. In computer... Read more

ReBoard: Revolutionary Keyboard (Utilit...
ReBoard: Revolutionary Keyboard 1.0 Device: iOS Universal Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: Do everything within the keyboard without switching apps! If you are in WhatsApp, how do you schedule a... | Read more »
Tiny Empire (Games)
Tiny Empire 1.1.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.1.3 (iTunes) Description: Launch cannonballs and blow tiny orcs into thousands of pieces in this intuitive fantasy-themed puzzle shooter! Embark on an... | Read more »
Astropad Mini (Productivity)
Astropad Mini 1.0 Device: iOS iPhone Category: Productivity Price: $4.99, Version: 1.0 (iTunes) Description: *** 50% off introductory price! ​*** Get the high-end experience of a Wacom tablet at a fraction of the price with Astropad... | Read more »
Emo Chorus (Music)
Emo Chorus 1.0.0 Device: iOS Universal Category: Music Price: $1.99, Version: 1.0.0 (iTunes) Description: Realistic Choir simulator ranging from simple Chorus emulation to full ensemble Choir with 128 members. ### introductory offer... | Read more »
Forest Spirit (Games)
Forest Spirit 1.0.5 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.5 (iTunes) Description: | Read more »
Ski Safari 2 (Games)
Ski Safari 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: The world's most fantastical, fun, family-friendly skiing game is back and better than ever! Play as Sven's sister Evana, share... | Read more »
Lara Croft GO (Games)
Lara Croft GO 1.0.47768 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.47768 (iTunes) Description: Lara Croft GO is a turn based puzzle-adventure set in a long-forgotten world. Explore the ruins of an ancient... | Read more »
Whispering Willows (Games)
Whispering Willows 1.23 Device: iOS Universal Category: Games Price: $4.99, Version: 1.23 (iTunes) Description: **LAUNCH SALE 50% OFF** - Whispering Willows is on sale for 50% off ($4.99) until September 9th. | Read more »
Calvino Noir (Games)
Calvino Noir 1.1 Device: iOS iPhone Category: Games Price: $3.99, Version: 1.1 (iTunes) Description: The film noir stealth game. Calvino Noir is the exploratory, sneaking adventure through the 1930s European criminal underworld.... | Read more »
Angel Sword (Games)
Angel Sword 1.0 Device: iOS Universal Category: Games Price: $6.99, Version: 1.0 (iTunes) Description: Prepare to adventure in the most epic full scale multiplayer 3D RPG for mobile! Experience amazing detailed graphics in full HD.... | Read more »

Price Scanner via MacPrices.net

iPad Air 2 on sale for up to $100 off MSRP
Best Buy has iPad Air 2s on sale for up to $100 off MSRP on their online store for a limited time. Choose free shipping or free local store pickup (if available). Sale prices available for online... Read more
MacBook Airs on sale for $100 off MSRP
Best Buy has MacBook Airs on sale for $100 off MSRP on their online store. Choose free shipping or free local store pickup (if available). Sale prices for online orders only, in-store prices may vary... Read more
Big Grips Lift Handle For iPad Air and iPad A...
KEM Ventures, Inc. which pioneered the extra-large, super-protective iPad case market with the introduction of Big Grips Frame and Stand in 2011, is launching Big Grips Lift featuring a new super-... Read more
Samsung Launches Galaxy Tab S2, Its Most Powe...
Samsung Electronics America, Inc. has announced the U.S. release of the Galaxy Tab S2, its thinnest, lightest, ultra-fast tablet. Blending form and function, elegant design and multitasking power,... Read more
Tablet Screen Sizes Expanding as iPad Pro App...
Larger screen sizes are gaining favor as the tablet transforms into a productivity device, with shipments growing 185 percent year-over-year in 2015. According to a new Strategy Analytics’ Tablet... Read more
Today Only: Save US$50 on Adobe Elements 13;...
Keep the memories. lose the distractions. Summer’s winding down and it’s time to turn almost perfect shots into picture perfect memories with Elements 13. And get the power to edit both photos and... Read more
1.4GHz Mac mini on sale for $449, save $50
Best Buy has the 1.4GHz Mac mini on sale for $50 off MSRP on their online store. Choose free shipping or free local store pickup (if available). Price for online orders only, in-store price may vary... Read more
12-inch 1.1GHz Gold MacBook on sale for $1149...
B&H Photo has the 12″ 1.1GHz Gold Retina MacBook on sale for $1149.99 including free shipping plus NY sales tax only. Their price is $150 off MSRP, and it’s the lowest price available for this... Read more
27-inch 3.3GHz 5K iMac on sale for $1849, sav...
Best Buy has the 27″ 3.3GHz 5K iMac on sale for $1849.99. Their price is $150 off MSRP, and it’s the lowest price available for this model. Choose free shipping or free local store pickup (if... Read more
Worldwide Tablet Shipments Expected to Declin...
Does Apple badly need a touchscreen convertible/hybrid laptop MacBook? Yes, judging from a new market forecast from the International Data Corporation (IDC) Worldwide Quarterly Tablet Tracker, which... 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* Retail Online Store: Customer Insigh...
**Job Summary** Apple Retail (Online Store) is seeking an experienced e-commerce analytics professional to join the Customer Insights Team. The Web e-Commerce Analyst Read more
*Apple* Music, Business Operations - Apple I...
Changing the world is all in a day039s work at Apple . If you love innovation, here039s your chance to make a career of it. You039ll work hard. But the job comes with Read more
WW *Apple* Retail Online Store: Customer In...
**Job Summary** The Apple Retail - Online Store is seeking an experienced web merchandising analytics professional to join the Customer Insights Team. The Web Read more
Senior Payments Security Manager - *Apple*...
**Job Summary** Apple , Inc. is looking for a highly motivated, innovative and hands-on senior payments security manager to join the Apple Pay security team. You will Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.