Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
About a year ago I started a consultancy project for a firm that used OpenVMS as their primary operating system. As you probably know, one of the designers of VMS also was the designer of Windows NT. That shows in the way that NT and, as far as it goes, Windows'95 are designed and more specific handle hardware exceptions.
Also, I recently looked through Borland C++ Builder How-To and found a topic on structured exception handling. I already did some work on structured exception handling for OpenVMS and for Borland C++, and while planning this article I stumbled on a topic on this matter in this book. While otherwise being a good resource I found the remarks in this topic not truly accurate. My conclusion is that structured exceptions are integrated quite well in C++Builder, provided that you link in SysUtils! If you do not want to link in SysUtils for some reason, then an unhandled exception aborts your program.
Structured exception handling
Exception handling differs from signal handling.
The exception handling mechanism makes sure that objects created on the stack are destroyed while unwinding the stack.
If an appropriate handler is reached, the stack unwinding process is stopped.
In OpenVMS and Windows NT this is implemented by a chain of handlers that look at the exception code that is passed by the operating system to decide whether they are able to handle that particular exception.
OpenVMS implements this by having three global function pointers in its process table.
These pointers point to the first chance, second chance and last chance exception handler.
For 'local' exception handlers it pushes the address of that handler or a zero if there is none.
Windows does this by having the top-level handler in its process data.
For threads you can point to a separate top-level exception handler in the thread process data.
Local exception handlers are implemented by inserting the address of the exception handler in front of the list of exception handlers.
Anyway, FS:[0x0000] points to the head of the exception chain.
If a structured exception is raised, the chain of exception handlers is walked and every exception handler in the chain has its chance to handle the exception.
If it can not or will not handle the exception, it is passed to the next handler in the chain.
If it is not handled by any of the exception handlers in the chain, it is handled by the default exception handler, which in most cases simply aborts the program.
Since the chain is a linked list, the last element in the linked list contains a pointer to a next handler that equals -1 to signal the end of the chain.
If an exception handler is found in a certain stack frame, the stack of the process incurring the exception is unwound.
This means that the objects created on the stack are destroyed.
C++ roughly does the same, but it makes sure that destructors for user defined objects are called.
Aside: letting the coprocessor generate exceptions
Per default, the runtime library of Borland C++ and Borland C++Builder, which are very alike if you compare Borland C++Builder and Borland C++ version 5.02, initialise the coprocessor in such a fashion that it will not generate an exception if you attempt to divide by zero.
Actually, dividing a positive floating-point number by 0.0 will yield +INF, which means positive infinity.
If that is behaviour we do not want, we can influence it by setting a bit in the coprocessor that tells it not to handle division by zero to yield +INF or -INF, but to generate a hardware exception.
In fact, that might be a better way to handle such a border case than checking every time for infinity or checking the denominator of a division every time.
If you instruct the coprocessor to generate an exception when you attempt to divide by zero, you actually let your hardware do the work for you instead of using software to check the results of the division.
The method to do that is to use the _control87 function to set the appropriate exception mask on the coprocessor.
Have a look at
#include <float.h> unsigned int oldx87mask = _control87(0, MCW_EM);To unmask a specific exception bit, you use:
_control87((_control87(0, 0) | ~EM_xxxx), MCW_EM);Actually, this will also unmask exceptions generated for dividing integers by zero. To restore the old situation we of course execute:
_control87(oldx87mask, MCW_EM);
Influencing the exception handler chain
You can influence the exception handler chain by inserting your own handler at the head of the chain.
This is roughly the safest approach.
Since we always have an object oriented approach, we use a class for it.
The beauty of that is that we can integrate a structured exception into the exception hierarchy of the standard C++ library if you so desire:
class XOS: public runtime_error { public: XOS(_EXCEPTIONREPORTRECORD* exRec, _CONTEXT* cxPtr); XOS(const XOS& that); const XOS& operator=(const XOS& that); ~XOS() throw(); static void XOS::SetExceptionHandler(); // Set translator function to our static Translator method. // Can be used only once. Any subsequent use does nothing. // PRE: oldtranslator == 0 // POST: oldtranslator points to previous translator routine // EXC: - static void XOS::RevertExceptionHandler(); // Revert translator function to old setting. // Can be used only once. Any subsequent use does nothing. // PRE: oldtranslator != 0 // POST: oldtranslator = 0 // EXC: - friend ostream& operator<<(ostream& ostr, const XOS& obj); // Print contents of object on ostream. Use as for example // cout << xosobject << endl; simply calls printon. no need // to specialize in derived classes. // pre: - // post: content of object output on ostr // exc: - protected: virtual void PrintOn(ostream& ostr) const; // PRE: - // POST: content of object output on ostr (partial) // EXC: - private: XOS(); _EXCEPTIONREPORTRECORD* er; _CONTEXT* cx; };And then offcourse the implementation:
unsigned long (*oldhandler)(_EXCEPTIONREPORTRECORD* p, _EXCEPTIONREGISTRATIONRECORD* q, _CONTEXT* r, void* s) = 0; unsigned long ExceptionHandler ( _EXCEPTIONREPORTRECORD* p, struct _EXCEPTIONREGISTRATIONRECORD* /*q*/, _CONTEXT* r, void* /*s*/ ) { #define CPP_EXCEPT_CODE 0x0EEFFACEL #define PAS_EXCEPT_CODE 0x0EEDFACEL unsigned long result = XCPT_CONTINUE_SEARCH; if ((p->ExceptionNum != CPP_EXCEPT_CODE) || (p->ExceptionNum != PAS_EXCEPT_CODE)) { result = XCPT_CONTINUE_EXECUTION; throw XOS(p, r); } return result; } // public XOS::XOS(_EXCEPTIONREPORTRECORD* exPtr, _CONTEXT* cxPtr): runtime_error(string("XOS: ")), er(exPtr), cx(cxPtr) {} XOS::XOS(const XOS& that): runtime_error(that), er(that.er), cx(that.cx) {}XOS& XOS:: =(const XOS& that) { if (this != & that) { runtime_error::operator=(that); er = that.er; cx = that.cx; } return *this; } XOS::~XOS() throw() {} void XOS::SetExceptionHandler() { if (oldhandler == 0) { oldhandler = _SetUserHandler(ExceptionHandler); } } void XOS::RevertExceptionHandler() { if (oldhandler != 0) { _SetUserHandler(oldhandler); oldhandler = 0; } } ostream& operator<<(ostream& ostr, const XOS& obj) { obj.PrintOn(ostr); return ostr; } // protected void XOS::PrintOn(ostream& ostr) const { ostr << runtime_error::what() << " error " << hex << er->ExceptionNum << "@" << er->ExceptionAddress << ", " << er->cParameters << " parameters." << endl; } // private XOS::XOS(): runtime_error(string("Private XOS::XOS() called!")) {}
Conclusions
Structured exception handling is integrated in the VCL exception hierarchy, however, if we can't or won't use SysUtils, it is quite easy to duplicate the behaviour of the VCL for console applications.
I originally developed this for OpenVMS and later adapted it to Microsoft Visual C++.Then I rewrote it for Borland C++ version 5.02 and last but not least adapted it for C++ Builder.
In those two steps, not one modification apart from some pragma's was necessary, which shows that the runtime libraries of both Borland C++ 5.02 and Borland C++ Builder are very compatible.