Generic Error Object

 

 

1.                Architecture

1.1.              OVERVIEW

The architecture description defines the interface and functionality for a generic error object. The intent is to create an error object that behaves as a bit-field, but allowing more than 32 bits of error flags. Normally, this would require defining several words to hold the error flags, and each error word would have to be checked. If the programmer forgets to check every error word, then the software may ignore certain types of errors. This object prevents that type of defect by encapsulating the error flags and error checking into an object.

To expand to more than 32 error flags requires using several DWORDs and a method of choosing which DWORD is accessed. Had the programmer been required to scroll through error groups then the original reason for this object would be defeated – the purpose is to isolate the programmer from the implementation of the error flags and to make the error code uniquely identifiable with a single DWORD. Since a DWORD is 32 bits, some form of structure or array is required to have more than 32 error flags.

This object creates four groups of error flags, each group with 30 bit flags. Errors can be checked by specific error, by group, or by all groups. This gives a total of 120 error flags that can be defined by this object.

The 32 bits in the error flag will be defined as 30 error bits and 2 address bits. The address bits identify which DWORD will be used. Errors are separated into groups and can be checked by groups or all together.

1.2.              Classes, Data Types and Variables

In addition to the standard C and Windows data types, the following classes and data types will be defined by the object and available to anyone using the object.

Class Name

Usage

CErrorObject

Class that implements the API in this document.

 

Data Type

Usage

ERRORTYPE

DWORD. Defines a variable that is an error code.

The following constants will be defined by the object and available to anyone using the object.

Data Type

Usage

NOERRORFLAGS

DWORD. Means that no error exists. Defined as 0L.

GROUP1

DWORD. Identifies the first error group. 0x0

GROUP2

DWORD. Identifies the second error group. 0x1

GROUP3

DWORD. Identifies the third error group. 0x2

GROUP4

DWORD. Identifies the fourth error group. 0x3

ERRORFLAG1 through
ERRORFLAG30

DWORD. Bit fields to define error flags 1 through 30. Or’ing ERRORFLAGx with GROUPx makes a complete ERRORTYPE value. 0x00000004 through 0x80000000

ALLGROUP1 through
ALLGROUP4

DWORD. 0xFFFFFFFC combined with the GROUP1 through GROUP4 flags. This allows all flags in the group to be set, cleared, or checked at once.

Error codes are defined by using the two LSBs as the GROUPx flags and the 30 upper bits as the 30 error flags. A complete error code is then created by combining the group with the error flag. The general form is:

Error Code = Error Flag | Group.

The error object can extract the group from the error code and use it to select the appropriate error DWORD. An application using this object should define constants and then always using the constants. For example:

#define UPPERLIMITERROR (ERRORFLAG2 | GROUP2)

1.3.              Restrictions

This object is designed to allow multiple errors to be set, checked, or cleared at once. That dictates an error flag arrangement such as 1, 2, 4, 8, 16; rather than a simple series of error codes such a 1, 2, 3, 4, 5, etc. Had a simple series been used, then only one error could be set or checked at a time, and a larger data structure would have been needed to hold multiple errors.

The error flags are intended to be combined with the group flags to make a complete 32 bit error code. This means that completed error codes can only be OR’ed with error codes in the same group.

ERRORFLAG1 through 30 must each have one and only one bit set. This makes each error flag unique and combinable with other error flags.

No more than 120 error codes can be handled with this object.

One possible objection is that it still relies on the client knowing something of the internal implementation - the 30 error flags and the 2 group flags. However, that is not the case. This format was dictated by design and not the internal implementation. Instead, the object was coded to accept this format. A more isolated method would have been to have enums for errors and store them in a linked list. However, the application for which this was designed often had to check to see if several specific errors had occurred, while ignoring other errors. Using an enum would have generated a series of "if (error A occurred)...if (error B occurred)...if (error C occurred)..." ad infinitum. In contrast, sticking with the "C" style of or-ing bit flags allowed a group of errors to be created, so the code could check "if (any algorithm error occurred)..."

2.                Interface Definition for CErrorObject Class

2.1.              Constructor

void                    CErrorObject(CWnd* hDestWin = NULL, WPARAM wMsg  = 0)

