Click here to Skip to main content
16,015,258 members
Articles / Desktop Programming / MFC

User Friendly Exception Handling

Rate me:
Please Sign up or sign in to vote.
3.20/5 (5 votes)
5 Aug 20036 min read 52.4K   576   17   4
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:

C++
__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:

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

C++
DO_ENTER_TRY_BLOCK
DO_EXIT_TRY_BLOCK_DLGPROC("ProcName",message,wParam,lParam);

for message type callbacks, and:

C++
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:

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

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


Written By
Web Developer
United Kingdom United Kingdom
Trained as a mathematician.

Now I program shareware apps - music and 3D. Main ones so far, Fractal Tune Smithy (music) and Virtual Flower (3D / VRML).

Comments and Discussions

 
GeneralNice, but seldom needed Pin
Patje6-Aug-03 22:11
Patje6-Aug-03 22:11 
GeneralRe: Nice, but seldom needed Pin
Robert Inventor7-Aug-03 0:48
Robert Inventor7-Aug-03 0:48 
Hi Patje,

Thanks for the interesting points. Yes surely one needs to be aware of possibility of corruption and allow for it, and it is an omission in the article not to say anything about that.

Perhaps one should add a warning to the message to say that any files saved after the exception may be corrupted. Maybe suggest that user saves it under another name. If as a user one has been working and then an exception occurs one might prefer to be able to save a possibly corrupted file than to lose ones work. That way you give the user the choice about whether to try to save it anyway even if it may be corrupted.

I can see it depends on the type of document. If you are saving word documents or as in my case, fractal music compositions or 3D flowers, only the current one can be affected. Also in the situation where I use it, the chance of corruption is small as the most complex part of the code is to do with displaying the flower or playing the tune and if an access violation occurs it is probably going to happen there - or maybe when it gets printed or saved as animation frames. Whlle debugging my apps I have had access violations but never anything that affects the saves in such a way as to make them unusable. Occasionally individual variables in the saves may be affected, and user will probably have seen (or heard) the effect of that anyway after the violation. So I think the chance of them being affected in a user access violation is very low. So for example one might prefer the possibility of getting one or two strange characters in the saved word document to losing it all, so long as one knows that could happen after an access violation.

Probably depends somewhat on the method you use for storing the data in the program internally too, and whether it is especially vulnerable to being changed after an exception.

If you are adding an entry to a database then the whole database could be affected and indeed surely one might not want to do that. One could make it impossible for user to save to any already existing database after the exception, but permit a save under a new name. That solution could be done generally - maybe add something to the file name to indicate that it is an emergency save. Or perhaps one could add extra options to validate the document or database before you save it. (And of course make sure the validation code is particularly thoroughly tested and not going to introduce exceptions itself!)

Generally the programmer will have had experience of access violations while debugging the code so should have an idea of what kind of effect they could cause and whether they are likely to affect saves. If they often do affect the whole document in such a way as to make it unintelligible then one would need to use those options such as to only permit saves with something added to the file name to indicate that it is an emergency save. Also with some types of program it would be totally unsuitable to continue after an access violation and I recognise that.

It is really intended as a way to let user save in an emergency - then send a report about it to the programmer. Making it really easy to send in a report is an important part of the method - and one should only do it if you have support which can respond to all bug reports promptly - which hopefully most developers will have!

Robert
GeneralRe: Nice, but seldom needed Pin
Patje7-Aug-03 2:33
Patje7-Aug-03 2:33 
GeneralRe: Nice, but seldom needed Pin
Robert Inventor7-Aug-03 12:06
Robert Inventor7-Aug-03 12:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.