Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

User Friendly Exception Handling

0.00/5 (No votes)
5 Aug 2003 1  
How to handle exceptions in release builds in a user friendly fashion

Exception handler in action

Introduction

As a user, how often have you been in this situation? You are working on 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 it may 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"

// Set these in your WinMain
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 recognize 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 execution 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" // Save this every time we run the debug build.
 // so that the exception message can include the date of the release
 // which we can check to make sure we have the same version of the source as
 // the user. See the beginning of WinMain(...) for example to show how one can make it.
#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

  • 2nd August, 2003: First release

Links

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here