Constructor for error object. Can pass in a window handle to receive a message each time an error is set. If an hDestWin is specified, then wMsg will be sent using SendMessage( ) each time the function SetError( ) is called. This can be used to notify the user interface that an error has occurred.

hDestWin       Window to receive the message.

wMsg            Message to send. Should be based on WM_USER + n or WM_APP + n. The windows message loop should include an ON_MESSAGE( wMsg, xFn ) statement to process the message. The prototype for xFn is:
void xFn(WPARAM wParm = 0, LPARAM lParm = 0).
The wParm and lParm are not used, but must be declared because SendMessage( ) will push them onto the stack..

RETURNS    void.

 

2.2.              General Functions Provided

void                    SetError(ERRORTYPE eType)

EType            Error code to set. Multiple error codes within the same group can be combined to set several errors at once. If eType = ALLGROUPx then all errors in that group are set.
If the constructor specified an hDestWin, then the message wMsg will be sent using SendMessage( ).

RETURNS    void.

 

void                    ClearError(ERRORTYPE eType)

EType            Error code to clear. Multiple error codes within the same group can be combined to clear several errors at once. If eType = ALLGROUPx then all errors in that group are cleared. This does NOT clear any error text strings (see the error text section).

RETURNS    void.

 

BOOL                IsErrorSet(ERRORTYPE eType)

EType           Error code to check. Multiple error codes within the same group can be combined to check several errors at once. For example: if eType is ALLGROUPx then all errors in that group are checked to see if they are set.

RETURNS    TRUE if any error specified by eType is set.

 

BOOL                IsAnyErrorSet()

Checks all groups to see if there are any errors set.

RETURNS    TRUE if any error is set.

 

void                    ClearAllErrors()

Clears all errors in all groups. This does NOT clear any error text strings (see the error text section).

RETURNS    void.

 

ERRORTYPE    GetFirstError()

Gets the first error code stored in the object (not necessarily the first error that was set).

RETURNS    The first error code among all the groups. NOERROR if no error has been set.

ERRORTYPE    GetNextError()

Gets the next error in the series that began with a call to GetFirstError(), in the order in which they are stored in the object (not the sequence that they were set).

RETURNS: The next error in the list. NOERROR if there are no more errors.

2.3.              Error Text Functions Provided

Since the client objects using the error object may want to set and get textual descriptions of the errors, these functions allow an array of error text information to be registered, read from, and written to. The TextErrorType array could be initialized and never changed, or the Set functions could be used to modify the text or add additional information, such as filenames that were open when the error occurred.

NOTE: These functions make no assumption about whether the error is actually set. Instead, they return only text descriptions of what the error means. For example, the GetFirstError() and GetNextError() functions could be called to get specific errors, then call the GetErrorText() functions to retrieve the detailed description of that error.

void                    RegisterErrorText(TextErrorType* errors)

Registers an array of supported error text messages. This must be called before any call to any of the GetErrorText(), SetErrorText() functions. The structure of the TextErrorType is as follows:

 

#define     MAXTEXTSIZE   80
struct      TextErrorType {
            ERRORTYPE     eType;
            TCHAR         szText[MAXTEXTSIZE + 1];
            TCHAR         szTextEx[MAXTEXTSIZE + 1];
            UINT          uID;
};

 

// Example Error Text Array
struct      TextErrorType eText[] = {
            … // error type and descriptions
            NOERRORFLAGS, “”, “”, 0
};

The last entry in the array must have NOERRORFLAGS for the eType, which terminates the array.

NOTE: szTextEx can only be initialized to an empty string, as it has no string resource ID. The szTextEx is intended to be set by a function that wishes to report an error, and is used to record information about that specific error event.

NOTE: The uID is not used by the Generic Error Object. It is intended to be used by the parent object to load the szText string from a string resource, using the uID as the string ID.

NOTE: Error flags that are not included in this array will not cause an error, but in that case the GetErrorText functions will return NULL for the error text.

RETURNS: void.

const TCHAR*  GetErrorText(ERRORTYPE eType)

Gets a text string describing the specified error code, using the text set by RegisterErrorText(). This string has to be internationalized. The object maintains the buffer for the string, but by declaring it as const the compiler will disallow modifications to the string.

