An 'extended' exception class

Tuesday Mar 2nd 1999 by Bernard Niset

An 'extended' exception class

Environment: Win98, VC 5.0


For quite a long time now, I'm using the exception class I'm presenting in this article and for a reason I can't figure out, I don't see any equivalent anywhere in specialized web sites or in class libraries.  Actually, I have no idea how far this exception class is clever, but I can tell you that it's very handy and efficient to use.

My point about a scheme is meant to deal with the error/exception handling is that it has to be EASY.  The reason is that programmers don't *naturally* deal with errors.  They generally suppose when they write code that everything will be just fine and that they (and their code) are invincible.  So an exception system must be easy to use : 1. at the place where, in the code, you detect the error, and 2. at the place where you decide to do something with the error.

MFC is offering classes that use the C++ exception mechanism.  Those classes handle several errors that can happen when you do a certain amount of system operations, but they do not handle everything that can happen.  Also, there is no real usable User Exception class : CUserException requires a different type of handling.

When we go further in the analysis, we notice that we might want to catch an exception to report the message that comes to the system AND report the user a message that is more referring to the task (function) he was currently doing in the application.  For exemple, the messages arriving to the user when doing an import of data could be : 1. "Could not open the file 'c:\temp\inputfile', permission denied", and 2. "The application could not complete the importation process".  I think it's no problem if, regarding an exception, two messages arrives to the user.  More could drive him crazy.

So let's gather what we will be asked from this new exception class :

  1. It should be beyond the other 'system' exception classes.
  2. The messages reported to the user should not be stored in the code but in an external structure (file or whatever), so that it can be easily modified, extended, etc.
  3. The messages should be able to deal with positionned parameters so that we are done with those kind of messages : Could not open file (we prefer to see : Could not open file : 'c:\temp\inputfile').
  4. It should provide some debugging help like : the exact place in the code where the error was detected, a simple way to go into debug (DEBUG builds only).
  5. It should have an alternative mode where no message box are showed (for batch processes).
  6. Be a derivative of the MFC exception mechanism so that handling them is done with the same code is the same as handling MFC exceptions.

The response to this is the CNBException class and the global functions that comes with it.  CNBException is a derivative of CException and it does not much. When you throw an CNBException, you specify an error number (a negative number) and eventually parameters (always of string type).  The GetErrorMessage method in CNBException is written to seek a file (by default 'main.err') for the message number and get the corresponding message string.  GetErrorMessage also replaces the positional parameters in the string.

The syntax for the error message file is easy :

; This line is a comment
-100,Error message correponding to error number : -100
-101,Your message can be multilined, line 1\nline 2\nline 3
-102,Message with a parameter '%1'

The message file must be stored in the same directory of the executable or dll.  Actually, the function that searches for the file (search_error) is tricky because I noticed that, during the development process, it is not convenient to place the error file in the Debug directory that is seen as output only.  During the development cycle, the file would rather stand in the main development directory where the other sources files are.  That's why the search_error function searches for the file first in the directory of the executable, then, if not found there, goes a directory upper, if not found it even goes a directory upper (why should we stop in such a good way) because you might want to share it with other projects and put it in a more global directory.

Here is how to use it :

	CString filename = "c:\\temp\\inputfile";
		NBThrowException(-200, filename);
catch(CException *e)
	NBReportError(e); // You can also use e->ReportError()	
	NBDeleteException(e); // It actually does e->Delete()
Here is the header file nbex.h where you'll find some commentary on the classes, functions and macros.
class CNBException : public CException
// Constructor
	CNBException(int cause, 
		CString param1 = "",
		CString param2 = "",
		CString param3 = "",
		CString param4 = "");
// Attributes
	int     m_cause;

	CString m_param1;
	CString m_param2;
	CString m_param3;
	CString m_param4;
// Operations

// Implementation
	virtual ~CNBException();
#ifdef _DEBUG
	virtual void Dump(CDumpContext&) const;
	virtual BOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
		PUINT pnHelpContext = NULL);
	int ReportError(UINT nType = MB_OK, UINT nMessageID = 0);

 *   Internal function used to set file and line anchor. 
 *   Those are stored in static variables (no way to do it
 *   differently).
DECL_EXP(long) _NBExSetFileLine(char *file, int line);
 *	  Use this function to set the system to use a different
 *    error file than the default one ('main.err').
DECL_EXP(void) NBSetErrorFileName(LPCTSTR sFileName);
 *    Use this function to report error to the user.  It works
 *	  exactly like the standard CException::ReportError method
 *    except that :
 *       - it takes into account the ErrorMode
 *       - in debug builds, the message box will have OK/Cancel
 *         buttons.  CANCEL goes to debug mode (it calls AfxDebugBreak().
DECL_EXP(void) NBReportError(CException *e);
DECL_EXP(void) NBReportError(CException& e);
 *  The error modes can have the value 0 or 1.
 *      - 1 is the default mode, ReportError shows a dialog
 *        to the user.
 *      - 0 is an alternate mode where the message is stored in
 *        internal buffer.  You can recall the last error message
 *        by calling NBGetLastErrorMessage.
DECL_EXP(void) NBSetErrorMode(int mode);
 *  Retrieves the last error message.
DECL_EXP(CString) NBGetLastErrorMessage(void);
 *   Gets the current error mode.
DECL_EXP(int) NBGetErrorMode(void);

 *   Enclose some code between NBTRY and NBCATCH when you don't want
 *   an exception to modify the normal flow of the program.
#define NBTRY try {
#define NBCATCH  } catch(CException *e){e->Delete(); /*ASSERT(FALSE);*/}
#define ANDTRY  } catch(CException *e){e->Delete(); /*ASSERT(FALSE);*/}\
#define NBCATCHASSERT  } catch(CException *e){e->Delete(); ASSERT(FALSE);}

 *   Use this macro in order to use the File, line feature of the
 *     exception mechanism.
#define NBThrowException	_NBExSetFileLine(THIS_FILE, __LINE__),\
								throw new CNBException
#define NBStackException	_NBExSetFileLine(THIS_FILE, __LINE__);\
 *   Just a wrapper to be consistent.
#define NBDeleteException(e) (e)->Delete()


Demo project and source   ext_exception.zip (17 KB)

Mobile Site | Full Site