The contents of the .zip file and the following text were not created by and are not owned or copyrighted by mov Software.

The zip file containing this page in .DOC format and the sample code can be downloaded here.

The .zip file contents are as follows:

EXCEMU.DOC - Vladimir Belkin's article that provides an overview of the exception handling emulation mechanism. Translated by Fedor Sherstyuk.
TCURTTI.* - Implementation of RTTI for Windows CE. Written by Fedor Sherstyuk.
TCUEXC.* - Platform-independent part of C++ exception handling implementation. Written by Fedor Sherstyuk.
TCUXHAPI.* - Windows CE Win32 APIs used by exception handling emulation. Written by Fedor Sherstyuk.
TCUWARN.H - Helper file that eliminates irrelevant warnings on /W4. Written by Fedor Sherstyuk.

C++ exception handling emulation

1. Preface

This text contains a translation of Vladimir Belkin’s article "Exceptions processing in C++: what, when, how".

Article (chapter 2) is translated as is (only first 3 paragraphs are slightly abridged). Some my comments are presented at the end of this text (in chapter 3).

This entire article was typed-in manually, so some code damage is possible - code was NOT checked on compiler after typing-in. Apart from possible typos there were no any intentional modifications to presented code.

Also I apologize for my imperfect English - I’m not a native speaker. English in code presented without any modifications.

2. Article

C++ exceptions are essential part of a language. Unfortunately some compilers have no support for them. Additionally there are significant overhead, associated with standard exception processing and this overhead could be avoided by processing exceptions at a level of C++ program.

Exception handling mechanism described here was implemented as a part of "CHARMS" product, intended for program development under MS Windows on C++ (memory models ‘large’ and ‘Win32’). Source code presented here was debugged by means of Symantec C++ 6.11. Described exception handling mechanism is not suited for multithreaded mode, but it could be modified for it - to do this one will need to use threads’ local storage (Tls).

Microsoft tried to implement exception at C++ level in MFC. This implementation is not good since their mechanism do not perform destruction of local object during stack unwinding and there is no way to throw exceptions of arbitrary type (e.g. int, char). Exceptions handling mechanism presented here implements both of these features. Additionally it allows to organize exception handling scheme that is somewhat more optimal than one, described in ARM.

Unfortunately presented implementation has no mechanism for exception grouping, suggested by ARM: T type catch will not intercept exceptions of types, derived from T. That’s main drawback of this implementation.

For description of exception handling scheme let’s make following definitions:

‘Automatic’ object - object with ‘automatic’ storage class
Automatic object ‘exists’ if its destructor should be invoked during stack unwinding.

Scheme presented here diverge from one, presented in ARM in following respect:

Per ARM scheme object start existing only after it is completely constructed.
Per scheme presented here object start existing from a moment when explicit part of its constructor starts executing.

In practice this means that if explicit part of object’s constructor throws the exception then per ARM scheme only destructors for base and members will be called - destructor of object itself will not be called. Per scheme presented here object’s destructor will be called as well.

If exception occurred during base’s or member’s constructor then according to both schemes only already constructed parts of object will be destructed.

This discrepancy stems from inability to implement ARM scheme appropriately at a C++ level.

Let’s look at class "A" constructor that can throw the exceptions, implemented per ARM scheme:

struct X_A { // Type used for subsequent exception throwing

unsigned case_num;

X_A(unsigned case_n):case_num(case_n) {}

};

class MemBlock {

char *p;

public:

MemBlock(size_t sz,unsigned case_n) {

p=new char[sz];

if (p==NULL) throw X_A(case_n);

}

operator char * () const {return p;}

~MemBlock() {delete [] p;}

};

struct A {

MemBlock p1, p2, p3;

A():p1(100,1),p2(100,2),p3(100,3) {

// do something

}

~A() {

// do something

}

};

This class "A" could be implemented in a similar way by means of exception handling scheme, presented here:

struct X_A { // Type used for subsequent exception throwing

unsigned case_num;

X_A(unsigned case_n):case_num(case_n) {}

};

DeclareException(X_A,101) // Bind exception type with integer identifier

