Visual C++ exception handling

Thursday, 15 August 2002

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:

Listing 5: win32_exception.h

#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 call win32_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 from std::logic_error, this is not the case in general for Win32 exceptions. Therefore, win32_exception inherits from std::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 class access_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:

Listing 6: win32_exception.cpp

#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:

Listing 7: main.cpp

#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.

Pages: 1 2 3

Tags:

41 comments

You can leave a comment, or trackback from your own site.

Leave a comment