Visual C++ exception handling

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.

Share This | Comments | Permalink | Trackback | Comments feed

26 responses

Other comment pages: « 1 [2]

  • nice article,

    could_be_a_bunch_of_rubbish(

    you always can mix old c try - except exception handling with c++ using inline asm, in this way you can guard sections of code wich can only produce win32 exceptions in a safe way and being able to get crash information. This type of exceptions have nothing to do with c++ exceptions so there is no problem.Later you could pack those inline assemblies into macros. In this way it would be very easy, i mean something like this:
    _TRYEXCEPT(handler)
    code
    _END

    );

    that idea come to me from a far place on my memory so.. isnt very reliable, an intuition.. dont hit me if in fact its rubbish :)
    anyway, I need all this things to do some stuff so i have to take a closer look at it, i will post again whenever i have more info ;). happy christmas.

    simkin | 23 December 2005
  • Good article
    Don’t know if you’ve come across this before…
    It would appear that you need to register the handler for each new thread you create. I tried doing it once in main(), but my other threads still showed the default behaviour until I explicitly registered the handler for each one as it started.

    Sandy McPherson | 14 June 2006
  • A very good and helpful article. Solved my problem, thanks for the article. (c) Ashish

    Fischer | 30 June 2006
  • I think you wrong about Listing 3 in “How optimisation affects exception handling” part. In my Visual Studio 2005 both listings give same result: unhandled exception. In a real sense, this additional statement should not affect on exception handling mechanism, so it is.

    Noid | 5 September 2006
  • Noid, this article was originally about VC++ versions 6 and 7, not VC++ 2005. You’re right that the behaviour has changd in VC++ 2005. I have updated the article appropriately. I also fixed some formatting problems, and I now mention threading as suggested by Sandy above.

    Thanks for your help in making the article more useful and up-to-date.

    Bennett | 5 September 2006
  • I’ll never say anything geekier, but your article was an absolute joy to read.

    It will make SEH 10 times easier in the unit testing system I’m working on. Many thanks for taking the time to put this all together.

    Edd | 19 October 2006
  • I have one more question : In the below piece of code, though the exception is caught, the destructor for t1 is not getting called. I know using the /EHa compiler option will solve this. But I don’t want to use this as it adds a overhead and reduces the speed of the program. Is there any other solution for this?

    #include "stdio.h"
    class thing
    {
      public:
          thing() { printf("hello ");  }
          ~thing()       {           printf("goodbye ");       }
    };
    
    int main(int argc, char* argv[])
    {
        try {
          thing t1;
          printf(”try “);
          char* cp = 0;
          *cp = ‘a’;
        }
        catch (…)
        {
            printf(”catch “);
        }
        printf(”exiting “);
        return 0;
    }
    
    Sunil | 9 November 2006
  • stdio.h? printf? You’re obviously a C programmer at heart. :)

    /EHa is the only reliable way to solve this. Anything less will not guarantee that destructors will be called in all situations.

    In the unlikely event that the speed overhead actually makes any measurable difference, you can turn off /EHa for the speed-critical parts of your program. Most programs spend most of their time executing only a few small segments of code. Use a profiler to find out which segments these are, debug them until you’re sure that there won’t be any asynchronous exceptions, and then compile (only) those parts without /EHa.

    Bennett | 10 November 2006
  • “Furthermore, no information will be available about the Win32 exception.”

    This is not true. There is the GetExceptionCode Macro which one can use to determine the type of exception.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/getexceptioncode.asp

    Markus Klinik | 7 December 2006
  • Hmm. I thought that GetExceptionCode only works within an SEH exception handler. I would be surprised if it works inside a C++ catch(...) block — but then I have been surprised before.

    Bennett | 9 December 2006
  • Yes, you are right. Sorry.

    Markus Klinik | 12 December 2006
  • Good article. Helps me!

    Please provide information about getting stack trace

    Packia | 29 December 2006
  • Packia, as I said: “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…”

    Try looking up EXCEPTION_POINTERS on MSDN Library.

    Bennett | 30 December 2006
  • Thanks for the information. The Microsoft documents are very misleading to an exception handling newbie.

    Jason Powell | 8 February 2007
  • Jason, it can take a bit of investigation and preparation to set everything up, but once you get it working it’s really nice. Now you too can recover from null pointer references, just like all your fancy C# friends!

    Bennett | 8 February 2007
  • Great description of a potentially tricky issue. I rarely read such well written pieces.

    The C++ boost (v 1.33.1) unit testing library pre-built didn’t get built with the ‘async exception model’ and couple of the self-tests broke in a way which I think you’ve just explained for me.

    Also, good font and layout choice, which made it not only worthwhile to read, but also pleasant to read.

    John McM | 1 May 2007
  • VS8 allows you to keep the (bad) old behaviour by using the /EHa flag (project properties -> C/C++ -> Enable C++ Exceptions = Yes with SEH Exceptions)

    Motti Lanzkron | 28 May 2007
  • Excellent article, a must!

    Dominic C | 8 November 2007
  • There is a pblm with me…that I want to upgrade a project that is successfully compiling in C++ compiler 5.02…I want to compile it in C++ builer 6.0..but it gives error..as in this project try and except is used …error is for except()…and error is “catch expected”…what I do to resolve this error…help me

    Sushil | 8 December 2007
  • Hi,

    This article well describes the Exception Handling in C++. I am able to understand it very well. Is there any way we can get the File and line no where exception occured ?

    Raghavan

    Raghavan | 28 June 2008

Other comment pages: « 1 [2]

Post a comment

(required)
(required)
Close
E-mail It