Class: cWindowsErrorHandler
Properties | Events | Methods | Index of Classes
Use the cWindowsErrorHandler to handle errors in Windows applications
Hierarchy
cObject > cBaseErrorHandler > cWindowsErrorHandler
Show full hierarchy and direct subclasses
- cObject
- cBaseErrorHandler
- cWindowsErrorHandler
- ErrorSystem
Library: Windows Application Class Library
Package: cWindowsErrorHandler.pkg
Description
Use the cWindowsErrorHandler to handle errors in Windows applications.
This class supports DataFlex's interactive interface for all errors generated in a program.
Understanding Error Handling
A global handle named Error_Object_Id identifies the object that should receive errors. Anytime an error occurs, the Error_Report message is sent to the object identified by this global handle. It is expected this will handle the error as required.
When the standard windows error package, DfError.Pkg, is used, a global error object is created and its object ID is moved to Error_Object_Id. Anytime an error occurs, the message Error_Report is sent to the error object. Some objects temporary replace the Error_Object_Id to do custom error handling, for example business processes, so it is not guaranteed that Error_Object_Id always points to the same object.
Never call Error_Report directly to generate an error. Errors should be generated using the Error command. The message Error_Report may be sent if the sending object is itself an error object. This occurs when a custom error handler within an object has been created to handle a process.
Usage
An instance of the cWindowsErrorHandler is automatically created by the DfError.pkg, so including DfError.pkg is all that is necessary to setup the error handling in Windows. DfError.pkg creates an instance named oErrorHandler that registers itself as Error_Object_Id and places a handle to itself in ghoErrorHandler. Use ghoErrorHandler to access the global error object directly as the Error_Object_Id might (temporary) point to other objects.
Note
In most applications, you will not need to make any changes in the error handling as supplied and described here. This handler works well for most data entry programs. The DataFlex report- and batch-processing objects provide their own custom error-redirection logic to handle batch errors, and can be easily customized. The information provided below is provided for advanced usage.
User Errors and Unhandled ErrorsGenerating ErrorsRecommendationsProtecting against Error RecursionTrapping and Ignoring ErrorsCreating Your Own Error HandlerDirecting and Redirecting ErrorsProtecting against Error RecursionAugmenting the Standard Error_ReportCustomizing Unhandled Error Reporting
User Errors and Unhandled Errors
DataFlex distinguishes between two types of errors - user errors and unhandled errors.
User Errors: User Errors (handled errors) are the errors that you expect your users will make. When a handled error occurs, you want to present them with useful information that will help solve their problem. You also want your user to know that nothing is wrong with your application and that they've simply done something wrong. In such a case, they certainly don't need to know about the error's instruction address and they probably are not even interested in an error number.Unhandled Errors: These are errors that indicate that something has gone wrong with your application (i.e., a bug). These are usually programming errors and when they occur, you want the user to contact you. In addition, you'd like to be provided with as much useful debugging information as possible. You want to know the error number, the instruction address and a whole lot more. When a handled error occurs, a message box is presented with text with no technical information - no instruction address, no error number. If error numbers are meaningful to your application, you can choose to display error numbers by setting the pbShowErrorNumber property. The default caption text for a user error is "Error", which can be changed by setting the psUserErrorCaption property.
When an unhandled error occurs, a completely different dialog is presented. This dialog provides a complete message stack dump. If the error occurs at a deployment site, the error stack can be easily copied to the clipboard, added to an email message and sent to you. If the error occurs while you are testing (debugging) your application, a "Debug" button will appear in the error dialog. Click this button and the application will break and enter debug mode at the error source. The default caption text for an unhandled error is the name of the executable followed by "Unhandled Program Error", which can be changed by setting the psUnhandledErrorCaption property.
The ErrorSystem object determines if an error is a user error by checking the error against a list of user error numbers. The class has a pre-defined list, which may be customized using the AddUserError, RemoveUserError and RemoveAllUserErrors methods.
Generating Errors
A special global procedure, UserError, is defined along with this class definition and it should be used to generate user errors. While the Error command can be used to generate both unhandled errors or user errors, it will mostly be used to generate unhandled errors. Note that many, if not most, of the unhandled errors will be generated by the runtime or the framework.
Sample
The following is an example of generating a user error.
Function Validate_Save Returns Integer
Integer iRetVal
Forward Get Validate_Save to iRetVal
If iRetVal Function_Return iRetVal
If (Invt.On_Hand < 0) Begin
Send UserError "Insufficient Inventory Stock" ""
Function_Return 1
End
End_Function
Sample
The following is an example of generating an unhandled error.
Procedure ProcessList
String[] ArrayOfNames
Integer iNames
Get GetUserNames to ArrayOfNames
Move (SizeOfArray(ArrayOfNames)) to iNames
If (iNames=0) Begin
Error DFERR_PROGRAM "GetUserNames should never have 0 names"
Procedure_Return
End
End_Procedure
Note that unhandled errors are not supposed to happen. The above example show how the Error command is used to identify and track down problems in an application's logic.
Recommendations
Determine if your application makes meaningful use of error numbers when reporting user errors to users. If they do not, use the UserError procedure to generate these errors. If they do, set pbShowErrorNumber to True and use the Error command to generate user errors.
When creating Error support in application, determine if an error condition is a user error or an unhandled error. If the error is a user error, use the UserError procedure to report the error. If the error is unhandled, use the Error command to generate the error.
Do not use the message box interface to generate errors. It is not "lock-safe".
Make your user error messages descriptive.
Trapping and Ignoring Errors
An error can be fatal, ignored, or trapped. If an error is fatal, an error message is displayed and the program is terminated. If an error is trapped, it is displayed with a pop up error dialog. If an error is ignored, it is ignored. When initialized, the error system will trap all errors except Error 41 (Find past end of file) and Error 42 (Find prior to beginning of file). You may choose to trap or ignore other errors. You may change this through the use of the TrapError, IgnoreError, TrapAllErrors, and IgnoreAllErrors messages.
There is a single error that will never be passed to the error object, Error 10 (Out of memory). Since the message-passing mechanism uses dynamically allocated memory, this would most certainly cause a system failure.
Creating Your Own Error Handler
You can turn any object into an error handling object. This is done as follows:
-
The global integer Error_Object_Id must be assigned to this object. Usually this assignment is temporary.
-
The Integer Property Error_Processing_State must be created within the object and supported within your custom Error_Report procedure.
-
The Error_Report procedure must be created within the object to handle the error.
Directing and Redirecting Errors
Custom error handlers are created when you need to change the way errors are handled during a process. Usually, you will turn the object that is running the process into its own error handler. When the process begins, you save the current value of Error_Object_Id (usually to a property) and set the value of Error_Object_Id to the current object (Self). At this point, all errors will be directed to the current object. When the process is complete, Error_Object_Id should be restored to its original value. This is typically done as follows:
Property Handle phOrigErrorObj 0 // stores orrigional error object
Procedure Run_Process
Set phOrigErrorObj to Error_Object_Id // remember old error object
Move Self to Error_Object_Id // make self the error object
// At this point, all errors will be directed to Error_Report within this object.
Error DFERR_PROGRAM "Something went wrong!" //run the process
Get phOrigErrorObj to Error_Object_Id // restore original error object
// At this point, all errors will again be directed to the previous (presumably standard) error handler
End_Procedure
Protecting against Error Recursion
While custom error handler Error_Report can be made to do anything you wish, you must provide logic to prevent recursive error reports (i.e., an error creating an error). You do this by creating a property named Error_Processing_State, setting this property to True while within the Error_Report procedure, and resetting it to False when the procedure is complete. Because other objects may check the value of this property to see if an error is in process, this is a public property and must be provided in all error handlers. Here is a typical example of how this property is used:
// This property MUST be created within the object or the object's class.
Property Boolean pbErrorProcessingState False
Procedure Error_Report Integer iErrNum Integer iErrLine String sErrText
// if we are already within an error, do nothing
If (pbErrorProcessingState(Self)) ;
Procedure_Return
// mark that we are now processing an error
Set pbErrorProcessingState to True
// perform custom error handling
Showln iErrNum sErrText
// mark that we are no longer processing an error
Set pbErrorProcessingState to False
End_Procedure
Augmenting the Standard Error_Report Behavior
Often, a custom error handler will want to augment the behavior of the standard error handler. For example, an error handler may wish to log an error and, if the error is of the right type, display a standard pop-up error dialog. Your custom Error_Report procedure does this by sending the message Error_Report to the standard error handler (usually the previous error handler). This is done as follows:
Procedure Error_Report Integer iErrNum Integer iErrLine String sErrText
Handle hOrigErrorObj
// if we are already within an error, do nothing
If (pbErrorProcessingState(Self)) ;
Procedure_Return
// mark that we are now processing an error
Set pbErrorProcessingState to True
// perform custom error handling
Showln iErrNum sErrText
Get phOrigErrorObj to hOrigErrorObj
Send Error_Report of hOrigErrorObj iErrNum iErrLine sErrText
// mark that we are no longer processing an error
Set pbErrorProcessingState to False
End_Procedure
More information is provided in the documentation of Error_Report.
Customizing Unhandled Error Reporting
The cWindowsErrorHandler class can be augmented at multiple levels. The Error_Report procedure can be augmented / overridden at the lowest level, when doing so one needs to make sure to protect against recursiveness (as shown in the previous sample with pbErrorProcessingState). One can also augment HandleError, which is called after the processing state is checked. And then one can also augment or override UnhandledErrorDisplay which is called only for unhandled errors after all checks are done and the full error text is assembled.
In the following sample we augment HandleError to display a custom error. The CallStackDump command retrieves the same message stack information you would see in your error dialog and places it in a string. You can use this do other things with the error information such as logging it to a file or sending an error report. The CallStackDump command should only be used within the error handler's Error_Report event.
Use cWindowsErrorHandler.pkg
Object oCustomErrorHandler is a cWindowsErrorHandler
Procedure HandleError Integer iErrNum Integer iErrLine String sErrText
Boolean bUnhandled
String sStack
// Check if error is unhandled
Get IsUnhandledError iErrNum to bUnhandled
If bUnhandled Begin
CallStackDump sStack
Send LogUnhandledError iErrNum iErrLine sErrText sStack
End
// Forward to default error handling
Forward Send HandleError iErrNum iErrLine sErrText
End_Procedure
Procedure LogUnhandledError Integer iError Integer iLine String sErrMsg String sStack
Showln "Unhandled Error: " iError " at " iLine
Showln sErrMsg
Showln sStack
Showln
End_Procedure
End_Object