Dr. Dobb's Journal August 2002
Suppose you spent months writing an application for embedded system X running under RTOS Y, then more painful months debugging and getting it to run. Further imagine that a large part of your code is C++, which relies heavily on exceptions. Finally, imagine that most of your exception-dependent code is not hand written but generated by a separate tool. In any case, you make the deadline and ship your product you are finished. Right?
Not always. Upon completing just such a project at Silicon & Software Systems, we got a phone call from the client who loved the application, but wanted to run it under Microsoft's Windows CE, rather than the RTOS it was written for. No big deal, we thought. Just recompile the code with the new compiler and, with some tweaking, the client would have the app by the weekend.
In a perfect world, compiler vendors would comply with C++'s international Standard. Unfortunately, this isn't so. In our case, we found out the Windows CE 3.0 compiler doesn't support two important C++ features exception handling and Run Time Type Identification (RTTI). Furthermore, a Microsoft-specific alternative known as Structured Exception Handling (SEH) does not exhibit the right stack-unwinding semantics. This means that when an exception is thrown, the destructors of any objects created on the stack won't be called during stack unwinding. Our problem was that the generated code relied on exceptions, so even if we had the time (hardly an option when you have 100,000-plus lines of code), it couldn't be rewritten.
We quickly discovered we weren't the only ones faced with this problem. Alternatives other programmers used ranged from removing exception handling from their code, to attempts to live with Microsoft's SEH "object disoriented" behavior. We considered investing in a new compiler, but no off-the-shelf C++ compiler was available for our OS and target processor, and a custom port of a standard C++ front end incurred additional licensing costs and delivery lead time we couldn't afford.
Luckily, we came across the TCU library, a freely available library written entirely in C++ that emulates exception handling and RTTI, and that comes with full source code. It was created to expressly address the lack of support for exceptions and RTTI in the Windows CE compiler. However, the library did have problems that required modifications on our part. In this first installment of a two-part article, I describe what the library can and cannot do. Next month, I'll present the workarounds we implemented to turn the TCU library into a viable alternative. With the modifications presented here, TCU solved our problem, and our application has been in the field for months with no reported exception-handling-related bugs.
Originally written by Fedor Sherstyuk, the TCU library (available electronically; see "Resource Center," page 5) is tailored to Windows CE but also runs under other versions of Windows. This is handy when testing the library itself. When running it under NT, I usually turn off native exception handling and RTTI support to avoid any interference between different exception-handling mechanisms. The library is largely platform independent, except for a platform-dependent file that contains a nonportable routine, which determines whether a particular memory address is on the stack. Since this routine makes use of Win32 API calls, it works fine on desktop systems.
From a usage point of view, the TCU library is intrusive you must modify your code to use the library's exception and RTTI syntax. On the upside, this isn't much different from the native C++ try, catch, and throw keywords, so if you are familiar with ordinary exception handling, the code looks familiar. On the downside, the change to the new syntax is complex enough that it can't be done with preprocessor macros. We solved this by using some in-house parsing expertise and writing a parser that transforms C++ code from the standard syntax to that required by TCU. Of course, this is of no consequence if you are writing code from scratch because you can use the new syntax from the outset.
TCU mimics standard C++ exception-handling behavior. When an exceptional error condition arises, you can create an object to model that condition. Typically, each exception object contains information about the error. These objects can then be thrown at a particular point in the program and caught up the call stack. The right execution flow is achieved through the Standard C Runtime Library setjmp()/longjmp() functions. Implementing the right stack-unwinding semantics requires internal bookkeeping, coupled with help from the programmer. Basically, you tell TCU what objects you want destroyed when exceptions are thrown.
The TCU library defines the tcu__Xsc base class that represents exception-sensitive objects. These are objects that need to be destroyed during stack unwinding. So if you want the destructors of objects of a particular class to be called when an exception is thrown, you would have that class inherit from tcu__Xsc. In short, protected inheritance is enough because you are not modeling an "is-a" relationship, but instead what Scott Meyers refers to as an "is-implemented-in-terms-of" relationship (see Effective C++: 50 Specific Ways to Improve Your Programs and Design and More Effective C++: 35 New Ways to Improve Your Programs and Designs). In other words, the tcu__Xsc interface does not need to be publicly accessible by clients of your class. It is only an implementation detail used to add exception-handling capabilities. This restriction may turn single inheritance into multiple inheritance, and nonvirtual multiple inheritance into virtual multiple inheritance. In fact, deriving from tcu__Xsc is not strictly necessary if all resources that need to be freed at destruction are already contained in members derived from tcu__Xsc. In any case, when derivation is required, you must decide whether to use virtual or plain inheritance. The rule of thumb is to use plain inheritance whenever you can and virtual inheritance whenever you must. When must you? When there is no unambiguous conversion from your derived class to tcu__Xsc. Figure 1, the dreaded multiple inheritance diamond, is one such case. Were inheritance not virtual, D would contain two subobjects of class A and a conversion from D to A would be ambiguous. Virtual inheritance should be used sparingly though, especially in an embedded environment, since it introduces its own inefficiencies.
Another consequence of the way the TCU library is implemented is that the first line in the constructor body of any class derived from tcu__Xsc should be a call to the TCU_X_RESET macro. This is true even for constructors with empty bodies. This statement may or may not be omitted for classes that have no members derived from tcu__Xsc. This is one of the major drawbacks of the library because it invalidates all compiler-generated copy constructors, since they don't include the call to TCU_X_RESET. Any class whose objects need to be copied requires a manually written copy constructor with a call to this macro in its body.
The exceptions thrown using the TCU library must be of a class derived from tcu_Xc, which acts as a base class for all exception objects. This means that you cannot throw std::string or int exceptions, for instance, because they are not derived from tcu_Xc. In practice, this is hardly a limitation. A requirement imposed by TCU to any exception class is that it must provide a copy constructor, because when exception objects are thrown, they are copied from a local copy on the stack to a more permanent exception object created on the heap.
The C++ try, catch, and throw keywords have their own versions in the TCU library. The TCU_X_TRY and TCU_X_END_TRY macros mark the beginning and end of a TCU try block, respectively. TCU_X_ CATCH, TCU_X_CATCH_TYPE, and TCU_ X_CATCH_ALL specify TCU catch clauses. The first makes a reference to the caught TCU exception available within the scope of the TCU catch clause. TCU_ X_CATCH_TYPE is used when only the type of exception is important and we are not interested in its value. Finally, TCU_X_CATCH_ALL is a substitute for the catch(...) clause used in C++ proper. TCU exceptions are thrown using the TCU_ X_THROW macro, which takes a TCU exception object as an argument. A separate TCU_X_RETHROW macro exists that rethrows the current TCU exception.
When using the TCU library, exceptions thrown inside constructors do not behave according to the C++ Standard, which says objects are not considered fully constructed until their constructor has completed. Therefore, exceptions thrown from inside a constructor do not result in the object's destructor being called. However, any fully constructed object members and base class subobjects are destroyed. The TCU library, on the other hand, calls an object's destructor if an exception is thrown during its construction. This is because exception-handling mechanisms implemented at the C++ level never have access to the same amount of context information as a compiler-level implementation. When an exception is thrown from a constructor, TCU calls the destructors for the fully constructed base class subobjects and data members. In this respect, it mimics standard behavior.
TCU does not implement the exception specifications described in the C++ Standard. These are the throw keywords found in function declarations that give the caller certain guarantees as to what may or may not be thrown from a particular function. In practice, exception specifications are not widely used in C++, so this is not a major drawback. If you have ever used Microsoft's Visual C++, for instance, you probably know this feature is parsed correctly but otherwise ignored.
If an appropriate exception handler is not found or if an exception is thrown from within a destructor during stack unwinding, TCU calls tcu_xTerminate(). This function cleans up and calls a terminate handler, which by default kills the current process. You can override this handler with tcu_xSetTerminate(). This is similar to the standard terminate() behavior, which either calls a terminate handler set via set_terminate() or defaults to calling abort().
The TCU exception-handling emulation is threadsafe as it uses Windows Thread Local Storage (TLS) memory to store thread-specific information. Each thread has an exception-handling context saved in its associated TLS. Also, TCU lets you manage several instances of the library in memory. This is handy in a Windows environment if, for example, an instance of the library is used by an executable (.exe file) and another by a DLL loaded at run time. Each instance of the TCU library creates a unique token. It is your responsibility to ensure that all instances within the same process share the same unique token.
While this pretty much describes TCU's exception-handling capabilities, a natural consequence of implementing any exception-handling mechanism is the need for RTTI. This is because the exact type of a thrown exception object needs to be known at run time if an appropriate exception handler is to be found for it. TCU has its own version of RTTI, which is another missing feature in the CE compiler. The Microsoft Foundation Classes (MFC) offer some kind of RTTI via the DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, STATIC_DOWNCAST, and DYNAMIC_DOWNCAST macros. MFC's main drawback is its lack of support for multiple inheritance. (Sherstyuk mentions other limitations with the MFC implementation in tcurtti.h.)
For a class to use RTTI services from the TCU library, the class must either directly or indirectly inherit from the RTTI abstract base class tcu_Rtti. In the class declaration body, either TCU_RTTI_DECLARE (for a concrete class) or TCU_RTTI_DECLARE_ABSTRACT (for an abstract class) must be included. Both of these macros declare a public virtual function and change the access specifier to public. Never insert them in the middle of a private or protected section. A way of avoiding this kind of problem is to always insert these macros at the end of a class declaration. Listing One illustrates this problem.
An implementation for these macros must also be provided. This is done using one of the following statements as part of your class definition:
TCU_RTTI_IMPLEMENT(MyClass);
TCU_RTTI_IMPLEMENT_1(MyClass,
BaseClassName);
TCU_RTTI_IMPLEMENT_2(MyClass,
BaseClass1Name,BaseClass2Name);
TCU_RTTI_IMPLEMENT_3 ...
TCU_RTTI_IMPLEMENT_4 ...
TCU_RTTI_IMPLEMENT_5 ...
TCU_RTTI_IMPLEMENT_6 ...
TCU_RTTI_IMPLEMENT_7 ...
TCU_RTTI_IMPLEMENT_8 ...
TCU_RTTI_IMPLEMENT_9 ...
where MyClass is the name of your class, BaseClass1Name the name of its first base class, and so on. The first macro takes only the name of the current class as an argument, and no base class is specified. This is because it assumes your class typedef's its base class name as Base. For single inheritance hierarchies, it is generally useful to add typedef BaseClassName Base; to your class declaration (private access suffices).
Having set up your classes, the RTTI services TCU provides are:
One limitation of this RTTI implementation is its lack of support for DLLs. If you have an object with RTTI information constructed in a DLL, which is queried for type information in a different module (other DLL or executable), their type IDs will differ. This leads to dynamic cast failures. A typical case where this problem manifests itself is when throwing an exception from a DLL, which ends up getting caught in your core executable. The appropriate catch clause fails to be recognized as such. The reason for this lack of support is that type IDs are implemented in terms of a static variable in a template class. Different DLLs have different instantiations of the template, hence the problem (a solution will be outlined in Part II of this article).
In the following example, I make use of helper classes (see Listing Two). These classes are also used to illustrate the rest of the examples in this article. For brevity, classes have been implemented inline. This has the added advantage of showing that you can use TCU_RTTI_IMPLEMENT with the inline keyword as indeed one could use template when dealing with template classes. UnwindableObj is a base class representing exception-sensitive objects that need to be destroyed during stack unwinding. It also inherits RTTI functionality. In a real application, everything should directly or indirectly inherit from UnwindableObj or a similar class to ensure proper cleanup during stack unwinding. In these examples, I've used DerivedObj as some class derived from UnwindableObj. The other two classes I use are ExcBase, which acts as a base class for exception objects, and ExcDerived as a derived exception class. TCU provides some macros to express inheritance relationships, such as TCU_COLON_XC, which expands to ": public tcu_Xc". I use this in the example. See the TCU files for a more detailed coverage of these macros. ExcBase also inherits from UnwindableObj. This ensures that exception objects created locally on the stack get destroyed during stack unwinding.
Listing Three is the TCU library at work. main() contains a try block with three catch clauses one catches exceptions of type ExcDerived, the second catches ExcBase objects, and a final clause catches everything else. The body of the try block creates a local object of type DerivedObj and calls a function foo(), which in turn creates two local UnwindableObj objects and throws an exception.
The second part of main() demonstrates RTTI. A DerivedObj is allocated on the heap and the result assigned to a pointer to its base class UnwindableObj. A dynamic downcast is then performed on that pointer which yields a DerivedObj nonzero pointer. This pointer is then used to call a method only present in the derived class. Finally, the TCU typeid operator functionality is presented. Figure 2 is an annotated output of a program run.
First, local object d is constructed, then foo() is called and local objects foo1 and foo2 get constructed. When the exception is thrown, an ExcDerived object is first created locally in foo(). Then a new instance is allocated on the heap and a copy constructor call is incurred. The local exception object is then destroyed together with the other objects on the stack (foo1, foo2, and d). The order of destruction is the reverse of the order of construction as per standard exception-handling behavior. Code execution then branches off to the catch block for ExcDerived exceptions and finally the exception allocated on the heap gets destroyed.
In the next installment of this article, I'll examine the TCU library in more detail, and present the modifications we made to address its shortcomings.
DDJ
// Gotcha using TCU_RTTI_DECLARE. Member access changed to public.
class A : public tcu_Rtti
{
public:
A();
~A();
private:
TCU_RTTI_DECLARE; // Public access from here on
int mv_anInt; // Oops ! No longer private
};
// To avoid the above, always declare RTTI at the end of your class.
class A : public tcu_Rtti
{
public:
A();
~A();
private:
int mv_anInt;
TCU_RTTI_DECLARE; // OK now given this is the last statement
// in the class declaration
};
// File: tcutest.h
#include <iostream>
#include <string>
#include <exception>
#include "tcurtti.h"
#include "tcuexc.h"
// Exception-sensitive object to be destroyed during stack unwinding
// It also contains RTTI info
class UnwindableObj : public tcu_Rtti, virtual protected tcu__Xsc
{
// Only public base classes should be accessible via dynamic_cast
typedef tcu_Rtti Base;
static unsigned int mv_nCount;
public:
// Default constructor
UnwindableObj()
{
TCU_X_RESET;
std::cout << "UnwindableObj ctor for 0x" << this << std::endl;
mv_nCount++;
}
// Copy constructor
UnwindableObj(const UnwindableObj &u) : tcu__Xsc(u), tcu_Rtti(u)
{
TCU_X_RESET;
std::cout << "UnwindableObj copy ctor for 0x" << this << std::endl;
mv_nCount++;
}
// Destructor
virtual ~UnwindableObj()
{
std::cout << "UnwindableObj dtor for 0x" << this << std::endl;
mv_nCount--;
}
// Accessor
static unsigned int getCtorCount()
{
return mv_nCount;
}
TCU_RTTI_DECLARE;
};
inline TCU_RTTI_IMPLEMENT(UnwindableObj);
// Derived class to show RTTI at work
class DerivedObj : public UnwindableObj
{
typedef UnwindableObj Base;
public:
// Default constructor
DerivedObj()
{
TCU_X_RESET;
std::cout << "DerivedObj ctor for 0x" << this << std::endl;
}
// Copy constructor
DerivedObj(const DerivedObj &d) : UnwindableObj(d)
{
TCU_X_RESET;
std::cout << "DerivedObj copy ctor for 0x" << this << std::endl;
}
// Destructor
virtual ~DerivedObj()
{
std::cout << "DerivedObj dtor for 0x" << this << std::endl;
}
// New class method
void DerivedMethod()
{
std::cout << "DerivedMethod()" << std::endl;
}
TCU_RTTI_DECLARE;
};
inline TCU_RTTI_IMPLEMENT(DerivedObj);
// Exception base class
class ExcBase TCU_COLON_XC, public UnwindableObj, public std::exception
{
std::string mv_msg;
public:
// Default constructor
ExcBase(std::string msg) : mv_msg(msg)
{
TCU_X_RESET;
std::cout << "ExcBase ctor for 0x" << this << std::endl;
}
// Copy constructor
ExcBase(const ExcBase &e) : tcu_Xc(e), UnwindableObj(e)
{
TCU_X_RESET;
std::cout << "ExcBase copy ctor for 0x" << this << std::endl;
mv_msg = e.what();
}
// Destructor
virtual ~ExcBase()
{
std::cout << "ExcBase dtor for 0x" << this << std::endl;
}
// Retrieve error message
TCU_RTTI_DECLARE;
};
inline TCU_RTTI_IMPLEMENT_2(ExcBase,tcu_Xc,UnwindableObj);
// Another exception class derived from above
class ExcDerived : public ExcBase
{
typedef ExcBase Base;
public:
// Default constructor
ExcDerived(std::string msg) : ExcBase(msg)
{
TCU_X_RESET;
std::cout << "ExcDerived ctor for 0x" << this << std::endl;
}
// Copy constructor
ExcDerived(const ExcDerived &e) : ExcBase(e)
{
TCU_X_RESET;
std::cout << "ExcDerived copy ctor for 0x" << this << std::endl;
}
// Destructor
virtual ~ExcDerived()
{
std::cout << "ExcDerived dtor for 0x" << this << std::endl;
}
TCU_RTTI_DECLARE;
};
inline TCU_RTTI_IMPLEMENT(ExcDerived);
#include <iostream>
#include <cassert>
#include "tcutest.h"
using namespace std;
// File scope functions
namespace {
void foo()
{
cout << "In foo() ..." << endl;
UnwindableObj foo1,foo2;
TCU_X_THROW(ExcDerived("Derived exception"));
}
}
// Static member definition
unsigned int UnwindableObj::mv_nCount = 0;
// main
int main()
{
cout << "Test of an EH working case using the original TCU library"
<< endl << endl;
// EH test
TCU_X_TRY
{
DerivedObj d;
foo();
}
TCU_X_CATCH(ExcDerived,e)
{
cout << "ExcDerived caught: " << e.what() << " (0x" << &e << ")"
<< endl;
}
TCU_X_CATCH(ExcBase,e)
{
cout << "ExcBase caught: " << e.what() << " (0x" << &e << ")" << endl;
}
TCU_X_CATCH_ALL
{
cout << "Unknown exception" << endl;
} TCU_X_END_TRY
assert(UnwindableObj::getCtorCount() == 0);
// RTTI test
cout << endl<< "RTTI test" << endl << endl;
UnwindableObj *pB = new DerivedObj();
// Downcast
DerivedObj *pD = tcu_rttiDynamicCast<DerivedObj>(pB);
cout << "pD = 0x" << pD << endl;
pD->DerivedMethod();
// Typeid
bool b = tcu_rttiTypeId(*pB) == TCU_RTTI_TYPE_ID(DerivedObj);
cout << "TypeId test: " <<b << endl;
delete pB;
cout << "Press Enter to exit" << endl;
cin.get();
return 0;
}