Introduction
As a user, how often have you been in this situation? You are working in a
program and then without warning an exception occurs and you lose all your work.
Few programs seem to have exception handlers in the release builds. One can
understand this from the developer's perspective - if you add in an exception
handler it will hide the problem. You want to get a bug report with an exception
address from the user if possible. We all hope that by the time our program gets
to a release then there will be no more exceptions left in the program. But
despite best efforts, sometimes itmay still have a few remaining exceptions. So
how can we handle these in a user friendly fashion, and at the same time make it
easy for the user to send bug reports to the developer with exception addresses?
The answer is to write an exception handler that will report the exception
address - but permit the program to continue execution. The user then has an
opportunity to save his or her work, and if the exception isn't serious, maybe
just continue with the work session. To make it easy for the user to send in a
bug report then write the exception handler so that it will offer to make an
e-mail for the user to send to the developer. The e-mail can then include any
technical details about the exception as an attachment, and in the body one can
encourage the user to add any more details they remember about how it happened.
So - that is how I do it now in all my release builds. This has evolved
gradually over the years, and so here I present my most recent exception
handler. I use it in programs written in pure C but perhaps it can be used in
C++ too. The demo program is a simple Hello World program with an exception
handler in cpp. The cpp file in the zip is also valid c code so if by chance you
are writing in pure c as I do, just save it with extension .c instead of cpp and
replace the #include <stdafx.h> with #include
<windows.h>
Background
Well you need to know about exception handling. This code
uses
__try{ ... } __except(..){...} blocks.
Then you need to have a way to obtain the exception address. Look up the
documentation, and you find that what you have to do is to call a procedure
within the __except statement itself like this
__except( Eval_Exception(GetExceptionCode( ), GetExceptionInformation() )
{
...
}
Then you use it to set a couple of global variables which the code can then
inspect at its leisure - and so that is what I do here.
The exception information is only available at that point. You can't obtain
it in the block that follows the __except(...) statement.
Using the code
Well probably you will evolve your own version of this -
it is more a demo of the concept than a piece of code to add to your program.
But if you want to use this source code just as it is, here is how to do it.
Note that the example code is written for builds with UNICODE undefined - in a
UNICODE build you would have to do something about the uses of
sprintf etc.
Include ExceptionHandler.h, ExceptionHandler.cpp and
ExceptionHandlerUtils.cpp in your build.
You also need to set these variables appropriately, as in the demo
#define SZ_EMAIL "support@tunesmithy.co.uk"
#define SZ_NAME "Robert Walker"
extern char szWorkingDirectory[MAX_PATH];
extern char szAppName[MAX_PATH];
In your code you need to bracket all places where an exception may occur with
DO_ENTER_TRY_BLOCK
DO_EXIT_TRY_BLOCK_DLGPROC("ProcName",message,wParam,lParam);
for message type callbacks, and
DO_ENTER_TRY_BLOCK
DO_EXIT_TRY_BLOCK_PROC("ProcName");
for other procs.
The ProcName string here gets shown in the exception report that
the handler generates.
The point in this is that sometimes an exception may occur in a small
subroutine that gets called from many places in the code - and even if you know
which line the exception occurred in, you may need to know a bit about where
that subroutine got called from. You can nest these within each other of course
so you would put them around all the large procs, any part of the code which you
would like to be able to recognise as the scope of an exception. At present the
report shows just the label of the block that handles the exception, but one
could easily elaborate it to show a call stack type report of the labels for all
the nesting exception blocks above it.
Here is my definition of these macros:
#ifdef cl_exception_handler
#define DO_ENTER_TRY_BLOCK\
__try\
{
void ExceptionHandlerForDlgProc(char *sztype,UINT message,WPARAM wParam,LPARAM lParam);
void ExceptionHandler(char *sztype);
int Eval_Exception ( int n_except,LPEXCEPTION_POINTERS ExceptionInfo);
#define DO_EXIT_TRY_BLOCK_DLGPROC(szName,message,wParam,lParam)\
}\
__except(Eval_Exception(GetExceptionCode(),GetExceptionInformation()))\
{\
ExceptionHandlerForDlgProc(szName,message,wParam,lParam);\
}
#define DO_EXIT_TRY_BLOCK_PROC(szName)\
}\
__except(Eval_Exception(GetExceptionCode( ),GetExceptionInformation()))\
{\
ExceptionHandler(szName);\
}
#else
#define DO_ENTER_TRY_BLOCK
#define DO_EXIT_TRY_BLOCK_DLGPROC(szName,message,wParam,lParam)
#define DO_EXIT_TRY_BLOCK_PROC(szName)
#endif
and I usually define cl_exception_handler only in the release builds as in
the debug build one prefers to break at the exception. When running in a
debugger, if you want to continue excecution after the exception you can just
set the next statement to the statement after the exception and keep going.
Then for the automatic dating of the bug reports with the build date, you
need to add the version date somehow. I define it in a header that gets remade
every time I run the debug build Anyway use some method that guarantees that
each release build has a unique identifying date in it - and then backup the
source code (including the date header of course) for each upload you do.
Here is how I do it:
#ifdef _DEBUG
char *szVersionDate="<DATE builds release in here shown gets>";
#else
#include "VersionDate.h" #endif
When you receive the exception report from a user, then you will want to
find out where the exception happened in your code, which you can figure out
from the exception address by making a Map file for the program. For details
about this, see
Finding crash information using the MAP
file
If you version date all your code automatically and backup the code for every
build you upload then you don't have to make a Map file in advance, but
can look through your backups to find the one with the appropriate version date
re-build the app when you get the report and do the Map file then.
Note I use MessageBox rather than a custom dialog for the
exception messages because of the possibility that the app may be running with
resources so low that it is impossible to make a new dialog to show. The
MessageBox dialog is guaranteed to be always available.
History
First release 2nd August 2003
Links
Finding crash information using the MAP file