class MemBlock COLON_CHECKED { // "COLON_CHECKED" macro means that for

// automatic objects of class destructor will

// be called during stack unwinding.

char *p;

public:

MemBlock(size_t sz,unsigned case_n) {

p=new char[sz];

if (p==NULL) xThrow (X_A(case_n)); // "xThrow" macro is analogous to

// C++ "throw" operator

}

operator char * () const {return p;}

~MemBlock() {if (p!=NULL) delete [] p;}

};

struct A COLON_CHECKED {

MemBlock p1, p2, p3;

A():p1(100,1),p2(100,2),p3(100,3) {

resetChecked (); // Necessary to prevent duplication of destructors’

// calls for data members

// .....................

}

~A() {// do something

};

};

If class "A" uses default destructor then implementation may be simplified:

struct A {

MemBlock p1, p2, p3;

A():p1(100,1),p2(100,2),p3(100,3) {

// .....................

}

};

Finally there is one more way to implement class "A" using presented scheme:

struct X_A { // Type used for subsequent exception throwing

unsigned case_num;

X_A(unsigned case_n):case_num(case_n) {}

};

DeclareException(X_A,101)

struct A COLON_CHECKED {

char *p1,*p2,*p3;

A():p2(NULL),p2(NULL) { // From translator: Apparently that’s bug

// and should be p1(NULL),p2(NULL),p3(NULL)

if ((p1=new char[100])==NULL) xThrow(X_A(1));

if ((p2=new char[100])==NULL) xThrow(X_A(2));

if ((p3=new char[100])==NULL) xThrow(X_A(3));

}

~A() {

if (p1!=NULL) delete [] p1;

if (p2!=NULL) delete [] p2;

if (p3!=NULL) {

delete [] p3;

// do something // From translator: pretty funny code logic

}

}

};

For comparison of schemes let’s look at one more code example, written according to ARM:

struct A {

char *p;

A() {

p=new char [100];

if (p==NULL) throw ERR;

try {

// Exception could be thrown here

} catch (...) {

delete [] p; throw;

}

}

~A() {delete [] p;}

}

Now the same example, written according presented scheme;

struct A {

char *p;

A() {

p=new char [100];

if (p==NULL) xThrow (ERR);

exception_maybe_throwed_here();

}

~A() {if (p!=NULL) delete [] p;}

}

Exceptions handling scheme presented here provides following macros and functions to use for exceptions handling:

  1. "xTry" - equivalent of C++ "try" operator.
  2. "xTryEnd" - ends try-block.
  3. "xCatch(T,v)" - equivalent of C++ "catch(T v)" operator.
  4. "xCatchType(T)" - equivalent of C++ "catch(T)" operator.
  5. "xCatchAll" - equivalent of C++ "catch(...)" operator.
  6. "xThrow(v)" - equivalent of C++ "throw v" operator.
  7. "xThrow()" - equivalent of C++ no-parameters "throw" operator.
  8. "void terminate()" and "PFV set_terminate(PFV)" functions analogous to ones, defined in ARM.
  9. "Checked" class, used as a base for those classes whose automatic objects should be destructed during stack unwinding (presented system can distinguish automatic objects from all other). Such classes hereinafter will be named Checked-classes and their objects as Checked-objects.
  10. Macros:

 

#define COLON_CHECKED :protected Checked
#define CHECKED_COMMA protected Checked,
#define COLON_VCHECKED :protected virtual Checked
#define VCHECKED_COMMA protected virtual Checked,

  1. "void Checked::resetChecked()" method that Checked-class’ constructor should call if this class contain Checked-objects as data members.
  2. Macros:

"DeclareTypeId(T,id)",
"DeclareException(T,id)".

These macros bind types to integer identifiers. To use class T as exception type one need to use one of these macros.

 

  1. "void SetTheStackBottom(void __ss *p)" function used for exception handling system initialization. This function need to be called to enable distinguishing automatic Checked-objects. In "CHARMS" program this function is called during "CHARMS" initialization. In other programs this function may be used in a following way:

 

void main(int argc,...) {

SetTheStackBottom(&argc);

//.................................

}

or in a following way:

int PASCAL WinMain(HINSTANCE hInst,...) {

SetTheStackBottom(&argc);

//.................................

}

 

