Error handling
In addition to the run-time type being passed by default in the constructor, an error handler is also passed.
At the root of this mechanism the error handler is simply a _cdecl function. The function dumps a string to an output device, and optionally aborts depending on the severity of the error. Default error handlers are provided, and often used.
Although an application may have more than one error handler, it will generally only have one abort handler. The abort handler too is a _cdecl function. It may be set to a user defined function pointer through VObject. The abort handler is maintained as a private static member variable. If set, it only need be set once.
To report an error the VObject base class has a function, ExitObject(), dedicated to this task. This function simply takes a string, and an abort flag. It can be called from anywhere within a VObject subclass. The function manages the distinction between the class error handler, and the global abort handler internally.
It is clear that this approach differs significantly from that of the software exception. Our approach avoids generating exceptions primarily because it can be used to do everything that a software exception might, but also because once exceptions are being generated they have to be handled. Handling exceptions is not a difficult task, but it can complicate the readability of code, and that leads to a larger programming overhead. One of the nicest things about a _cdecl error handling scheme is that it naturally takes strings that have been declared in the .rdata section of the program. Since the strings are already in memory, one can never encounter the situation where a handled error then causes a further error when reserving memory for the error string.
In most cases the error handler passed to a created object simply comes from the creating object. This can be considered the default method or "fixed style". In cases where a separate error handler is required, that new error handler can be passed to the created object, in just one place. Since the fixed style normally propogates a parent handler to the child, this new handler is propagated into all subsequent children. This is the idea of error handler propagation.
Typically our library objects never implement anything other than the fixed style. This ensures that application programmers have a choice about the error handler used and are never left in any doubt that they have implemented a handler for all of the possible errors that a module could create. This particularly is quite different from the concept of software exception.
Our error handler, works naturally with multithreaded environments. Such environments require that each thread must have it's own error handler, and propagation with a fixed coding style is beneficial.
In an environment where some objects generate application errors and others errors to the user, such as perhaps in a parser, our simple approach to error handling is also very useful. One would naturally want most errors to be logged and end in an application abort. In the case of a parser most of the possible errors are of this type. The parser also has a smaller requirement to handle errors which arise out of an incorrectly formatted input. Such errors are more appropriately diverted to a dynamic error pane or logfile, and it would be unnatural for the application to abort on such an error. Giving the parser it's own error handler allows a different handler to propagate through the parser, with a common coding style, for both the application and the parser.
The VCore library does provide a mechanism for exception generation and handling. We typically apply this at the end of the development phase for an application. By this means the standard abort handler, will generate a software exception. There can then be a single exception handler at the top of the application.
A distinct class/object is provided for carrying the exception data. It is one of the very few which is not VObject derived.
The default error handlers provided are normally passed into the main application object in main(), or WinMain(). The default error handler prints the error string to stdout. Release versions of the library, abort with a call to exit(). Debug versions, execute int 3 as an easy entry into stack tracing. Normally GUI applications would use a message box to display an error, but this is not implemented by the libraries, and consequently the default GUI implementation executes as a console application, with a separate main window. Multithreaded applications must be sure to use TerminateProcess() in their abort handlers, for thread safety. |