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 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_exceptionclass 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_exceptioninherits fromstd::exceptiononly. - 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:
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.
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.
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.
A very good and helpful article. Solved my problem, thanks for the article. (c) Ashish
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, 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.
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.
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; }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.
“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
Hmm. I thought that
GetExceptionCodeonly 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.Yes, you are right. Sorry.
Good article. Helps me!
Please provide information about getting stack trace
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.
Thanks for the information. The Microsoft documents are very misleading to an exception handling newbie.
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!
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.
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)
Excellent article, a must!
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
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
Other comment pages: « 1 [2]