Mentioned in item 11 "resetChecked();" function requires special explanation. Suppose we have Checked-class "A" with data member of Checked-class "B". In this case "resetChecked();" should be the first operator in any "A"-class constructor - otherwise destructor of class "B" will be called twice during stack unwinding.

Before description of exception handling mechanism implementation let’s look at one example of its usage:

struct X_DC {

HWND hwnd;

X_DC(HWND wnd):hwnd(wnd) {}

};

DeclareException(X_DC,8801)

// Exceptions of this type will be thrown by constructors of classes

// for responsible window DC. For example:

class DC:CHECKED_COMMA public Rect {

DECL_ASN(DC)

HDC dc;

HWND hwnd;

public:

DC(HWND wnd) {

dc=GetDC(hwnd=wnd);

if (dc==NULL) xThrow(X_DC(hwnd));

CetClientRect(hwnd,(RECT *)this);

}

operator HWND ()const{return hwnd;}

operator HDC ()const{return dc;}

~DC () {

if (dc!=NULL) ReleaseDC(hwnd,dc);

}

};

// try-block usage sample

xTry {

DC dc1(hwnd);

// Perform drawing

} xCatch(X_DC,except) {

char buf[60];

sprintf(buf,"can not create DC for window %x\n", except.hwnd);

OutputDebugString(buf);

xThrow();

}

xTryEnd

Now let’s discuss the implementation.

System uses two stacks for its operation: try-blocks stack and Checked-objects stack. Stacks are implemented as lists. Stack of checked objects also is referred to as destruction list. Objects on top of these stacks are referred to as current. Pointers to stack tops and to bottom of current execution stack are stored in global variables that form a context of exception handling system. So if one will want to adapt this system to multithreaded mode he’ll need to use Tls. At the beginning of program execution both stacks are empty. When control is passed to try-block this block creates "TryToExecute" class object which became a top of try-blocks stack. At exit from try-block this object is removed from stack. "TryToExecute" constructor saves context of exception handling system. Destructor restores saved context and rethrows not processed exception (if any) in restored context.

struct TryToExecute {

jmp_buf buf;

Checked __ss *chk;

TryToExecute __ss *tr;

int type;

void *lvalue;

void OnError(int type,void *ptr);

TryToExecute();

~TryToExecute();

};

Following global variables form a context of exception handling system:

Checked _ss *TheLastChecked=NULL; // Top of Checked-objects stack

TryToExecute __ss *TheLastTryToExecute=NULL; // Top of try-blocks stack

int TheLastExceptionType=~0; // Index of exception type

void *TheLastExceptionLvalue=NULL; // Pointer to buffer that

// contain exception object

unsigned TheStackBottom=NULL; // Bottom of execution stack

The last variable is used to check whether some address belongs to stack:

int StackLocated(void __ss *p) {

return (unsigned(p)>SP) && (unsigned(p)<=TheStackBottom);

}

Following macros are used for try-block organization:

#define xTry {TryToExecute _TTE_; if (!setjmp(_TTE_.buf)) {

#define xCatch(t,val) } else if (typeTypeId(t)==_TTE_.type) { \

t &val=*(t *)(_TTE_.lvalue);_TTE_.type=0;

#define xCatchAll } else {_TTE_.type=0;

#define xCatchType(t) } else if (typeTypeId(t)==_TTE_.type) {_TTE_.type=0;

#define xTryEnd }}

"TryToExecute" constructor stores current destruction list and assigns NULL to global variable that points to top of execution list. Destructor of this class restores value of this variable and if there is a not processed exception it is being rethrown in new context.

Checking for presence of not processed exception performed as follows: constructor of "TryToExecute" zeroes the "type" field, at execution throwing this field is filled with exception type and if catch-block processed the exception it zeroes "type" field as well. Thus if destructor reveals that "type!=0" it concludes that there is not processed exception of type "type". This means that type identifier must be nonzero for any type.

To throw an exception following function is being used:

template<class T> void xThrow(T value)

{static T x=value;

OnException(typeId(value),&x);

};

However because there is a bug in Symantec 6.xx that do not allow compiler to precompile headers with template functions and because there are compilers that do not process templates at all there is a following macro:

