An ‘extended’ exception class

Environment: Win98, VC 5.0

Description:

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:tempinputfile’, 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:tempinputfile’).
  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 1nline 2nline 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 :

try
{
CString filename = "c:\temp\inputfile";
if(!SomeCall(filename))
{
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
{
public:
DECLARE_DYNAMIC(CNBException)
// Constructor
CNBException(int cause,
CString param1 = "",
CString param2 = "",
CString param3 = "",
CString param4 = "");
// Attributes
int m_cause;

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

// Implementation
public:
virtual ~CNBException();
#ifdef _DEBUG
virtual void Dump(CDumpContext&) const;
#endif
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);*/}
try{
#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__);
CNBException
/*
* Just a wrapper to be consistent.
*/
#define NBDeleteException(e) (e)->Delete()

Download:

Demo project and source   ext_exception.zip (17 KB)

More by Author

Must Read