Example: Turning Win32 exceptions into C++ exceptions
Here a sample implementation of a win32_exception class hierarchy. First, the header file for the exception classes:
#include "windows.h"
#include <exception>
class win32_exception: public std::exception
{
public:
typedef const void* Address; // OK on Win32 platform
static void install_handler();
virtual const char* what() const { return mWhat; };
Address where() const { return mWhere; };
unsigned code() const { return mCode; };
protected:
win32_exception(const EXCEPTION_RECORD& info);
static void translate(unsigned code, EXCEPTION_POINTERS* info);
private:
const char* mWhat;
Address mWhere;
unsigned mCode;
};
class access_violation: public win32_exception
{
public:
bool isWrite() const { return mIsWrite; };
Address badAddress() const { return mBadAddress; };
private:
bool mIsWrite;
Address mBadAddress;
access_violation(const EXCEPTION_RECORD& info);
friend void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info);
};
A few notes on these classes:
- The basic class is
win32_exception
. To set up the enhanced exception handling in a thread, you should callwin32_exception::install_handler()
. This must be done in each thread for which you need the enhanced exception handling. - Since access violations represent a common program bug, and since such exceptions have more information available (the type and location of the bad memory access), there is a specialised derived class for this case only.
- The
win32_exception
class inherits from the standard library exception classes for consistency. However, even though access violations and divisions by zero could legitimately inherit fromstd::logic_error
, this is not the case in general for Win32 exceptions. Therefore,win32_exception
inherits fromstd::exception
only. - The constructors are private, since only
win32_exception::translate()
is allowed to instantiate these exceptions. - The two classes are tightly coupled (note that the base class’s
win32_exception::translate()
is a friend of the derived classaccess_violation
). This is fine in this lean implementation, but if you wanted to create a large hierarchy of separate classes for each kind of Win32 exception, you’d want to change this design.
Now here’s the implementation of these classes:
#include "win32_exception.h"
#include "eh.h"
void win32_exception::install_handler()
{
_set_se_translator(win32_exception::translate);
}
void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info)
{
// Windows guarantees that *(info->ExceptionRecord) is valid
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
throw access_violation(*(info->ExceptionRecord));
break;
default:
throw win32_exception(*(info->ExceptionRecord));
}
}
win32_exception::win32_exception(const EXCEPTION_RECORD& info)
: mWhat("Win32 exception"), mWhere(info.ExceptionAddress), mCode(info.ExceptionCode)
{
switch (info.ExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
mWhat = "Access violation";
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
mWhat = "Division by zero";
break;
}
}
access_violation::access_violation(const EXCEPTION_RECORD& info)
: win32_exception(info), mIsWrite(false), mBadAddress(0)
{
mIsWrite = info.ExceptionInformation[0] == 1;
mBadAddress = reinterpret_cast<win32_exception::Address>(info.ExceptionInformation[1]);
}
For more details on EXCEPTION_RECORD
and the suspicious-looking cast in the access_violation
constructor, look up EXCEPTION_RECORD in the MSDN Library.
And finally, here’s part of a program that uses these classes:
#include "win32_exception.h"
#include <iostream>
int main() {
win32_exception::install_handler();
try {
DoComplexAndErrorProneThings();
}
catch (const access_violation& e) {
std::cerr << e.what() << " at " << std::hex << e.where()
<< ": Bad " << (e.isWrite()?"write":"read")
<< " on " << e.badAddress() << std::endl;
}
catch (const win32_exception& e) {
std::cerr << e.what() << " (code " << std::hex << e.code()
<< ") at " << e.where() << std::endl;
}
}
If DoComplexAndErrorProneThings()
attempts to write through a null pointer, a win32_exception will be thrown and something like the following will appear.
Access violation at 72a5ee00: Bad write on 00000000
The great thing about this is that the call stack unwinds correctly as the exception is being thrown: local objects are destroyed, resources are released, and log messages can be written, just as if a normal C++ exception had been thrown — because it has!
The EXCEPTION_POINTERS structure that gets passed to win32_exception::translate()
contains a lot of other information. In particular, you can use it to get a stack trace, which would be useful for debugging. This would complicate the exception classes a bit, so I haven’t done it here.
Summary
Visual C++ treats certain runtime errors such as access violations in a similar way to regular C++ exceptions. Unfortunately, the default compiler and project settings in pre-2005 versions cause this to happen in a half-baked way, leading to unstable programs. Visual C++ 2005 treats such errors in a more predictable way: by crashing your program.
However, by writing a small amount of extra code, such errors can be handled exactly the same way as C++ exceptions. This greatly aids tracking down mysterious crashes, and allows programs to recover from bugs that normally would prove fatal.