#define DeclareException(T,Id) DeclareTypeId(T,Id) \

inline void xThrow(const T &value) \

{ checkExceptionSize(sizeof(T),Id); \

new(exceptionBuffer) T(value); \

OnException(Id,ExceptionBuffer); \

}

There are following routines that perform stack unwinding and catch-blocks processing:

void OnException(int type,void *p) {

TheLastExceptionType=type;

TheLastExceptionLvalue=p;

if (TheLastTryToExecute==NULL) {

if (TheLastChecked!=NULL) {

Checked __ss *p=TheLastChecked;

TheLastChecked=TheLastChecked->prev;

p->DestroyChecked();

} terminate ();

}

TheLastTryToExecute->OnError(type,p);

}

void TryToExecute::OnError(int _type,void *ptr)

{ if (TheLastChecked!=NULL) {

Checked __ss *p=TheLastChecked;

TheLastChecked=TheLastChecked->prev;

p->DestroyChecked();

}

type=_type;lvalue=ptr;

TheLastChecked=chk;

TheLastTryToExecute=tr;

longjmp(buf,~0);

}

void Checked::DestroyChecked() {

this->~Checked();

if (TheLastChecked!=NULL) TheLastChecked->DestroyChecked();

}

Checked::~Checked() {

if (StackLocated((void __ss *)this)) TheLastChecked=prev;

}

"DeclareTypeId(T,id)" and "typeTypeId(T)" macros and a "typeId(...)" functions are defined as follows:

#define DeclareTypeId(type,xx) \

inline int typeId(const type &) {return (xx);} \

inline int typeId(const type *) {return (xx);}

#define typeTypeId(T) ptypeId((T *)NULL)

Some header file declares integer identifiers for all C++ primitive types and pointers to them up to "***".

DeclareTypeId(short,1);

DeclareTypeId(unsigned short,2);

// ...............................

DeclareTypeId(unsigned long***,42);

DeclareTypeId(double***,62);

DeclareTypeId(char***,72);

Here is a definition of "Checked" class:

class Checked {

friend class TryToExecute;

friend void OnException(int type, void *p);

Checked __ss *prev; // Points to previous Checked-object

public:

NULL_ASN(Checked) // Neutralizes assignment operator.

Checked(const Checked &) {new(this) Checked ();} // Copy constructor

void DestroyChecked(); // This method calls destructors of this object

// and destructors of those automatic

// Checked-objects that are back in the list

// inside current try-block. If there is no

// enclosing try-block then all Checked objects are

// being destructed.

virtual ~Checked();

Checked();

void resetChecked(); // If Checked-object contains another

// Checked-objects then its constructor should call

// this method. This method removes from the list

// all objects, placed after this object.

};

Constructor of "Checked" class detects whether object being constructed is located on stack and includes it into destruction list only if it is.

Checked::Checked() {

#if M_I86LM || M_I86CM || M_I86VM

if (FP_SEG(this)==getSS())

#endif

if (StackLocated(this))

{ prev=TheLastChecked;

if (TheLastChecked!=(Checked __ss *)this)

TheLastChecked=(Checked __ss *)this;

}

}

Other parts of exception handling system are quite simple and any C++ programmer may implement them by himself.

3. One possible improvement

Exception handling system presented in article above is rather well thought-out. It seems, however that it has two limitations that could be avoided:

There should be a global static mechanism (header file) for binding types to unique integer identifiers.
As it was mentioned in the article it is impossible for "xCatch(T,v)" to catch exception of type derived from "T".

It seems that both of these problems stems from the common root: desire to throw exceptions of primitive types (like "int", "double *", etc.). Alternative approach is to require that all exception types should be derived from some specific base class (after all - isn’t it a C++ style?)

Having all exception types derived from single class we have a possibility for class self-identification (with type identifier being "void *" pointer to some static class member - kind of pseudo-RTTI) and for checking inheritance relationships between catch-operator’s class and exception object’s class (that’s even better than RTTI!) This technique is described in detail in John Lacos book "Large-Scale C++ Software Design" in appendix, devoted to protocol hierarchy design pattern (by the way - this book by itself is very good albeit rather large).

Up ]

 

 

Copyright 2008 mov Software