eType            Error code. Can only define a single error code (i.e. do not combine with other error codes).

RETURNS    Pointer to the text string. Returns NULL if the object does not have a string for this error or if eType =  NOERROR or if eType defines more than one error.

NOTE: When designing the implementation of this function it may be necessary to add additional functions and/or data structures to provide the list of error text. For example, an array of errorcodes and text could be created and passed to the constructor or registered via a function call. If so, it is recommended that this be a protected function and that the text be created and passed in a subclass. This subclass would be a specific error object, rather than a generic error object. The generic class would provide this function to perform the search, while the subclass would provide the text itself.

const TCHAR*  GetErrorTextEx(ERRORTYPE eType)

Gets a text string describing more details about the specified error code, using the text set by SetErrorTextEx(). This string has to be internationalized. The intent is for the module producing the error to add any additional data about this specific error, such as a filename.

eType            Error code. Can only define a single error code (i.e. do not combine with other error codes).

RETURNS    Pointer to the text string. Returns NULL if the object does not have a string for this error or if eType =  NOERROR or if eType defines more than one error.

void                    SetErrorText(ERRORTYPE eType, const TCHAR* szText)

Sets a text string describing the specified error code, using the eType set by RegisterErrorText(). Copies no more than MAXTEXTSIZE characters. This sets the text that will be returned by GetErrorText() for this error. This string is initialized by the RegisterErrorText() function, and can be modified by this funtion.

eType            Error code. Can only define a single error code (i.e. do not combine with other error codes).

RETURNS    void.

NOTE: If more than one flag is set in eType, only the first one will have the error text set.

void                    SetErrorTextEx(ERRORTYPE eType, const TCHAR* szText)

Sets a text string describing additional data about the specified error code, using the eType set by RegisterErrorText(). Copies no more than MAXTEXTSIZE characters. This sets the text that will be returned by GetErrorTextEx() for this error. This string is initialized by the RegisterErrorText() function.

eType            Error code. Can only define a single error code (i.e. do not combine with other error codes).

RETURNS    void.

NOTE: If more than one flag is set in eType, only the first one will have the error text set.

 

2.4.              Some Examples of Usage

Example PDL for setting an error is:

#define ErrorCode1       (ERRORFLAG1 | GROUP1)
CErrorObject err

… code …

IF (error condition exists) THEN
            err.SetError(ErrorCode1)
END IF

The PDL for displaying text for each error that occurred is:

CErrorObject err
ERRORTYPE  errCode

… code …

ErrCode = err.GetFirstError()
WHILE (ErrCode != NOERROR) THEN
                Write err.GetErrorText(ErrCode)
                ErrCode = err.GetNextError()
END WHILE

 

The PDL for checking several errors at once within the same group is:

CErrorObject err
BOOL  bIsErr

… code …

bIsErr = err.IsErrorSet(ERRCODE1 | ERRCODE2 | ERRCODE3)
// bIsErr will be TRUE if one or more of the specified errors are set

2.5.              Notes For Using Multiple Error Objects

There is no reason why there could not be multiple error objects. It may be useful to create an aggregate object that holds multiple error objects, each object responsible for certain classes of errors.

2.6.              Notes For HANDLING ERROR NOTIFICATIONS

If an hDestWin is passed to the constructor, then each time SetError() is called, it will send a wMsg message to the application. The intent is to allow the application to queue errors for later display, rather than display them as they occur. The application's main message loop accepted the message and, if it had not displayed the errors yet, posted a message to itself. In the application, this allowed a single user action to have a single error message, rather than a series of error messages.

The pseudo-code is:

User did something that caused SetError() to be called
SendMessage(wMsg)
Receive wMsg from SetError()
IF (errors have not been displayed yet) THEN
   PostMessage(WM_DISPLAYALLERRORS)
ENDIF

Receive Message WM_DISPLAYALLERRORS
Use GetFirstError() and GetNextError() to make a single error message string.
Display the error message.

SendMessage() causes the SetError() message to be immediately handled. PostMessage() causes the error display to be delayed until the application is back to checking messages in its message queue.

The sequence diagram is (in this case, wMsg was defined as the constant WM_CHECKERROROBJECT):