The latest version of this document and full HuntERR download package is located here.
Your feedback is welcome: feedback@urfinjus.net


HuntERR

Error-Handling Solution For Visual Basic

Version 3.12

Programmer's Guide


  Copyright URFIN JUS (www.urfinjus.net), 2001-2003.
 
All rights reserved.

 

Contents

1. Introduction
     1.1. What is HuntERR?
     1.2. How It Started
     1.3. What Error Handler Should Do? New
     1.4. Unwinding the Call Stack New
     1.5. Error-Handling Sequence
     1.6. Exceptions New
2. HuntERR: How It Works
     2.1. Procedure Template
     2.2. Error Map
     2.3. ErrorIn Super-Function
     2.4. Example with ErrorIn
     2.5. Raising Errors And Exceptions: First Look at Check Sub
3. Error Resolution
     3.1. Persisting Error Report
     3.2. Handling Without Re-raising
     3.3. Error Resolution in EXE Applications
     3.4. Error Resolution in COM Servers
4. Advanced Features
     4.1. API Errors
     4.2. ADO Errors
     4.3. ADO Transactions
     4.4. COM+ Transactions
     4.5. WEB Request Information
     4.6. System Exceptions
     4.7. HuntERR Extensions
     4.8. Preserving Error Information New
     4.9. Long Strings, XML Formatting New
     4.10. Closing Files New
     4.11. Releasing Objects New
     4.12. Stopping On Error in IDE New
5. Check Sub: Detailed Look New
     5.1. Check Declaration and Overview New
     5.2. Parameterized Description New
     5.3. Accumulating Messages New
     5.4. Sending Additional Tip to HandleError New
     5.5. MessageSource Object New
6. Using HuntERR
     6.1. Well Behaved Components
     6.2. Shift to the Middle Tier
     6.3. Architecture Design Tips
     6.4. Example: Creating a Wrapper
     6.5. Setting Up Your Projects
     6.6. A Few Coding Tips
     6.7. HuntERR Add-In for Visual Basic
7. Final Thoughts
     7.1. Bugs, Errors and Application Malfunctioning New
     7.2. What will happen
Appendixes
     A1. HuntERR Quick Start Guide New
     A2. Releasing ByRef Object Parameters - When It Doesn't Work New
     A3. Version 3.0 New Features
     A4. Version 3.1 New Features

 

New - Section added in version 3.1.



 

Introduction

What is HuntERR?

If you are already familiar with HuntERR and have seen previous versions then we are glad to meet you again! If you are new to HuntERR - then welcome, fellow developer! Before you engage into reading this lengthy document we would like to give you a clue on what this is all about.

Just imagine a VB application that tries to execute a SQL statement in database, fails, and logs the following error report to text file or database table:
Invalid column name 'X'. 
  Time='04/18/02 09:13:56' App='MyApp' ADO-version='2.6' Computer='Solo' 
  Method: ExecUpdateSQL 
  Number: -2147217900 = &H80040E14 = vbObjectError + 3604 = ERRMAP_APP_FIRST - 1594 
  Source: Microsoft OLE DB Provider for SQL Server 
  Description: Invalid column name 'X'.
  ADO Info: 
    ADO Version:   2.6
    DbObject:      Connection
    Conn. String: 'Provider=SQLOLEDB.1;Server=;Password=;User ID=sa;Initial Catalog=Northwind;
    Conn. State:   adStateOpen
    Error:         Invalid column name 'X'.
    Error:         Invalid column name 'Y'.
    Error:         Invalid column name 'Z'.
  Call Stack: ExecUpdateSQL(SQL='Update Customers Set X = Y WHERE Z = 1') ----------------
    Transaction: Attempt to call RollbackTrans succeeded
    Connection: Attempt to call Close succeeded

You probably think - "not bad, but so what - just a big bunch of code in error handler to build all this stuff. Are going to teach me how to format a text like this in error handler?". Certainly not. The thing is, the error handler in method executing SQL is quite simple:
Public Sub ExecUpdateSQL(ByVal SQL As String)
    Dim Conn As ADODB.Connection
    On Error GoTo errHandler
    Set Conn = New ADODB.Connection
    With Conn
        .Open CONNECT_STRING
        .BeginTrans
        .Execute SQL
        .CommitTrans
        .Close
    End With
    Exit Sub
errHandler:
    ErrorIn "ExecUpdateSQL(SQL)", SQL, EA_DFTRBKCLS, Conn
End Sub

That's it - just one simple line in error handler - and you have the error report above! Now look again at error report. This one-line handler managed to report the location of error, its number and description, retrieve and report all ADO errors from Connection.Errors collection, list some system and environment information, report the connection string used, report the name of method and its parameter value (executed SQL statement), then it also rolled back the transaction and closed the connection. Not bad for one line, isn't it?

And it can be even more - error handlers in methods that called this failed method would add their names with list of their parameters values to error report, so it would finally include full stack trace, up to original method that started the operation, like cmdDoStuff_Click !

As you see now, all this activity was performed by function ErrorIn implemented by HuntERR library. The wonderful thing is that HuntERR is implemented as VB standard module, which you include into your VB project. No compiled DLLs, no monstrous system services - just plain VB code packed into .Bas module. And it is free. You've just seen that it can do something real - it is more that just a fancy code snippet.

Interested? Let's go in for details. We hope you see now that this document is worth spending some of your time. We promise, you will not be disappointed.
Contents

1.1. How It Started

Programmers who are with Visual Basic for a long time have been witnessing its fascinating transformation from a simple scripting tool into a powerful enterprise development environment. And while we observed many dramatic changes in VB, one particular area we think was falling more and more behind. This area is error handling.

VB itself didn't deliver much new support at the language level - Err object, and On Error... statement had been there for years, providing just basic error-catching functionality. Also not so much was added in programming practices and techniques. Authors in published articles and code snippets treated error handling as sort of sideline issue - at best error handler was there with standard "MsgBox Err.Description ", with nothing more to say about it. We think that general attitude was that error is something that should never happen in released system, and if it happens during development - here is the debugger, go step-by-step to find and fix it. And it worked quite well this way for a while.

However at some point things began to change dramatically. VB started to be used for enterprise development, to create components working on remote servers, where no step-by-step debugging was available. During development it has become a difficult and sometimes impossible task to reproduce on developer's computer the error that happened on server.

The other big change was that applications have become multi-tiered, distributed and heterogeneous, built from numerous diverse components working together. As a result, even if a component was bug-free (hardly possible as we know but let's imagine for a moment such a miracle) - still it could crash in target "production" environment because of external factors like invalid data from other components, or "inter-components" communication failure.

It was becoming more and more apparent (at least in our own experience) that old approaches and attitudes towards errors wouldn't work in enterprise development. Error was no longer a design accident, a bug to be quickly exterminated and forgotten. It has become more serious and more "normal" problem, with which application has to deal on a regular basis, anticipating it as an event that can happen at any time in any place. Application should always be ready to face the error, and not as a killing disaster, but as normal event, for which it is prepared by programmer. And even if it is doomed to crash, it should create and save the last message to the developer with as much information as possible about what happened and what killed it. It should crash decently.

There were, and there exist now some commercial third-party solutions that try to deal with the problem of error handling, each in its own way. We found these solutions absolutely unsatisfactory, and unable to solve even the basic problems faced by VB developer. What confirms this view is the fact that these systems didn't gain popularity among VB programmers. We think that the biggest problems with them is that they are too invasive - they add too much to your mainstream code, drastically altering it and damaging its clarity and readability.

So programmers continued to use some homemade error-handling snippets built specifically for particular applications. So did we, but at some point we decided to stop, think, and design something really robust, non-invasive, and reusable.

That's how HuntERR(tm) was started.
Contents

1.3. What Error Handler Should Do?

Let's first formulate what we normally should do in error handler. The first thing is of course log the error, including its number, source and description. Additionally error handler may report module/method name, values of local and global variables, and even method's current parameter values. Sometimes applications show this information immediately to user, but we think it's reasonable thing to do only if user is a developer himself testing the application.

Most existing error-handling systems consider at this point their error-handling job done. They cannot do anything more, except maybe some fancy stuff like automatic email to developer. This is definitely not enough. The story only begins here.

First, sometimes Err.Description field doesn't contain all error information. Code in error handler has to make additional efforts to retrieve this information from some external sources. It can be description of an API error, or additional messages stored in Connection.Errors collection. It also may be information from other COM servers that application uses. The other thing to do in error handler for database applications is to abort transaction if there is one in progress. And finally, we should do some clean-up - release all the temporarily used resources, close connections if they were opened in this method, and probably release local object instances.

All this doesn't seem too much to do, but additional restriction is that this must be done safely, without provoking any more errors. In reality all this sometimes adds up to considerable amount of code in error handler. The problem is not only that error-handling code adds significantly to number of code lines in your application. The biggest problem is that this code is error-prone itself. There is always a possibility that when time comes to handle error this code will either not do what it is supposed to do, or even crash with another error, which is a complete disaster. This error-handling code should be put into almost every method throughout the application, and it is really difficult to test - it is not a part of mainstream business logic, so it can hardly be handled by QA department. Considering these facts proper and full error handling becomes a serious issue. The only solution would be to have a few, preferably one or two pre-coded and tested methods that can take care of all error-handling business in every error handler, with a very low possibility of failure without any testing. ErrorIn is such a method.

ErrorIn method doesn't do any magic, anything that cannot be coded directly in a few lines in error handler. It's just powerful pre-coded and pre-tested method that allows you to shrink error handler into one line. This greatly improves your chance that error handler wouldn't fail. Another important result is that error handlers are compact, and mainstream logic is not overshadowed by huge error-handling blocks.
Contents

1.4. Unwinding the Call Stack

Now having discussed what should be done immediately in error handler of the failed method, let's discuss the other important question: What to do next? After we have done all these things, what should the application do? Here is the standard situation.

Suppose you method A in your application is invoked by some external event. Method A calls method B, which in turn calls method C, and so on until method Y calls Z. Method Z fails, error is caught and processed in error handler in Z. How this error-handling code will finish? The problem is that there is a line of methods from A to Y waiting for method Z to finish so they can continue with their own jobs. If execution simply returns from Z and all these methods continue execution the results are unpredictable, and serious damage to data and user sanity is quite possible. If Z fails, all these methods MUST abandon their workflow, and immediately stop doing what they were doing. Now, if it is not safe to continue in Y method, then we must find a safe point, where execution CAN continue, and somehow jump there from where we are - in error handler of method Z .

What can be such a safe point? Most of the time it is the exit from A method, the one that was triggered by external event. For example for desktop applications it can be Click event handler of a button. If we exit from this method, then application continues it's message processing cycle. When doing this jump to the safe point in A a good thing to do is to notify B...Y methods about change of plans, and to let them do necessary local clean-up if they can.

How can we execute such a jump to this safe point? There is a mechanism that implements this jump up the call stack. It is called unwinding the call stack. It is built into operating system. VB performs stack unwinding when error is thrown in code(We are not sure how much VB run-time relies on this stack unwinding functionality of OS). When processing error VB doesn't try to break you application in the most brutal way, it just searches for the safe point to continue! VB unwinds the call stack by jumping up from method to method, stopping at error handlers hoping that this is the safe point, and allows your code to continue. Most of the time the error handler in a method is not such a safe point, so the most natural thing to do is to resume unwinding process in error handler by re-raising error. However making a stop in every error handler in a call stack is a good chance for each method to do the clean-up of local and temporary variables and resources. Additionally each method can add some additional information to error description/report, for example, its name and parameters values to make it easier for the developer to investigate the case.

This is the technique used by HuntERR. After error occurs in some method, error-handling code prepares initial error report, and re-raises error. ErrorIn method that we've already met in error handler does this automatically. Re-raising error in error handler starts the process of controlled UNWINDING of the call stack. Error handler in the immediate caller of the failed method catches the re-raised error and detects that the unwinding (propagation) process is on the way, so it should simply do the necessary local clean-up, and add method's signature (method name and parameters values) to error report. Then it again it re-raises error resuming the unwind process. And so on, the unwinding continues with stopping in every error handler in the call stack. ErrorIn method alone can do all this job in error handlers, without any extra coding. During unwinding the error information is sent in properties of Err object, with error report in Description property. The final goal is to arrive at safe point, which is usually the top or the first method in a call stack. Here the unwinding stops, we cannot re-raise error anymore. Here we do what we call Error Resolution, and we discuss this topic in a separate chapter of this document.

1.5. Error-Handling Sequence

Having discussed some general error-handling topics lets now give a general outline of what happens when error occurs in code guarded by HuntERR. HuntERR error-handling model implements the following steps in handling of a single error:

  1. Initial Processing - initializing error report, collecting basic information about error itself and execution environment.

  2. Unwinding the call stack (Error Propagation) - the process of repetitive re-raising error jumping from one error handler to another in the sequence of methods in a call stack. Stack is Unwinded in the sense that execution jumps along the call stack in the reverse order of nested calls that formed it, and pops methods out of the stack. Error report is sent in Description property of Err object. Error report is a multi-line text with the first line being original error description. In the process of unwinding the stack HuntERR continuously appends lines with environment information to error report. We can say that HuntERR "expands" original error description with additional information.

  3. Error Resolution - final error processing actions. Application can retry the operation, or use the alternative way of doing the failed job if it has one, or simply notify the caller component or user about the failure and log the error report.

Note: We used the term Error Propagation in earlier versions of HuntERR for the second step. We decided to change it to Unwinding the call stack, because it is more standardized term, and probably better describes what's happenning. However we'll continue to use Propagation eventually. And HuntERR's InPropagation function is still there with the same meaning/functionality.
Contents

1.6. Exceptions

Stack unwinding process we've just discussed is a powerful mechanism provided by VB runtime and operating system. It is sometimes assumed that it comes into play exclusively when error happens - spontaneously, and most often with no good consequences.

But aside from spontaneous errors application may invoke this mechanism intentionally to perform some smart purposeful actions. Let's just look at unwinding process in the following way: Stack unwinding implemented by error-raising mechanism is a way of interrupting the normal workflow of the program, and jumping directly up along the call stack to the upper method in stack, with a chance to stop in every method, and perform the necessary clean-up. Certainly such a neat jump may be employed somewhere, in addition to the case of disastrous application failure. One obvious place where this jump can be used is user input validation. Validation is easy when it can be done directly in Click event handler in the form. But for complex applications sometimes validation methods are buried deep inside objects/method hierarchy, they are sometimes part of a complex workflow. In this case interrupting the workflow, cleaning-up the resources, and returning a message to user may become a really uneasy task. That's where the unwinding process comes handy - just raise a custom error, and catch it in the top method in a call stack. This catching method should recognize the error as "validation" fault, and show the appropriate message to user.

Most of the modern languages support such a controlled jump along the call stack through mechanism of Exceptions handling. (And as we see, Visual Basic supports it too!) We will use the term Exception for errors raised by application on purpose, using Err.Raise method, when no application failure occurred, but simply execution needs to "jump up" the call stack. HuntERR supports Exceptions by defining a range of error numbers for them. HuntERR's ErrorIn function processes exceptions in a different way than errors. As there is no application failure when exception is raised, ErrorIn doesn't build error report, but only makes a clean-up (aborts the transaction), and re-raises it passing it to the error handler in the caller method.

One important comment. Please don't think of exceptions as a tricky way to communicate some information to the caller method or parent object. After HuntERR first release some programmers were saying: "I don't need exceptions, I usually raise an event to show a message to user or to send some information to parent object." But this is different. Exception safely aborts the workflow, and unwinds the call stack jumping to the top originator method - raising event doesn't do this. After returning from event handler you are still in the same place with stack unchanged.

Try Exceptions - you'll be surprised how convenient and powerful they are! Without exceptions you'll have to code every method with a provision that execution may be cancelled, so it must stop and exit, sending to its caller some return value indicating that cancellation happened. And the caller must check this return value after every call, and so must do it's own callers and so on. Instead of doing this you make one jump directly to your target, with no provision code in methods in between.

HuntERR provides a convenient method that makes it really easy for you to raise Exceptions: it is Check Sub. We discuss all its features in a separate chapter of this document.
Contents

2. HuntERR: How It Works

Now let's see how all this is implemented. We'll first have a look at general error-handling template, then discuss in details HuntERR's core ErrorIn method, then we'll look at a simple example.

2.1. Procedure Template

General schema is quite simple: all procedures have error handlers and are built similar to the following template:
Private Sub MySub(Param1 As Type1, Param2 As Type2, ....)
    On Error Goto errHandler
    ........ 
    Exit Sub
errHandler:
    ErrorIn "MyClass.MySub(Param1,Param2, ....)", Array(Param1, Param2, ...)....
End Sub

Details may vary: Public vs. Private, Function vs. Sub, parameter list, list of ErrorIn arguments, etc. But general schema is the same. Most of the time just one call to ErrorIn in error handler takes care of all error-handling business.

The rule that every method should have error handler is not absolute. Trivial methods (like simple Property Get/Let/Set procedures) may be left without handlers.
Contents

2.2. Error Map

Visual Basic defines vbObjectError constant that sets the range of error numbers available for custom applications. However, if you use database access with ADO, you'd better not use error numbers that immediately follow vbObjectError . These numbers are used by data access components provided by Microsoft.

To organize the error ranges and to make them easy to use by applications HuntERR declares enumeration ENUM_ERRMAP:
Public Enum ENUM_ERRMAP
        ERR_ACCUMULATE = 0                          'Check Sub accumulates messages
    ERRMAP_FIRST = ERRMAP_BASE
    ERRMAP_RESERVED_FIRST = ERRMAP_FIRST            'Errors reserved for HuntERR and UJ apps.
       ERR_SYSEXCEPTION                             'System exception
    ERRMAP_RESERVED_LAST = ERRMAP_RESERVED_FIRST + 100
    ERRMAP_EXC_FIRST = ERRMAP_RESERVED_LAST + 1     'Exceptions - re-raised by ErrorIn
        EXC_GENERAL = ERRMAP_EXC_FIRST              'Use it if you don't need specific number
        EXC_VALIDATION                              'User input validation exception
        EXC_MULTIPLE                                'Multiple messages in error description
        EXC_CANCELLED                               'Cancelled operation
    ERRMAP_EXC_LAST = ERRMAP_EXC_FIRST + 1000
    ERRMAP_APP_FIRST
        ERR_GENERAL = ERRMAP_APP_FIRST              'Use it if you don't need specific number
        'Application errors here
End Enum

Your may decide to move this enumeration to public class - in this case it will be visible through COM services to COM clients (like VBScript) and they can refer to constants by their names. To do it copy the declaration to public class, and define H_NOENUMS=1 in conditional compilation constants. This will exclude this Enum in HuntERRxx.Bas module from compilation.

Constant ERRMAP_FIRST sets the start of a map. ERRMAP_BASE value is set by default to vbObjectError + 4096 to move above the range used by data access components. You can redefine this constant if necessary using H_EXTBASE conditional compilation parameter. See http://support.microsoft.com/support/kb/articles/Q168/3/54.ASP for more information about ADO errors.

The first range [ERRMAP_RESERVED_FIRST .. ERRMAP_RESERVED_LAST] identifies error numbers reserved by HuntERR. HuntERR defines ERR_SYSEXCEPTION for system exception error (see System Exceptions section).

The next range [ERRMAP_EXC_FIRST .. ERRMAP_EXC_LAST] is reserved for exceptions. You can define exceptions for your application here. Exceptions are re-raised by ErrorIn "as is" (additionally current transaction may be aborted if necessary). EXC_GENERAL may be used by application in case if specific number is not needed. We predefined several exceptions for your convenience. EXC_VALIDATION may be used for user input validation failures. EXC_MULTIPLE is for the case when exception message includes several messages, as a result of accumulation. EXC_CANCELLED is a silent exception, when no additional message should be shown to user. It should be used with empty ErrDescr parameter of Check Sub.

Error numbers above ERRMAP_APP_FIRST are for custom application errors. Application can use ERR_GENERAL when unique error number is not needed instead of popular in literature but risky vbObjectError + 1.
Contents

2.3. ErrorIn Super-Function

We are now ready to discuss the core super-function of HuntERR library - function ErrorIn. Procedure calls ErrorIn in error handler, and it takes care of all error-handling business. We have already used it in one of previous listings Here is a declaration of ErrorIn in HuntERRxx.Bas :
Public Function ErrorIn(ByVal MethodHeader As String, _
                        Optional ByVal arrArgs, _
                        Optional ByVal ErrorAction As Long = EA_DEFAULT, _
                        Optional ByVal DBObject As Object, _
                        Optional ByVal EnvVarNames As String, _
                        Optional ByVal arrEnvVars, _
                        Optional ByVal TransControlObject As Object) As String

MethodHeader is the only required parameter. ErrorIn expects here the name of procedure, from which ErrorIn is called followed by optional list of parameter names in parenthesis. Procedure name may be in qualified form: ModuleName.ProcName. Values of procedure's parameters are provided in arrArgs parameter in a Variant Array. Application can use Array() function to create array on-the-fly. A single value may be provided as is, without embracing it into array - ErrorIn will figure it out correctly. ErrorAction is a set of flags that specify what ErrorIn should do. ENUM_ERROR_ACTION public enumeration declared in HuntERR defines available flag values:
Public Enum ENUM_ERROR_ACTION
    EA_RERAISE = 1         're-raise error
    EA_ADVANCED = 2        'Build Error report
    EA_SET_ABORT = 4       'Call SetAbort on current object's context.
    EA_DISABLE_COMMIT = 8  'Call DisableCommit on current objects' context. Recommended.
    EA_ROLLBACK = &H10     'Call Connection.Rollback
    EA_WEBINFO = &H20      'Add web request information
    EA_CONN_CLOSE = &H40   'Close connection
    EA_DEFAULT = EA_ADVANCED + EA_RERAISE + EA_WEBINFO + EA_DISABLE_COMMIT 'Default
    'The following constants are defined for convenience
    EA_NORERAISE = EA_ADVANCED + EA_WEBINFO + EA_DISABLE_COMMIT
    EA_DFTRBK = EA_ADVANCED + EA_RERAISE + EA_WEBINFO + EA_ROLLBACK
    EA_DFTRBKCLS = EA_ADVANCED + EA_RERAISE + EA_WEBINFO + EA_ROLLBACK + EA_CONN_CLOSE
End Enum

Most of the time you don't need to specify ErrorAction parameter, just skip it and ErrorIn would use default value. You need to specify it explicitly in at least two cases:

  1. In methods that don't re-raise errors. In this case you can use EA_NORERAISE flag set, which is predefined but not used by HuntERR itself, it is just for your convenience.
  2. If you use ADO and control transactions through ADO Connection object then you should use explicit values in method(s) where you start/commit ADO transaction. In these methods you should use EA_DFTRBK constant as ErrorAction parameter. It has EA_ROLLBACK bit set, so ErrorIn would abort the transaction if it had been started. EA_DFTRBK is defined for your convenience.

In some cases you may need to define your own flag combination. Do it the same way as we define these "convenience" constants in the enumeration.

DbObject parameter is an ADO object that was used to execute database operation, if procedure executes one. It can be Connection, Command, or Recordset object, whatever is used by application to perform database operation. Or it can be any custom object with Connection property that returns ADODB.Connection object. What ErrorIn actually uses is ADODB.Connection object, and it knows how to get it if application provides Command or Recordset instances. ErrorIn uses Connection object for two purposes: first, to retrieve ADO errors (if there are any) from Connection.Errors collection and to add them to error report; second, to abort the transaction by calling Connection.RollbackTrans (if instructed to do so with flag EA_ROLLBACK).

EnvVarNames and arrEnvVars allow application to report values of any variables that are not procedure parameters. These may be local, module, or global variables. EnvVarNames specifies comma-delimited list of variable names, and arrEnvVars provides the array of corresponding values in Variant array. As with arrArgs application can use Array() function to create array on-the-fly. A single value may be provided as is, without embracing it into array.

TransControlObject provides a way to abort current transaction when application uses separate object to control COM+ transactions. If this parameter is provided, ErrorIn attempts to call its SetAbort method.

Note that exceptions are not completely "ignored" by ErrorIn. All transaction-related actions are executed as with errors.
Contents

2.4. Example with ErrorIn

Suppose we have a simple function that calculates 1/(X-1). Its code with the simplest possible use of ErrorIn is as follows:
Function Calc1X1(ByVal X As Double) As Double
    On Error GoTo errHandler
    Calc1X1 = 1 / (X - 1)
    Exit Function
errHandler:
    ErrorIn "Calc1X1"
End Function

If this function is called with X=1 then error re-raised by ErrorIn will contain the following error report in Description field:
Division by zero 
  Time='04/23/02 16:25:25' App='MyApp:1.0' ADO-version='2.6' Computer='Solo' 
  Method: Calc1X1 
  Number: 11 = &HB = vbObjectError + 2147221515 = ERRMAP_APP_FIRST + 2147216317 
  Source: MyApp 
  Description: Division by zero
  Call Stack: Calc1X1() --------------------------------------------

If we add X as a second parameter to ErrorIn, the last line of report will show value of X:
  Call Stack: Calc1X1(1) -----------------------------------------

If we provide name "X" in parenthesis after function name:
ErrorIn "Calc1X1(X)", X

then we'll get parameter name and value in the signature of Calc1X :
  Call Stack: Calc1X1(X=1) ---------------------------------------

Finally, if lines in procedure are numbered:
Function Calc1X1(ByVal X As Double) As Double 
10  On Error Goto errHandler
20  Calc1X1 = 1 / (X - 1)
30  Exit Function
errHandler:
    ErrorIn "Calc1X1(X)", X
End Function 

... - then we'll have line number in function's signature in error report:
Division by zero 
  Time='04/23/02 16:31:41' App='MyApp:1.0' ADO-version='2.6' Computer='Solo' 
  Method: Calc1X1 
  Number: 11 = &HB = vbObjectError + 2147221515 = ERRMAP_APP_FIRST + 2147216317 
  Source: MyApp
  Description: Division by zero
  Call Stack: Calc1X1(X=1)  at Line 20 -------------------------------

ErrorIn called in error handler of Calc1X1 performs the initial processing of the error. At the end of processing ErrorIn will re-raise error with Err.Source="HuntERR.ErrorIn" and Err.Description containing error report. Error number will be the same as original error number. Error handler in the caller of Calc1X1 should catch the error and again call ErrorIn. This time ErrorIn will recognize that it is not the first time it is called, and that unwind process is on the way. It will add caller's signature to error report, and again re-raise it. And so on, the process of re-raising error will continue until finally we arrive to some top method where we must stop stack unwinding. We'll discuss what to do in this top method in a separate chapter later.
Contents

2.5. Raising Errors And Exceptions: First Look at Check Sub

Now suppose in previous example we want to check value of X before performing division, and raise exception if X=1 to tell the user about invalid input. (We will see how exception results in error message later in section about Error Resolution).
Function Calc1X1(ByVal X As Double) As Double 
    On Error Goto errHandler
    If X = 1 Then 
        Err.Raise EXC_GENERAL, "Calc1X1", "Value of X may not be 1."
    End If 
    Calc1X1 = 1 / (X - 1)
    Exit Function
errHandler:
    ErrorIn "Calc1X1(X)", X
End Function 

The same function may be rewritten using Check sub provided by HuntERR:
Function Calc1X1(ByVal X As Double) As Double 
    On Error Goto errHandler
    Check X <> 1, EXC_GENERAL, "Value of X may not be 1."
    Calc1X1 = 1 / (X - 1)
    Exit Function
errHandler:
    ErrorIn "Calc1X1(X)", X
End Function 

Essentially Check checks if some condition is true. If it is not, it raises error with specified number and description. Although it doesn't seem like you get a big gain from this Sub, we think there is a good reason to use it. First, the code is shorter and looks clearer, more like "natural English". Second, when Check is consistently used throughout the code, these calls visually stand out and are easily spotted as "validation points", while use of If-Then may be left for mainstream functional code that does the real stuff.

What we have shown is a trivial example of using Check. Since version 3.1 it has a lot more inside than we've just shown. We'll discuss this method in details in a separate chapter.
Contents

3. Error Resolution

The unwinding process of re-raising an error cannot continue indefinitely, and at some "top" point application should stop and decide what to do about the error. This "top" point is usually a method that was called from outside the component, and error cannot be re-raised any further simply because VB's error handling is not supported by the caller. Application should do what we call Error Resolution, and this is a subject of this chapter.

At resolution point application should normally do two things: persist error report, and notify the caller (client, user) about failure. We provide samples of resolution code for two distinctive environments: EXE desktop applications, and WEB components. First let's look at HuntERR support functions for persisting error information.
Contents

3.1. Persisting Error Report

HuntERR provides 3 methods for persisting error report. You can either send error report to event log, append it to text file, or save it in database table.
Public Function ErrSaveToEventLog() As Boolean
Public Function ErrSaveToFile(Optional ByVal ErrFileName As String = "Errors.txt") As Boolean
Public Function ErrSaveToDB(ByVal ConnectString As String, _
                       Optional ByVal AppID As Long = 1, _
                       Optional ByVal ProcName As String = "spErrorLogInsert") As Boolean

ErrSaveToEventLog sends error report to system event log. There are some problems with persisting error reports to event log.

First, size of the message is limited to about 1.5 Kb, and you will quickly find out that it is not enough for real-world applications. Error report will appear truncated in event log. Second, event log is not available in Win 9X systems. Third - logging is ignored when done from inside VB IDE. And one more problem - difficulty in accessing the event log remotely. If ordinary (not computer expert) user complains about error happening in your application on his computer, it can be a tricky task to get your eyes on local event log.

The alternative is to use text file. Use ErrSaveToFile to save (actually append) error report to a text file. If ErrFileName is not a full file path then function will attach application's path (location of your DLL or EXE). ErrSaveToFile makes provision for the fact that several components may try to access the file at the same time trying to save its errors. It tries to open error file multiple times for 100 milliseconds before giving up. Make sure your component has enough permissions to access error file.

ErrSaveToDB saves report to tblErrorLog table using spErrorLogInsert stored procedure. Both objects must exist in your database. SQL scripts for creating them are included in installation package, in SQL folder. AppID parameter and corresponding field in tblErrorLog table allow you to sort out errors in case if you are sharing error log table between several applications or components. If ErrSaveToDB fails to save error report for whatever reason it posts a message to system event log.
Contents

3.2. Handling Without Re-raising

For EXE applications UI event handlers must be final points of stack unwind process - if you raise error again application will crash. For COM servers these would be public method of public classes that are callable from environment that doesn't support VB error handling, or supports it poorly, like VBScript. There are few easy steps that you should follow if you are coding class or form that is not going to re-raise errors.

Example:
Private Sub cmdDoStuff_Click()
    On Error GoTo errHandler
	'...... do stuff here 
    Exit Sub
errHandler:
    ErrorIn "cmdDoStuff_Click", , EA_NORERAISE
    HandleError
End Sub

Private Sub HandleError()
	........ 
End Sub

Note that you cannot re-raise errors in ANY event handler, not only visual controls, but handlers for events raised by your custom classes as well!

In the following sections we discuss some specifics of EXE applications and COM libraries. The difference is mainly in what HandleError Sub is doing.
Contents

3.3. Error Resolution in EXE Applications

Methods that cannot re-raise errors in EXE applications are all event handlers. These may be control's event handlers in VB forms, or handlers of events raised by COM objects.

We recommend you to use exceptions for handling user input errors. Exceptions are very efficient way to handle this type of tasks. When detecting user input error (or any other condition preventing execution from continuing) anywhere in your application all you need to do is raise an exception. Error resolution procedure must recognize the exception and show error message to user. Again, nothing wrong with application, it is user's error, or something wrong with environment that can be fixed by user.

Here is what HanldeError may look like:
Private Sub HandleError()
    On Error Resume Next 'no more errors or exceptions
    Screen.MousePointer = vbDefault 'restore pointer if it was changed to hourglass
    If InException Then
        Select Case ErrNumber
            Case EXC_GENERAL:    MsgBox ErrDescription, vbOKOnly, "Exception"
            Case EXC_CANCELLED:  'nothing to do - it is silent exception.
            Case EXC_MULTIPLE:   frmMultiUserErr.ShowUserErrors ErrDescription
            Case EXC_VALIDATION: MsgBox ErrDescription, vbOKOnly, "User input error"
        End Select
    Else
        If ErrInIDE Then
            frmShowError.ErrorReport = ErrReport
            Else
            ErrSaveToFile
            MsgBox "Application error. Contact support."
        End If
    End If
End Sub

As you see, we segregate predefined exceptions, but you may not want to do this, and just show message box for any exception.

Now with error resolution code in place application can use Check sub to set a validation point anywhere in code hierarchy:
    Check FileExists(FileName), EXC_GENERAL, "File not found: " & FileName

If file does not exist Check will throw exception, and after unwinding the stack execution finally arrives into HandleError , which will show "File not found..." exception message to user.

Interesting type of exception is EXC_CANCELLED. You can use it when you are canceling operation after asking confirmation from user. For example, you may have checkpoint like the following somewhere in your code:
    Check MsgBox("Something may be wrong here. Do you want to continue?", _
        vbYesNo, "Warning") = vbYes, EXC_CANCELLED, ""

If user answers No, Check will raise silent exception EXC_CANCELLED , which will not result in any additional messages.

Let's look at error-handling clause now. We use ErrInIDE function provided by HuntERR to detect whether we are running in IDE. What is good about current implementation is that it is safe and doesn't clear error object, so it may be called from anywhere in your application (credits to Dan F for the idea). If we are running in IDE we show error report in a separate window; if we are in executable we log error to file and display "Sorry" message to user.

When in development having a chance to see error report immediately you will find often unnecessary to go to step-by-step debugging. If you don't see the cause of error from error report it must be a red flag for you - what if similar error happens in released version on client machine? You will have to understand what has gone wrong using only error report. May be you need to reorganize your code so that error report will better identify the exact place and cause of error.

Beginning from version 3.1 HuntERR provides support for stopping on error immediately in method where error occurred. We discuss this technique in a separate section.
Contents

3.4. Error Resolution in COM Servers

The "top" methods that cannot re-raise errors in COM servers are those that are called from environment that doesn't support VB error-handling model. The most common example is VBScript in ASP. VBScript doesn't do great with errors raised from COM so you need to set the rule that script-callable public methods never raise errors. These methods should be reserved for calls ONLY from VBScript. To preserve error information after failed method returns to the caller you need to create corresponding properties in your class. Here is the example:
Public ErrorNumber As Long, ErrorSource As String, ErrorDescription As String
Public ErrorIsException As Boolean 

Private Sub HandleError()
    ErrorNumber = Err.Number
    ErrorSource = Err.Source
    ErrorDescription = Err.Description
    ErrorIsException = InException
    If Not ErrorIsException then ErrSaveToFile    'or ErrSaveToDB, or ErrSaveToEventLog
End Sub

Keep in mind that ErrorDescription may contain either error report, or message from exception. ErrorIsException propery to help VBScript to figure out quickly what happened - exception or application failure:
    '-- VBScript
    Dim Obj 
    Set Obj = CreateObject("MyLib.MyObject")
    Obj.DoSomething
    If Obj.ErrorNumber <> 0 Then 
        If Obj.ErrorIsException Then 
            Response.Write "Please correct your input: " & Obj.ErrorDescription
            'or something like this... 
            Else
            Response.Redirect "ContactSupport.asp"
        End If 
    End If 

Important question is how to inform the caller about failure if we don't re-raise errors? We can check ErrorNumber property after every call in VBScript. Alternative way is to implement all script-callable methods as functions returning true in case of success and false if error occurred. With this strategy there is one useful trick that allows you to join several method calls with AND operator, and check only once the AND of all return values. The problem is that VB doesn't support short Boolean evaluation - if we AND several Boolean functions, VB will call all of them even if the first one returned False and value of AND expression is false anyway. If object's method returns false as an indicator of error we should stop the operation, and stop calling object's methods. However if we add the following line to all script-callable methods inside our COM object:
If ErrorNumber <> 0 Then Exit Function 

then after any method of your object fails the object becomes effectively disabled. We will be able to join several calls in one AND expression:
'VBScript
If Not (MyObj.Method1 AND MyObj.Method2 AND MyObj.Method3) Then         
    'Error, redirect to "sorry" page
End If 

If error happens in Method1 then Method2 and Method3 will do nothing, and value of expression will be False.
Contents

4. Advanced Features

HuntERR provides additional support for applications that call API functions, use ADO objects, COM+ services, and work in IIS/ASP environment. You can also easily extend HuntERR functionality and add support for specific servers or APIs that you use in your application. HuntERR can help you to handle so called system exceptions - application faults so severe that they are not handled by VB runtime, even with error handlers in place. The example is "Access Violation" fault, which results in program termination by Windows, to put it straight - crash, together with VB IDE if it is running.

This chapter discusses in details these advanced HuntERR features.
Contents

4.1. API Errors

Windows API functions don't raise errors. Instead, they indicate success or failure by returning specific values. These return values are inconsistent throughout different Windows APIs. Some functions return zero as indication of success, others as indication of failure. You should check Windows documentation regarding particular function you are going to use. In any case if API function indicates failure by specific return value then application should call GetLastError API function to retrieve error number, which describes specifics of error. To get error description application can call FormatMessage API function that returns information about error identified by number.

If your application calls API function(s) that indicate success/failure in this way, then HuntERR helps you to handle its failure properly. ErrorIn retrieves API error number and description and includes them in error report automatically. All you have to do is raise error if API function failed. The following example calls GetWindowRect API function, and checks the returned value; zero indicates failure for this function.
Private Sub CallGetWindowRect()
    On Error GoTo errHandler
    ....
    Check GetWindowRect(hWin, R) <> 0, ERR_GENERAL, "GetWindowRect returned 0."
    ....
    Exit Sub
errHandler:
    ErrorIn "CallGetWindowRect"
End Sub

If GetWindowRect returns 0, Check raises error, which is caught by error handler. ErrorIn retrieves API error number and description automatically and adds it to error report:
GetWindowRect returned 0. 
  Time='04/24/02 12:36:03' App='HuntERRdemo:3.1.0' ADO-version='2.6' Computer='Solo' 
  Method: btnTestAPI_Click 
  Number: -2147216305 = &H8004144F = vbObjectError + 5199 = ERRMAP_APP_FIRST + 1 
  Source: HuntERR.Check 
  Description: GetWindowRect returned 0.
  API Error: (1400) Invalid window handle.
  Call Stack: CallGetWindowRect() ---------------------------------------------

Error report shows API error number returned by LastDllError in parenthesis (1400 in this case) and error description returned by FormatMessage API function. Internally ErrorIn checks value of Err.LastDllError property. VB applications should use this property instead of directly calling GetLastError, which does not work correctly in VB. Remember, you have to retrieve API error as soon as possible after it occurred - other functions may override its value.
Contents

4.2. ADO Errors

Whenever database operation fails ADO object fires an error. But this error often doesn't contain all error information. Sometimes multiple errors happen in a database call, and in this case error messages are placed into Connection.Errors collection. No doubt all this information should be included in error report. ErrorIn does this automatically if application provides reference to ADO object in DbObject parameter to ErrorIn .

DbObject is declared as object, without particular object type. What ErrorIn needs is Connection object, but it may happen that application doesn't create it explicitly. ADO library provides different ways of executing database operations. Application is free to choose among Command.Execute, Recordset.Open or Connection.Execute methods. For example, when application uses Recordset.Open method, it doesn't need to instantiate Connection object explicitly - ADO library does this automatically. This automatically created Connection object may be reached through Recordset.ActiveConnection property. In a call to ErrorIn application can provide reference to Recordset object itself, and ErrorIn will figure out how to get Connection .

Even more: you can provide object of your custom type - the only thing that is required is that it has a property Connection that returns ADODB.Connection object.

When retrieving Connection object, ErrorIn proceeds carefully, knowing that error could happen in procedure before any objects were created, or before opening connection to database, so Connection may be not available, or may be closed. Only if Connection object is successfully retrieved, and its Errors collection is not empty, only then ErrorIn adds list of ADO errors into error report.

ADO errors are added only during initial processing of the error, not during stack unwind process. Application should provide reference to ADO object in ErrorIn call in method where ADO operation is executed.

The following code uses ADO Command object to execute a SQL statement:
Public Sub ExecSQL(ByVal SQL As String)
    Dim Cmd As ADODB.Command
    On Error GoTo errHandler
    Set Cmd = New ADODB.Command
    Cmd.CommandType = adCmdText
    Cmd.CommandText = SQL
    Cmd.ActiveConnection = CONNECT_STRING
    Cmd.Execute
    Exit Sub
errHandler:
    ErrorIn "DBClass.ExecSQL(SQL)", SQL, , Cmd
End Sub

If SQL is invalid, we get error report that may look like this:
Invalid column name 'X'. 
  Time='04/24/02 12:39:56' App='HuntERRdemo:3.1.0' ADO-version='2.6' Computer='Solo' 
  Method: DBClass.ExecSQL 
  Number: -2147217900 = &H80040E14 = vbObjectError + 3604 = ERRMAP_APP_FIRST - 1594 
  Source: Microsoft OLE DB Provider for SQL Server 
  Description: Invalid column name 'X'.
  ADO Info: 
    ADO Version:   2.6
    DbObject:      Command
    Conn. String: 'Provider=SQLOLEDB.1;Password=;User ID=sa;Initial Catalog=Northwind;
    Conn. State:   adStateOpen
    Error:         Invalid column name 'X'.
    Error:         Invalid column name 'Y'.
    Error:         Invalid column name 'Z'.
  Call Stack: DBClass.ExecSQL(SQL='SELECT X, Y, Z FROM Customers') ------------------

As you can see, in version 3.1 connection information is reported automatically.
Contents

4.3. ADO Transactions

If error occurs in database application when it is in the middle of database transaction, part of error-handling procedure should be aborting or at least disabling transaction. There are two basic mechanisms that applications can use to control database transactions:

This section describes how HuntERR supports ADO transactions. COM+ transactions are discussed in the next section.

ErrorIn can abort ADO transaction automatically using RollbackTrans method of Connection object. To make it happen application should specify EA_ROLLBACK flag in ErrorAction parameter of ErrroIn, and provide reference to ADO object involved in transaction in DbObject parameter. ErrorIn cannot determine whether actual transaction was started and is active - ADO doesn't provide any function or property that "remembers" transaction status. If Connection object is retrieved successfully, it is open, and EA_ROLLBACK flag is specified, then ErrorIn attempts to call Connection.RollbackTrans anyway. If there is no active transaction, ADO fires an error, which is simply ignored by ErrorIn . However, the result of attempt to abort the transaction is shown in error report.

Let's look at the example. The following method tries to delete a record from Orders table by its OrderID:
Public Sub DeleteOrder(ByVal OrderID As Long)
    Dim Conn As Connection
    On Error GoTo errHandler
    Set Conn = New Connection
    Conn.Open CONNECT_STRING
    Conn.BeginTrans
    Conn.Execute "Delete From Orders Where Order_ID=" & OrderID 
    Conn.CommitTrans
    Exit Sub
errHandler:
    ErrorIn "DeleteOrder(OrderID)", OrderID, EA_DFTRBK, Conn
End Sub

In case of error no matter what has gone wrong in DeleteOrder method (database operation failed, or something else), if transaction started ErrorIn will abort it. The same is true for exceptions: if we add a line raising exception somewhere in code then current transaction will be aborted as well. So exceptions are not simply re-raised by ErrorIn , it takes care of active transactions if you want him to.

Transaction may be aborted during stack unwinding, not only at the moment of initial error processing. You may want to postpone the decision about aborting the transaction - we recommend to do it in the same method where you start it, and it is not necessarily the method where you execute actual database operations.
Contents

4.4. COM+ Transactions

HuntERR provides support for COM+ transactions. You can abort COM+ transaction automatically in two ways.

The first way is similar to controlling transactions through ADO connection object. If you specify EA_SET_ABORT flag in ErrorAction parameter in a call to ErrorIn then it will call ObjectContext.SetAbort, and your object will vote with "No" on transaction commit. Transaction will be aborted after returning from "top" method that executes in transaction context (See COM+ documentation for details). The other COM+ - related flag is EA_DISABLE_COMMIT. It causes ErrorIn to call DisableCommit method of object's context. With this method your object puts transaction temporarily on hold - any attempt to commit it will cause transaction to be aborted. Application can enable commit by calling EnableCommit method of object context.

The second way of aborting COM+ transactions is provided to support the technique that delegates transaction control to a special object, which we will call Transaction Control Object . The following paragraphs explain how it works.

COM+ transaction is started by instantiating a transactional object, i.e. COM class registered in COM+ services with transaction attribute "Requires New Transaction" (or "Requires Transaction" if there is no transaction in progress). What object can be used as such a transaction initiator? It can be your database access object that performs database operations, i.e. instantiates ADO objects and makes calls to database through ADO.

There are some inconveniences with this approach. First, many of database operations are only reading data, so they don't need to be executed in transaction. Transactions are expensive, and consume heavily database server resources. If you use your database class for initiating transactions, then all of its operations are executed in transactions. In order to make read operations efficient you have to move them to a separate non-transactional class. So you have to have two classes - non-transactional one for read operations, and transactional for operations that modify data in database. This "break-up" of database access methods doesn't seem very natural, and sometimes may be difficult to implement.

The other problem arises when you try to span transaction over several databases or even several database servers. You will have to code such distributed operation in one class, which is inconvenient - it seems better to have separate class for each database.

There is a simple solution to these problems. The idea is to give transaction control to a separate class, that will initiate and terminate transactions. We'll call this class Transaction Control Class, and its instance Transaction Control Object correspondingly. Application can include any object into scope of transaction by instantiating it from inside this transaction control object. We provide source code of ujTransaction - an implementation of such a Transaction Control Class. It is very simple and has only three methods: CreateInstance, SetAbort and SetComplete .

If you decide to use ujTransaction in your application, you should include it into your project, and configure it in COM+ with attribute "Requires New Transaction". Your database access classes should be configured with attribute "Uses Transaction".

To start the transaction simply instantiate ujTransaction. Then create instances of your database classes using not VB's CreateObject function but CreateInstance method of ujTransaction class. CreateInstance doesn't do anything complicated - it just calls CreateObject. But inside ujTransaction this call works differently than if you do it outside the class - the created object will be included into scope of transaction initiated by ujTransaction. In this way you can include several database classes into one transaction. After creating all necessary objects application performs database operations through created objects' methods as usual, and all these operations are executed in one transaction. Then application commits transaction by calling SetComplete method of ujTransaction class. To abort transaction, application should call ujTransaction.SetAbort .

So, if you want to execute database operation in transaction, you instantiate your database class through CreateInstance method of ujTransaction instance. Otherwise (if you don't want to run operation in transaction) you instantiate database class directly using CreateObject function.

The support that HuntERR provides for this technique is the following. ErrorIn can call SetAbort method of your transaction control object if you provide reference to it as the last parameter of ErrorIn .

Let's look at the example. Suppose you want to implement "money transfer" sub with accounts located in different databases. Here is how you can do this:
Public Sub Transfer(ByVal FromAcct As String, ByVal ToAcct As String, 
			 ByVal Amount As Currency)
    Dim Trans As ujTransaction, DB1 As CBranch1, DB2 as CBranch2
    On Error Goto errHandler    
    Set Trans = CreateObject("ujTransaction") 
    Set DB1 = Trans.CreateInstance("CBranch1")
    Set DB2 = Trans.CreateInstance("CBranch2")
    'Transaction started, DB1 and DB2 are included in its scope 
    DB1.ExecAccountDebit FromAcct, Amount 
    DB2.ExecAccountCredit ToAcct, Amount
    Trans.SetComplete 'Commit 
    Exit Sub
errHandler:
   ErrorIn "CBizClass.Transfer(FromAcct,ToAcct,Amount)", _
        Array(FromAcct, ToAcct, Amount), , , , , Trans
End Sub

When error occurs and ErrorIn is called it checks if TransControlObject is provided in the last parameter. If it is, ErrorIn attempts to call its SetAbort method. ErrorIn does this regardless of ErrorAction flags. Also in this case ErrorIn doesn't attempt to do anything about transaction through Connection object, regardless of ErrorAction flags, even if a call to TransControlObject.SetAbort fails.

Contents

4.5. WEB request information

ENUM_ERROR_ACTION enumeration defines flag EA_WEBINFO, which allows application to include WEB request information into error report. If this flag is set in ErrorAction parameter ErrorIn tries to retrieve object context, and then IIS Request object through object context. If it succeeds, it adds several lines to error report that contain request information like URL, query string, request method, etc. The resulting report may look like this:
Invalid column name 'X'. 
  Time='04/18/02 09:13:56' App='MyLib' ADO-version='2.6' Computer='Solo' 
  Method: ExecUpdateSQL 
  Number: -2147217900 = &H80040E14 = vbObjectError + 3604 = ERRMAP_APP_FIRST - 1594 
  Source: Microsoft OLE DB Provider for SQL Server 
  Description: Invalid column name 'X'.
  ADO Info: 
    ADO Version:   2.6
    DbObject:      Connection
    Conn. String: 'Provider=SQLOLEDB.1;Server=;Password=;User ID=sa;Catalog=Northwind;
    Conn. State:   adStateOpen
    Error:         Invalid column name 'X'.
    Error:         Invalid column name 'Y'.
    Error:         Invalid column name 'Z'.
  WEB Request Info:         
    RequestMethod='GET'        
    QueryString: 'www.dot.com/Test.asp?txtParam=SampleValue'         
    FormData:    ''         
    Cookies:     'SampleCookie=Very+Tasty;OtherCookie=Chocolate'    
  Call Stack: ExecUpdateSQL(SQL='Update Customers Set X = Y WHERE Z = 1') -----------
    Transaction: Attempt to call RollbackTrans succeeded
    Connection: Attempt to call Close succeeded

Beginning with version 3.1 EA_WEBINFO flag is included into EA_DEFAULT flag set, so you don't need to do anything special to get WEB request information included into error report - it will be done automatically, whenever your component executes in IIS environment.
Contents

4.6. System Exceptions

Some errors are so bad that they cause application crash even if you cover them with error handler. If you are running application from VB IDE it crashes as well. The example of such an error is "Access Violation" fault, which happens when your program tries to access memory outside the process's memory bounds. It may happen if you provide fault parameters to API function that expects memory buffer from you to place return information there. These errors are called System Exceptions, and we will use this term, although facing some ambiguity with HuntERR's own terms. Just remember that Exception is a user mistake, Error is your application's failure, and System Exception is application's super-failure.

If error handlers cannot help with system exceptions, what possibly can? There is a remedy for these situations, and a way to survive System Exceptions. Your application can setup system exception handler, a function that is called by Windows when system exception occurs. If you raise VB error in this function, then your application will survive. For more details and explanations we refer you to an excellent article "No Exception Errors, My Dear Dr. Watson" by Jonathan Lunman in May 99 issue of Visual Basic Programmer's Journal (Now Visual Studio Magazine). You have to be a subscriber of VSM to be able to see the past issues. Another source of information about system exceptions handling and stack unwinding mechanism in Windows is this article:
Win32 Exception handling for assembler programmers, by Jeremy Gordon.
http://archive.yates2k.net/Iczelion/Exceptionhandling.html

But as you see from its title it is not written for VB programmers - all samples are in assembly language, and you need to understand some internal Windows mechanics. However you can get some idea of what happens inside your application when exception occurs.

Without going into any more details, we'll just say that HuntERR provides two functions: ErrSysHandlerSet sets up custom exception handler, and ErrSysHandlerRelease releases it when it is no longer needed. If you are using some "dangerous" API functions, or COM Server(s) that may cause system exception, you should call ErrSysHandlerSet before using this potentially dangerous stuff. After you're done call ErrSysHandlerRelease to restore system default handler, but it is not required (if you don't remove exception handler, then make sure that your DLL instance is alive all the time while you keep it). If system exception happens with handler set in place your application will not crash but will get a VB error with reserved number ERR_SYSEXCEPTION. Err.Description will contain exception hex code and description. Let's look at procedure that intentionally calls API function with bad parameters:
Public Sub BadAPICall
    '-- Dim strBuffer As String * 255  -- this is correct declaration
    Dim strBuffer As String, BufLen As Long 'But we do it wrong
    On Error GoTo errHandler
    ErrSysHandlerSet
    .....
    BufLen = 255 'We promise 255-char buffer, but provide empty string 
    GetComputerNameAPI strBuffer, BufLen       ' System exception!
    .....
    ErrSysHandlerRelease
    Exit Sub
errHandler:
    ErrorIn "BadAPICall"   '-- And we survived!
End Sub

If parameters were so bad that they caused system exception, our custom handler will take over the situation and raise "normal" VB error. Error report after ErrorIn call will look like this:
(&HC0000005) Access violation 
  Time='04/24/02 13:35:57' App='MyApp:3.1.0' ADO-version='2.6' Computer='Solo' 
  Method: BadAPICall
  Number: -2147217407 = &H80041001 = vbObjectError + 4097 = ERRMAP_APP_FIRST - 1101 
  Source: HuntERR.SysExcHandler 
  Description: (&HC0000005) Access violation
  Call Stack: BadAPICall() ---------------------------------------------------------

All methods and declarations related to system exceptions are located in separate module HuntERR31SysH.Bas, and are almost completely independent from mainstream HuntERR functionality. In order to use these functions you must include this module into your project. Prior to version 3.1 ErrorIn was automatically releasing system exception handler. Now we know that it is not necessary, so beginning from 3.1 ErrorIn leaves it as is. We recommend you to set system exception handler at application start. You actually don't need to release it - Windows will do it automatically when application terminates.
Contents

4.7. HuntERR Extensions

HuntERR allows you to easily add support for specific COM servers used by your application, similar to one it provides for ADO COM library. HuntERR contains code that knows how to extract ADO errors from ADO instance. To do the same with some other component you don't need to modify HuntERR source. Instead, you encapsulate this error-extraction code in a separate class that we will call Error Extractor Class. It must have only one method:
Public Function Extract(ByVal COMServer As Object, ByVal Param) As String

COMServer is an instance of COM server that contains error information to be retrieved. Param is a free-form parameter that application sends to error extractor if it needs to pass some additional information to Extract method. Method should return a free-form error information (multi-line is OK) that will be included into error report built by ErrorIn .

You provide either instance of this class, or it's ProgID when you call the following HuntERR method in error handler:
Public Sub ErrGetFromServer(ByVal Extractor, ByVal COMServer As Object, _
        Optional ByVal Param, Optional ByVal Comment As String)

Extractor may be either instance of your extractor class, or its ProgID. COMServer is an instance of "troubled" object. Param is free-form parameter that application may use to pass some information to the Extract method. Comment is a string that will appear in error report in the header of error information from this COM server.

ujEEDomDoc.cls class provided with HuntERR is error extractor for MS XML DOMDocument. The following code loads XML string into DOM Document object, and uses ujEEDomDoc.cls in error handler to extract error if one occurs:
Private Sub ParseXML(ByVal XML As String)
    Dim xmlDoc As MSXML.DOMDocument
    On Error GoTo errHandler
    Set xmlDoc = New MSXML.DOMDocument
    Check xmlDoc.LoadXML(XML), ERR_GENERAL, "LoadXML method failed."
    MsgBox "XML string parsed successfully."
    Exit Sub
errHandler:
    ErrGetFromServer New ujEEDomDoc, xmlDoc, , " This is comment "
    ErrorIn "ParseXML(XML)", "..."
End Sub

If XML string is invalid then generated error report may look like this:
LoadXML method failed. 
  Time='04/24/02 13:45:33' App='HuntERRdemo:3.1.0' ADO-version='2.6' Computer='Solo' 
  Method: ParseXML 
  Number: -2147216306 = &H8004144E = vbObjectError + 5198 = ERRMAP_APP_FIRST + 0 = ERR_GENERAL
  Source: HuntERR.Check 
  Description: LoadXML method failed.
  Call Stack: ParseXML(XML='...') --------------------------------------------
    COM Server Errors: Server={DOMDocument} Extractor={ujEEDomDoc}  [ This is comment ]
      Description: End tag 'SomeElemXYZ' does not match the start tag 'SomeElem'.
      Source Line: </SomeElemXYZ>
      .              ^
      Line=3 Pos=3

ErrGetFromServer calls Extract method of extractor object, and stores returned error information in HuntERR internal buffer. This information will be later included into error report. We create the instance of error extractor on-the-fly using New Operator. (Note: Don't try to enclose this New... expression in parenthesis - VB will interpret this as reference to default property of the object, and the whole thing will fail). Alternatively we could use "MyLib.ujEEDomDoc" ProgID as the first parameter. COM Servers may either raise errors in case of failures (like ADO), or they may return false or some specific value indicating failure (like LoadXML method). In the latter case we must trigger error programmatically.

You can call ErrGetFromServer several times in a row in one error handler, possibly with different COM servers and extractors. Information returned by extractors is accumulated in internal buffer, which will be later included into error report by ErrorIn .

Here are some tips about creating extractors:

  1. ErrGetFromServer saves Err object properties in HuntERR internal variables in the very first call. This information will be later used by ErrorIn. Extractor doesn't need to worry about preserving Err object (it is very easily cleared)

  2. On the other hand, HuntERR restores the original error information into Err object just before the call to Extract method, so it CAN use Err properties if it wants to. If you call ErrGetFromServer several times in a row in one error handler each extractor will have the original copy of Err object.

  3. Keep in mind that COM object may be OK, and the cause of error is something else. In this case return empty string, and nothing will be included into error report. Your extractor will be called if application raises exception as well, so put a check for exception at the beginning of Extract method.

  4. Error can happen in a method before COM instance was created, so extractor must correctly deal with situation when COMServer parameter is Nothing. ErrGetFromServer calls extractor anyway. This is done to allow you to build extractor that gets errors from some special API or service, not necessarily COM object, and in this case you would send Nothing as COMServer parameter.

  5. Your error information may include CRLF combination, i.e. it may be multi-line. If your error message doesn't end with CRLF HuntERR will add it automatically. Don't indent or format the code - ErrorIn will do it automatically.

Contents

4.8. Preserving Error Information

However powerful ErrorIn and other HuntERR methods are, they cannot possibly cover every specific thing you may need to do in error handler. Sometimes you may need to put some custom code there. You certainly cannot put it after ErrorIn call - ErrorIn re-raises error, so your code never gets executed. If you put your custom code before a call to ErrorIn you should be very careful not to clear Err object because ErrorIn expects to find error information there. HuntErr makes this easier for you - just call ErrPreserve function at the beginning of error handler. ErrPreserve will save error information in internal variables, and your code doesn't need to worry about it. ErrorIn actually is looking for error information in one of two places - in internal variables where it could be saved by ErrPreserve, or in Err object itself. If it doesn't find it in either place, it puts into error description its own warning message that error information was lost, and a tip how to fix it:
ErrorIn: Error information was lost. _
	To fix: call ErrPreserve before doing anything in error handler. 
  Time='04/19/02 13:20:06' App='MyApp' ADO-version='2.6' Computer='Solo' 
  Method: frmMain.btnDoStuff_Click 
  Number: -2147216306 = &H8004144E = vbObjectError + 5198 = ERRMAP_APP_FIRST + 0 = ERR_GENERAL
  Source:  
  Description: ErrorIn: Error information was lost. To fix: call ErrPreserve..
  Call Stack: frmMain.btnDoStuff_Click() -------------------------------------

All HuntERR-provided methods call ErrPreserve automatically. If you call ErrPreserve several times only the first call preserves information, all following calls do nothing until ErrorIn is called. Error information is frozen inside HuntERR in this case.

Warning: make sure you call ErrPreserve, or other HuntERR functions calling ErrPreserve ONLY in error handlers, never from normal mainstream code. Otherwise you may lock internal HuntERR variables with error information, and when actual error happens in a method it will NOT pick the error information from Err object in error handler - it will report what ErrPreserve locked previously.
Contents

4.9. Long Strings, XML Formatting

Eventually parameter may contain long pieces of text, maybe even multi-line text. In this case showing them together with other parameters names/values in method's signature makes error report difficult to read. ErrorIn makes a special provision for this case. If parameter value is a string longer than 40 characters, or if it contains NewLine character then this value is shown separately, after method's signature:
Division by zero 
  Time='04/19/02 09:12:47' App='MyApp' ADO-version='2.6' Computer='Solo' 
  Method: frmMain.TestLongData 
  Number: 11 = &HB = vbObjectError + 2147221515 = ERRMAP_APP_FIRST + 2147216317 
  Source: HuntERRdemo 
  Description: Division by zero
  Call Stack: frmMain.TestLongData(Prm1='This is short parameter', Prm2={Text}, Prm3={Text})
    Value Of Prm2:
      'This parameter is a little longer than 40 characters.'
    Value Of Prm3:
      'And this has CRLF
      inside'

Another helpful thing that ErrorIn does is formatting XML strings. Although XML is human-readable by definition, looking through plain XML message is often not so easy task - the whole message is one continuous line, with no breaks or indents. It looks quite different from what you see in Internet Explorer when you load XML file. ErrorIn can help with this. It assumes that string parameters with names starting with "xml" prefix are XML messages. In this case it shows them separately in error report, not in method signature, and applies special formatting to it. It shows every XML element on a separate line with proper indentation for each child element. See HuntERR Demo Application for examples of error report.
Contents

4.10. Closing Files

If your application is working with disk files then part of the clean-up in error handler should be closing all opened files. You can do it easily by calling ErrCloseFiles method implemented by HuntERR. This Sub expects one or more file handlers, and it attempts to call Close on those of them that are not zero. Here is the example:
Public Sub ProcessFile(ByVal FileName As String)
    On Error GoTo errHandler
    Dim F As Long
    F = FreeFile
    Open FileName For Output As #F
        'Process file
    Close #F
    F = 0
    Exit Sub
errHandler:
    ErrCloseFiles F
    ErrorIn "ProcessFile(FileName)", FileName
End Sub

If error happens during file processing ErrCloseFiles will close the file. Without this call file will remain open and locked by Windows, so you won't be able even to retry the operation. You can specify more than one file handler to ErrCloseFiles. If file is not open, attempt to close it would result in error, which will be ignored by ErrCloseFiles.
Contents

4.11. Releasing Objects

Now let's discuss one hot and controversial topic - explicit release of local objects references in VB methods. Since first release of HuntERR programmers continued to ask us about this - they used to code explicit Set Obj = Nothing at the end of their methods and in error handlers, and now they wanted to continue doing this with HuntERR. We introduce support for explicit objects release in this version.

You may well ask - what's the big problem with releasing objects? Just put a few assignments in error handler before the call to ErrorIn, and that's it. It turns out things are not so simple. First, object release may invoke some automatically executed code in Class_Terminate method of object being released, which may eventually clear Err object. This is actually not a big problem - calling ErrPreserve method at the beginning of error handler may preserve error information.

The other problem is not so easy to solve: you cannot release ADO object in this way. You cannot release it before calling ErrorIn - ErrorIn needs a "live" reference to ADO object to retrieve error information, abort transaction or close connection. On the other hand you cannot release ADO object after calling ErrorIn - if ErrorIn re-raises error this release command never gets executed.

Here is the solution by HuntERR. It provides ErrRlsObjs method that allows you to safely release up to 8 objects references in one call. Let's look at a trivial example:
Private Sub TestRlsObjs()
    Dim Obj1 As CTestClass, Obj2 As CTestClass
    On Error GoTo errHandler
    Set Obj1 = New CTestClass
    Set Obj2 = New CTestClass
    ......
    Exit Sub
errHandler:
    ErrRlsObjs Obj1, Obj2 
    ErrorIn "MyClass.TestRlsObjs"
End Sub

Some comments on the code. ErrRlsObjs doesn't do much - it is simply a compact version of multiple Set ... = Nothing commands. It also preserves error information for later use by ErrorIn, so objects being released don't need to worry about this. ErrRlsObjs builds internal list of class names of objects being released; this list will be included into error report. Keep in mind that Class_Terminate and ObjectControl_Deactivate are actually event handlers - error inside these methods cannot be propagated to mainstream code where references are being released. We recommend you to put On Error Resume Next at the beginning of these methods.

Now what to do with ADO objects? Here is what ErrRlsObjs does. It checks class name of every object it releases, and if it is ADO Connection object, then it saves it in internal variable, and clears the reference in parameter. In case if object is ADO Recordset or Command object, it retrieves the Connection object from object's properties, and saves it in internal variable. This object reference saved in internal variable will be later used and cleared by ErrorIn. As a result if you release your ADO object through ErrRlsObjs you don't need to provide it's reference in a call to ErrorIn - in any case it would be Nothing after returning from ErrRlsObjs .

Let's look at the example:

Public Sub ExecUpdateSQL(ByVal SQL As String)
    Dim Conn As ADODB.Connection
    On Error GoTo errHandler
    Set Conn = New ADODB.Connection
    With Conn
        .Open CONNECT_STRING
        .BeginTrans
        .Execute SQL
        .CommitTrans
        .Close
    End With
    Exit Sub
errHandler:
    ErrRlsObjs Conn
    ErrorIn "ExecUpdateSQL(SQL)", SQL, EA_DFTRBKCLS
End Sub

In case of error the following report is generated:

Invalid column name 'X'. 
  Time='04/18/02 09:13:56' App='HuntERRdemo:3.1.0' ADO-version='2.6' Computer='Solo' 
  Method: ExecUpdateSQL 
  Number: -2147217900 = &H80040E14 = vbObjectError + 3604 = ERRMAP_APP_FIRST - 1594 
  Source: Microsoft OLE DB Provider for SQL Server 
  Description: Invalid column name 'X'.
  ADO Info: 
    ADO Version:   2.6
    DbObject:      Nothing (Connection object was preserved internally)
    Conn. String: 'Provider=SQLOLEDB.1;Server=;Password=;User ID=sa;Catalog=Northwind;
    Conn. State:   adStateOpen
    Error:         Invalid column name 'X'.
    Error:         Invalid column name 'Y'.
    Error:         Invalid column name 'Z'.
  Call Stack: ExecUpdateSQL(SQL='Update Customers Set X = Y WHERE Z = 1')
    Released Objects: [Connection]
    Transaction: Attempt to call RollbackTrans succeeded
    Connection: Attempt to call Close succeeded
  Call Stack: frmMain.cmdTest_Click() ------------------------------------

As you see, we didn't need to provide a reference to Conn object when calling ErrorIn - it is Nothing anyway. After method's signature ErrorIn has put a note that an object of class name [Connection] was released. For additional clarity there is also a message saying that although DbObject was Nothing at the moment of a call to ErrorIn, the Connection object was preserved internally. ErrorIn used this reference to retrieve errors, rollback transaction and close connection. The reference was cleared before ErrorIn re-raised error, so all references are finally gone, and object is destroyed.

Now you are probably already wondering: why we need all this complexity with ADO object? What if we do the following. We use ErrRlsObjs to release all non-ADO objects. We also change specification of ErrorIn's DbObject parameter from ByVal to ByRef. ErrorIn should set this parameter to Nothing before exit, so ADO object reference is cleared.

Unfortunately, it doesn't work, we explain why in Appendix.
Contents

4.12. Stopping On Error in IDE

Since first release of HuntERR many programmers expressed the strong desire to have some way to stop in failed method immediately when error occurs, before stack unwinding starts. In version 3.1 we introduce this functionality. All you need to do now is add one line to error handler in all methods:
errHandler:
    If ErrMustStop Then Debug.Assert False: Resume 'Add this line
    ErrorIn "...."
End Sub 

Whenever error occurs in the method ErrMustStop will show you message box with the message like the following:
Error: Division by zero Do you want to retry the operation in step 
						mode? Click YES to retry, NO to move to the caller, CANCEL for no more stops
					
Message box would have three buttons: Yes, No and Cancel. If you click Yes, execution will stop at Assert statement in error handler. You can try to figure out what is wrong, fix the bug, and continue in step mode. After Resume operator is executed you will return to failed operator, so you can retry it.

In case if you see that the cause of failure is not in the method where you are stopped, but somewhere above the call stack, then you can move up to the caller if you press No next time the message box appears. When you click No ErrMustStop would return false, execution will proceed to ErrorIn in error handler, which in turn would re-raise the error. If you have similar stop statement in the caller, you again will see message box with the same statement, but now it will be shown from the caller's error handler. You again will have a chance to fix the error, or proceed to the caller.

If you press Cancel button at any time HuntERR will let error go - you will not see any boxes anymore, error handlers will process error in usual way, and you will see error report.

HuntERR Add-In adds this "stop" line to your error handlers if you check the corresponding box in Add-In's settings dialog. You can suppress this "stop" behavior even when stop statements are in place if you define conditional compilation constant H_NOSTOP = 1 in project Properties dialog box.

Remember that stopping is enabled only in VB IDE; in compiled executable ErrMustStop always returns false.
Contents

5. Check Sub: Detailed Look

Let's now have a more detailed discussion of Check sub functionality. This method's functionality had been substantially expanded since version 3.0, so we'll spent some time discussing all these features.

5.1. Check Declaration and Overview

First let's have a look at Check declaration in HuntERR.
Public Sub Check(ByVal Cond As Boolean, _
                 ByVal AnErrNumber As Long, _
                 ByVal AnErrDescr As String, _
                 Optional ByVal Values, _
                 Optional ByVal AHelpFile, _
                 Optional ByVal AHelpContext)

The first parameter is a Boolean value to be checked. If this value is True, we'll say that "check passed", and "check failed" otherwise. Check failure results in raising error with number and description specified as second and third parameters. Values parameter is an array of values that should be embedded into error description. The last two parameters specify values to be put into corresponding fields of Err object when raising error. Only the first three parameters are required, the rest are optional.

Check basic functionality is quite simple. It is used to raise errors and exceptions if some ongoing condition(s) require interrupting the mainstream workflow and starting the stack-unwind process. Example:
Check X > 0, EXC_GENERAL, "X may not be negative."

With this basic functionality comes a whole set of additional conveniences that may help you to easily code various condition checks in your applications. We discuss all these features in the following sections.
Contents

5.2. Parameterized Description

Sometimes when application raises error or exception the description provided with raised error is not a constant string, but should be customized by including current values of some variables. Directly forming the string by concatenating fragments may be really painful to code. To make a life easier Check allows you to specify a template of the resulting message with place-holders like (%1, %2, etc) identifying places where to insert values of variables. The values themselves are provided in Values array. Additionally Check helps you to easily build multi-line messages by substituting "||" placeholder with a NewLine character. For example, the following command:
Check Count > 0, EXC_GENERAL, "Customer '%1' not found in state '%2'.||Operation cancelled.", _
    Array(CustName, State)

may result in message that looks like this:
Customer 'Smith' not found in state 'TX'.
Operation cancelled.

There may be a much bigger benefit in this embedding technique than simply easier coding. It can become a savior if you don't hard-code your messages directly, but store them separately in database or in resource file. Usually it happens if you are creating application that will be internationalized. For such applications using customized messages may pose a real challenge. With the described embedding technique things are much easier. You just store message templates with placeholders in your message tables, whatever they are, and customize them when you use it in code. Your code in this case is independent of target language, even if it has grammar quite different from English.

Note that Check does the embedding job correctly no matter what values you are embedding. You don't need to worry about situation when the first embedded value contains sub-string that looks like a placeholder (%2 for ex.) - Check will do everything correctly.
Contents

5.3. Accumulating Messages

There are situations when your code needs to check multiple conditions, and build overall message based on results of each check before you raise error or exception. Check Sub allows you to perform such accumulation. If you use ERR_ACCUMULATE constant as error number parameter (actually it is plain 0) then Check doesn't raise error immediately if Cond is False, but adds message to internal buffer available through ErrAccumBuffer property. We may say it Accumulates messages in this way. After completing all checks application can raise "normal" error or exception providing accumulated message buffer as description. Here is the example of validating user input in multiple fields:
Private Sub ValidateAll()
    On Error GoTo errHandler
    ErrAccumBuffer = ""  'Clear the buffer
    Check Trim$(txtAccumName.Text) <> "", ERR_ACCUMULATE, "Name may not be empty"
    Check txtAccumSSN.Text Like "???-??-????", ERR_ACCUMULATE, _
        "SSN entered [%1] is invalid. Should be in format '123-45-6789'", _
        txtAccumSSN.Text
    Check txtAccumPhone.Text Like "???-???-????", ERR_ACCUMULATE, _
        "Phone you entered [%1] is invalid. Should be in format '123-456-7890'", _
        txtAccumPhone.Text
    Check ErrAccumBuffer = "", EXC_MULTIPLE, _
        "Please correct the following errors: ||" & ErrAccumBuffer
    Exit Sub
errHandler:
    ErrorIn "frmMain.ValidateAll"
End Sub

The overall check pass/failure at the end is determined by accumulated buffer: if it is empty, it means that all checks passed; if it is not, then at least one of them failed. Don't forget to clear ErrAccumBuffer before starting checks. Messages in the buffer are separated with NewLine character. As you see, you can also use parameterized messages with accumulation. The resulting message shown to user may look like this:
Please correct the following errors: 
Name may not be empty
SSN entered [123] is invalid. Should be in format '123-45-6789'
Phone you entered [456] is invalid. Should be in format '123-456-7890'
Contents

5.4. Sending Additional Tip to HandleError

The last two parameters of Check Sub match the properties of Err object. Their names suggest that it is something to do with Help system, and quite possible you are thinking they are of no use for you. But it's not a must that these parameters should carry only what their names suggest. Think of them simply as additional pieces of information that you may send in Err object to your HandleError Sub that performs error resolution. In some situations you may find these parameters very helpful. We give you just one example, user input validation in desktop application.

Suppose you implemented a method that validates several values entered by user. If validation fails for any of them, you raise exception with appropriate message, which results in message box shown to user by HandleError Sub. In addition to telling user what is wrong with the input you may want to automatically set focus to the control that has invalid value - it will make your application look much friendlier. The easy way to do it is to specify the name of the control as AHelpFile parameter of Check Sub. HandleError should check this property, and if it is not empty, set focus to the control. You can easily retrieve a control by name using Form.Controls collection. We show working example of this technique in HuntERR Demo Application.

You've got the idea. You can probably find some other uses for these two parameters.
Contents

5.5. MessageSource Object

As we said before Check Sub is just a convenient substitute for If...Then Err.Raise... combination. Most of the time these two methods are equivalent, except one case, when Check is at serious disadvantage. It happens when you have your messages not directly hard-coded in your program, but stored in some table or file. The problem is that the message must be retrieved before you call Check, even if it will not use it because the condition being checked is True. With If...Then... construct you can first check the condition, and only if it is false you proceed to retrieve the message from storage. Considering the fact that conditions being checked are expected to be True much more often than False, then using Check may bring some unnecessary overhead.

Check provides the way around the problem. Instead of specifying the message directly when calling Check you can provide message index, or key, which identifies message in your storage. Message itself would be retrieved by Check through MessageSource object, which you provide to HuntERR through ErrMessageSource property. This object must have GetMessage method, which returns message by index/key. It is up to you to decide how messages are indexed.

Message key in AnErrDescr parameter is detected by starting pound (#) character. That is, if message source object was provided by application, and AnErrDescr parameter starts with # sign, then Check assumes that this is message key, so it calls GetMessage function of MessageSource object to retrieve the message. It sends AnErrDescr parameter value as a parameter of GetMessage . The format of message index or key is defined by your application itself, you can accept any rules you want regarding this. The only thing required is that reference starts with # sign.

HuntERR Demo Application shows additional technique that you may use. The biggest problem with storing messages in some storage instead of hard-coding them is missing entries. You enter message index 123 in code, make a note that you must add #123 to a file, but forget to do it. It is very difficult to catch all situations like this during application testing. Some of them are shown only if specific errors or situations occur, and it may never happen during testing. The worst thing happens: application tries to show appropriate message to user, doesn't find it, and shows "message not found". There is nothing more damaging for your reputation as a programmer than "Message #123 not found" message box. It can drive users crazy. Program rejects their input, they want to know what is wrong, but instead they get this Not Found nonsense.

The safe way to go then is to hard-code the default message together with message index. This default message would be shown if the original message is not found in storage. This may save your face in this situation. As this default message duplicates stored message most of the time then it makes your code more clear, as in addition to message index (12345 for ex.) you specify what is the corresponding message is about. HuntERR Demo application shows the example of how this can be done. It uses convention that message tag formatted as follows: it starts with #, followed by message index, then two pipe characters, then default message. Again, these conventions are at full discretion of application. Check simply passes AnErrDescr from caller method to MessageSource.GetMessage , and expects to get the message.

See HuntERR Demo Application for more implementation details.
Contents

6. Using HuntERR

6.1. Well Behaved Components

In this section we would like to have a look at the problem of error handling in a broader context of enterprise development, and focus on component architectural level.

Lets try to formulate what features, what behavior in error-handling would make a software Component a good participant in enterprise system. Lacking the better term we'll call this set of features Well Behavior in Error Handling. We think there are three basic rules of conduct that make up Well Behaved Component.

  1. Well Behavior regarding its own errors . Component should never crash itself. In case of internal failure it should build error report that alone should be enough to spot the problem and to fix it. A good basic assumption is that ERRORS ARE NOT REPRODUCEABLE AT ALL, so error report is the ONLY INFORMATION that doctor will have to prescribe the cure. This error report should be logged somewhere but never lost. The client component that requested the failed operation should be properly informed about the failure, and error report must be available to client as well.

  2. Well Behavior as Client . Suppose the Component acting as client requests some service from other component in the system (Server), and this other Server runs into error when processing request. Well behavior as Client means that in situation like this the Component should collect all available information about error from the failed Server and include it into Component's own error report. In other words the Component should care about error information from its servers in the same way it cares about its own error information - collect it, log it, pass it to the caller.

  3. Well Behavior as Server . Suppose the Component acting as server processes request from other component (Client) and runs into error caused by invalid request. Well Behavior in this case means that Component should help the Client as much as possible to figure out what is wrong with the request. A simple and very helpful thing to do is to include invalid request data into error message sent back to Client.

Although these rules seem obvious and quite reasonable to follow, it's amazing how often they are violated or even completely ignored. We'll give just one well-known example.

Famous "ActiveX component can't create object" is a textbook example of BAD behavior as Server. Your application (Client) requests COM services (Server) to perform a service - create an object identified by ProgID. If something goes wrong (misspelled ProgID for example or mess in the Registry) COM fires an error with "ActiveX can't.." description, which doesn't include failed ProgID. Probably COM assumes that application knows what ProgID it is talking about - the one that application just used in a failed call to CreateObject. However it becomes a real problem if application calls CreateObject several times in a row for different ProgIDs, probably even in one procedure. In this case even the power of HuntERR cannot help - failure in any of these CreateObject calls results in exactly the same error message and error report, and you have to guess which ProgID caused the error. The obvious solution is to make a "wrapper function" for CreateObject that reports ProgID in its error report. Essentially this wrapper turns COM services into Well Behaved Server.

You can probably recall situations when third-party applications were failing with this error message. These situations are usually easily fixable - re-registering COM DLL helps most of the time. But you have to know the component name, and error message doesn't provide it. Not much you can do in this situation. Just the same old Guess-Try-Pray-Fail exercise. Obviously including ProgID into error message (i.e. Well Behavior as Server ) could help a lot in this situation.

Staying with behavioral terminology Rule #1 (caring about internal errors) seems to be about internal discipline of the software component, while Rules #2 and #3 (Well Behavior as Client and Server) are about component's public conduct. There is an interesting twist here. The two public conduct rules are applicable to internal behavior of the component as well, if you think about objects and functions calling each other inside your component as clients and servers.

Thus general rule about rules comes up:

To get well behavior in a system, one should enforce well behavior, both internal and external, at all levels of system hierarchy.

We think that many problems in enterprise development originate in bad error-handling behavior of components. We urge you to build Well Behaved ones.

HuntERR can help you with this.
Contents

6.2. Shift to the Middle Tier

There is one important change that HuntERR may bring into architecture of your multi-tiered applications.

For the most part the functionality of presentation tier (ASP scripts) and middle tier (COM business objects) is interchangeable. Most of the things you can do in middle tier you can do in VBScript in ASP. Standard recommendation for choosing where to implement stuff is the following: without strong arguments in favor of ASP do it in the middle tier. Business COM objects are a better place for implementation for at least two reasons - much faster execution of compiled code in COM objects, and the fact that code in VB COM libraries is much easier to organize, maintain and reuse than script in ASP pages.

However there was always a practical consideration that favored script - when things are going wrong script is much easier to debug. To see variable value at a particular moment in script you just add "Response.Write ... " operator, hit the page again, and see the value shown on the page in browser. When you want to see something inside COM object then things are not so easy. You have to open VB DLL project, add some print-to-log command, rebuild and re-deploy the DLL, hit the page again, and finally open the log file (whatever it is) to see what you wanted to see. We think that this consideration of easier debugging is keeping in today's applications much more of functionality implemented in ASP script than this environment really deserves.

With HuntERR watching and reporting errors things may really change. Now it becomes easier to investigate errors happening in COM objects than errors in VB script. If error happens, you don't need to add any debug printing. You just hit the ShowErrors.asp page or alike, and see the whole story in error report.

This change in debugging suggests that you may need to re-evaluate your options, and implement much bigger part of functionality in middle tier than you used to. We advocate (and practice ourselves) the extreme approach - to move it ALL to the middle tier, or as much as possible. The best ASP script in our opinion is the one that declares only one variable - business object. Script creates its instance, feeds it with information from the request, calls some DoTheJob method, and then goes on to generate the page using object's properties to get data values on-the-fly and insert them into HTML.

Shifting stuff to middle tier actually means that you may finally start doing things as they should be done - all functionality is implemented in compiled components, and script is just a light-weight connector between components and IIS. This is just one of HuntERR's many positive side effects - there will be many others.
Contents

6.3. Architecture Design Tips

Error Handling should be essential part of your application's functionality, and it should be designed with the same care and attention to details as the mainstream functionality. Here are some general issues that we recommend you to address during architectural design of your application:

You can and probably will come up with your own additional practices and rules in error handling. Many of them are applicable not only to one specific project, but to many of them. It's worth writing them down into Error Handling Policy document, which can be part of your company's Design Standards document set. Quite possible you didn't have such a document in the past - without reusable technology of error handling there was not much to standardize. In every application developers built custom error handling adapted to application specifics, trying to get the most in trapping errors without excessive coding. HuntERR provides you with powerful, reusable and consistent technology of error handling, and thus makes it possible and necessary to establish some default rules of applying it in your projects. Each project may have its own specifics - design standards should not set the rigid rules that are a must, but rather default and recommended ones that may be excepted if necessary.

And the last friendly tip: if your clients are not comfortable with so much error-handling stuff in design documentation (...aren't we shooting at a perfect thing?...), just ask them if they are OK flying jets equipped with life jackets, oxygen masks and black boxes. This will help to explain your point.
Contents

6.4. Example: Creating a Wrapper

We'll give an example of how to build a wrapper function that turns a not well-behaved function into well-behaving one. It is a real-life listing of CreateInstance method of ujTransaction class.
Public Function CreateInstance(ByVal ProgID As String) As Object
    On Error GoTo errHandler
    Set CreateInstance = CreateObject(ProgID)
    Exit Function
errHandler:
    Err.Raise Err.Number, Err.Source, Err.Description & _
        ": ujTransaction.CreateInstance('" & ProgID & "')"
End Function

As you see, all you need to do in a wrapper is include original error description and parameters of the client request into error message. This wrapper doesn't use ErrorIn in error handler. We are showing it as an illustration that Well Behavior as Server doesn't necessarily mean using HuntERR. However you can put a normal HuntERR-style call to ErrorIn function into error handler.
Contents

6.5. Setting Up Your Projects

To use HuntERR in your application you have to include HuntERR source files into your VB project(s). The following table contains the list of source files coming with HuntERR with brief instructions on how to use them.

 

Source File Description/Instructions

 HuntERR31.Bas

Main HuntERR library file. Include it into all projects where you are going to use HuntERR for error handling.

 HuntERR31SysH.Bas

System exceptions handling methods. Include it if you are going to use this functionality.

 ujTransaction.Cls

Transaction control class. Include this file into COM DLL project if you are going to control COM+ transactions in the way described in COM+ Transactions section of this document. Make sure its MTSTransactionMode property in VB and Transactional property in COM+ is set to Requires New (Transaction)

 ujEEDomDoc.Cls

Error extractor for MSXML DOMDocument COM Class. Include it if you are going to use MSXML library . For specific COM servers that your application uses you'll have to create your own custom extractor classes.

 frmShowError.frm

A simple form to show error report when application runs in VB IDE. Include it during development into your EXE project or EXE application that you use to test your COM Server. Assign frmShowError.ErrorReport = ErrReport in your error resolution method.

 

You may need to set conditional compilation arguments used by HuntERR. To define argument(s) right-click on your project in Project Explorer in VB, and select Properties from popup menu. Switch to Make tab. Enter argument assignment in the box Conditional Compilation Arguments. Use colon (:) to separate definitions if you have more than one:

    H_NOCOMPLUS=1 : H_EXTBASE=1 : H_NOENUMS=1

The following table lists conditional compilation arguments used by HuntERR :

 
Argument Definition Description
 H_NOCOMPLUS=1

Identifies that project doesn't use COM+ services, so source lines in HuntERR that use these services will be excluded.

 H_NOENUMS=1

Identifies that you have Enum ENUM_ERRMAP declared in some public class, so it should be excluded from HuntERRxx.Bas during compilation.

 H_EXTBASE=1

Identifies that error ERRMAP_BASE is defined in other module. This constant sets the starting number for error ranges defined by ENUM_ERRMAP enumeration. You may need to shift these ranges if you use third-party components that already use some ranges above vbObjectError. On example is OracleObjects that use error numbers in [vbObjectError + 4096...8192]. If you use OracleObjects then you should set H_EXTBASE=1 and define the following constant in some BAS module:
Public Const ERRMAP_BASE = vbObjectError + 8192

 H_NOSTOP=1

Set this variable to prevent stopping on error in VB IDE, if you have stop-on-error operators in error handlers. This is just a simple way to make HuntERR ignore these commands in VB IDE without going through code and removing them one-by-one.

 

And this is it. You are ready to go with HuntERR.
Contents

6.6. A Few Coding Tips

Here are a few coding tips, things good to remember when coding:

Contents

6.7. HuntERR Add-In for Visual Basic

We provide VB6 Add-In for automating the process of adding HuntERR-style error handlers to your code. You can use it to add error handlers in one-at-a-time manner, or you can add handlers to all methods in your code module. This mass-insertion feature allows you to quickly migrate your existing code to HuntERR -style error handling. Add-In is easy to use, and we hope you quickly figure out everything about it yourself. We'd like to provide just some introductory comments.

To install Add-In locate it in HuntERR directory, and register it using regsvr32.exe Windows registration application. You may want to move it to VB98's AddIn folder, or to WinNT\System32 before the registration. Then start Visual Basic IDE and select Add-In | Add-In Manager from main menu. In the list that appears locate HuntERR Add-In, click it, and check the Loaded/Unloaded box. Close the dialog.

If you select Add-In menu again, you will see two new commands: [HuntERR: Insert Error Handler], and [HuntERR Control Panel]. The first command directly inserts HuntERR-style error handler into property, function, or sub procedure where cursor is located. Selecting this command will first bring popup window that gives you preview of your procedure with inserted error handler. Confirm or cancel the operation. Uncheck the box at the bottom of the dialog if you want to skip this dialog in the future. You can use shortcut sequence <Alt>-A,I for fast command selection.

The inserted error handler may be customized in HuntERR Control Panel dialog. Select Add-In|[HuntERR Control Panel] menu command. A dialog window with 3 tabs will appear.

The first Options tab allows you to customize error handlers being inserted. These options apply both to "one-method" insertion and "whole-module" command on Current Module tab.

When inserting the handler Add-In tries to guess whether it is "externally"-called procedure, or just internal, private one. Add-In takes into consideration type of module and private/public type of current procedure. Of course, the guess is not always accurate, add-in doesn't know how exactly you are going to use your module or method. Depending on "guessed" procedure type add-in might use different "styles" of inserted error handler. You may specify names of constants to use as ErrorAction parameter in each case, and additionally method to call after ErrorIn in externally called methods.

The second Module tab you can add error handlers to all procedures in your code module. You can migrate your existing code to HuntERR error handling using this mass-insertion feature.

When inserting error handlers to all procedures in code module add-in can skip "trivial" properties that don't need error handlers. Add-in identifies as "trivial" a property that has no error handler, has zero or one parameter, and has no more than two simple assignments statements in its body. You may choose not to add error handlers to properties like this.

Processing the whole code module is a tricky process. You probably will do it several times before you get what you want. Add-In provides you the way to roll-back the whole operation. If you don't like what add-in did to your code, just bring the dialog again and press Undo the last operation button. This "Undo" feature is independent from standard Edit|Undo IDE command

The last tab shows About information

You can download HuntERR add-in source code from our site. Your feedback about Add-In is welcome. HuntERR Add-In is a copyrighted software, is part of HuntERR software package, and is distributed under its terms and conditions of use.
Contents

7. Final Thoughts

7.1. Bugs, Errors and Application Malfunctioning

Let's have a refreshed look at the whole problem of bugs and errors. First let's give some basic definitions.

Our ultimate goal in any software project is to deliver bug-free application. We do it by eliminating bugs one-by-one starting with the first compilable version. To eliminate a bug we must first discover the fact that it is there. It is easier to do if application manifests the fact explicitly with run-time error. This brings us to the following strange but true conclusion:

Errors are good - they help us find bugs.

Yes, errors are good fellows. They are messengers about bugs. Don't blame messengers, but try to get maximum information from them. We are talking here about changing attitude towards errors. For many programmers Error is a horrifying event that they try to avoid as hard as they can. Application continuing doing wrong things seems a better outcome than application exploding with run-time error. And raising errors on purpose as we do with exceptions is then can be well viewed as an act of pure insanity. If you have this kind of attitude - you'd better change it. With HuntERR errors may become your friends.

First, don't let errors go unnoticed by suppressing or ignoring them. Second, detect faulty situations and generate errors whenever possible. Don't hesitate to put extra validation code and to raise error immediately when you detect that something is wrong. HuntERR will take over the situation and deliver the message to you. In our experience extra validation code has negligent negative impact on performance in most cases, but can make a huge difference in time spent in debugging, testing and application maintenance. Application malfunctioning faults are much more difficult to detect and fix than plain run-time errors. Detecting malfunctioning is a job that keeps busy QA departments in many companies. So while coding try to turn malfunctioning faults into errors, whenever possible. Make your application blow up with error report window instead of silently continuing along the unknowingly wrong path of execution.

And finally - try Exceptions. It's absolutely OK to raise error just to jump to the caller, it's not insanity. It is very powerful mechanism that may greatly benefit your applications, particularly their architecture. Try them - you will never regret, and will never want to come back to the world without them.
Contents

7.2. What will happen

In this last section we'd like to give you a warning about what will happen if you decide to go on and give HuntERR an opportunity to handle errors in your application. Don't worry - wonderful things will happen.

You will enjoy bumpless ride of coding mainstream functionality without spending any time on thinking about errors and handlers. You type method header, press Enter, VB appends End ... operator; then you press <Alt>-A,I and HuntERR Add-In inserts error handler. And that's it! You turn to main business with confidence that errors are taken care of, and in such a way that they have no chance to make a big trouble.

You will move much faster when coding, debugging and testing your application. Most of the time you will not need to go into step mode in debugger - you will see error report immediately, and at a glance will understand what is wrong and how to fix it. You will be able to quickly fix any error that happens on remote server, where your components are installed as a part of a bigger system.

But the main thing that will happen is that you will be producing really rock-solid code. You will gain much more confidence in what you do, and reduce greatly a chance factor in your work. No more endless periods of inexplicable crashes when "everything is almost working", but not quite yet because you can't catch this mysterious error that happens from time to time, and breaks everything. Your components either work OK, or you fix the problem immediately when it appears. You will get much more control over development process, will be able to estimate accurately the time you need to do the job, and do it on time.

This will happen, believe us. Enjoy the ride.
Contents

Appendixes

A1. HuntERR Quick Start Guide

Here is a quick start guide the simplest case: EXE application, possibly using COM classes in a separate DLLs, which are part of the project group.

  1. Register HuntERR Add-In DLL in your system. Launch Visual Basic, load your project group.
  2. Select Add-Ins|[Add-In Manager] menu command, in dialog that appears find HuntERR Add-In in the list, select it, make sure [Load/Unloaded] and [Load On Start-Up] boxes are checked, check them if they are not. Click OK.
  3. Select Tools|Options menu command in VB, switch to General Tab; select [Break On Unhandled Errors] option. Click OK.
  4. Add HuntERR31.Bas to each project in a project group. Add frmShowReport.frm to EXE project ONLY.
  5. Define H_NOCOMPLUS=1 in conditional compilation arguments in Properties dialog of every project in a project group.
  6. For each Form Module: Select Add-Ins|[HuntERR Control Panel] Menu command; In Options page, select Delete option button, check [Use Error Action ...] check box, make sure that text boxes show "EA_NORERAISE" and "HandleError" strings; check [Add Stop On Error Operator] box; all other check boxes must be unchecked. Switch to Module tab;make sure [Don't add...] check box is checked. Click [Insert Error Handlers] button. Confirm OK in a message box that appears. Repeat these steps for each form code module in your project.
  7. For each non-form code module: Select Add-Ins|[HuntERR Control Panel] Menu command; In Options page, select Delete option button, UNCHECK [Use Error Action ...] check box, check [Add Stop On Error Operator] box; Switch to Module tab; make sure [Don't add...] check box is checked. Click [Insert Error Handlers] button. Confirm OK in a message box that appears. Repeat all steps for every non-form code module in your project.
  8. Copy the following code to some .Bas module in main EXE project; you may need to do it also for DLL projects where you have Form modules.

    Public Sub HandleError()
        If InException Then
            MsgBox ErrDescription, vbOKOnly, "Exception"
        Else
            If ErrInIDE then frmShowError.ErrorReport = ErrReport
            ErrSaveToFile
        End If
    End Sub
    

  9. Compile and run project group or project. HuntERR is watching your errors now. If you can't compile - then probably you need to do some extra configuration, and you'll have to read more of this document.

A2. Releasing ByRef Object Parameters - When It Doesn't Work

In this section we are going to explain why setting ByRef parameter inside the method doesn't always change the original variable in the caller, hence we can't clean-up object reference through DbObject parameter of ErrorIn function.

The first thing to notice is that normally parameters with ByRef attribute require the actual argument to have exactly the same data type as method's parameter. This is true for at least all numeric and string data types - VB simply wouldn't compile if there is a mismatch. This rule seems quite natural - ByRef means sending a pointer to a variable, so the called method is manipulating the original variable directly, so it must be sure that it interprets correctly the information referenced by pointer.

Now recall that DbObject is declared ByVal with data type Object. Object data type allows application to pass any of three ADO objects: Connection, Recordset or Command. Does this type mismatch prevent us from declaring parameter ByRef? No, we can, and the reason is that VB is a bit more flexible when Object types are involved. It allows type mismatch for ByRef Object parameters.

Let's now follow an example. Consider the following code:
Sub MyMethod()
    Dim MyObj As CMyClass
    Set MyObj = New CMyClass
    ReleaseObj MyObj
End Sub

Sub ReleaseObj(ByRef Obj As Object)
    Set Obj = Nothing
End Sub

This code is compilable, and VB accepts it. But the fact is the "exact type matching" rule for ByRef parameters still holds. VB simply does something like the following behind the scene:
Sub MyMethod()
    Dim MyObj As CMyClass
    Set MyObj = New CMyClass
    
    Dim objTmp As Object
    Set objTmp = MyObj
    ReleaseObj objTmp
    Set MyObj = objTmp
End Sub

Note that assigning to an object variable a reference to another object involves more than just copying pointers if object types are different. VB makes QueryInterface call to retrieve the interface pointer.

That's why it all works - VB actually sends temporary variable of Object type as an argument, and after returning assigns the return value back to local variable.

So far all this works OK, and MyObj variable will be set to Nothing at the end of the method. But suppose ReleaseObj method does not return normally, but raises error at the end. Then the operator that re-assigns returned value to MyObj is not executed! The main result is the following: ByRef Object parameters don't behave like ByRef if method raises error.

Finally let's return to our original problem: is it possible to use ByRef DbObject parameter of ErrorIn to set caller variable method to Nothing. As you see now, the answer is NO, because ErrorIn re-raises errors. To change the caller's variable method MUST return normally - this is what ErrRlsObjs is doing, and it successfully clears all variables provided as arguments. Connection object is preserved inside HuntERR and carried till ErrorIn can make use of it and finally clear it.
Contents

A3. Version 3.0 New Features

  1. Preserving Original Error Number. ErrorIn in v3.0 doesn't use ERR_STACK_TRACE reserved error number for stack unwinding, but re-raises the original error number. Stack unwinding (error propagation) is identified by reserved value "HuntERR.ErrorIn" of Err.Source property. Re-raising original error number makes it easier to catch and process specific errors in application code.

  2. API Errors Support. ErrorIn automatically adds API error number and description to error report if error occurs in API call. API error number is returned by GetLastError API function (Err.LastDLLError property in VB). If this number is not zero ErrorIn retrieves error description using FormatMessage API function, and adds this information to error report.

  3. System Exceptions Handling. HuntERR is now able to handle severe application faults like "Access Violation". These faults (system exceptions) normally result in application crash, even with error handlers in place. Now your application can use HuntERR to catch this fault in custom exception handler, and then handle it as any other "standard" VB error. Additionally, ErrorIn automatically restores default system handler if custom exception handler was set by application.

  4. Extensibility. HuntERR now makes it possible for you to add support for custom COM Servers that you use in your application. You provide extractor class that knows how to extract error information from custom object, and HuntERR includes this information into error report, the same way it does this with ADO objects.

  5. MSXML Library Support. This feature is implemented as HuntERR extension. We provide ujEEDomDoc.cls class that is used as error extractor for MSXML DOMDocument object.

  6. VB6 Add-In. HuntERR provides VB6 Add-In that makes adding error handler to new method a one-click task.

  7. Migrating existing code. HuntERR Add-In provides a command allowing to add HuntERR -style error handlers to all methods in a program module.

  8. Line Numbers Support. Added support for line numbers - undocumented feature in VB, artifact from very old times but still supported in VB 6. If troubled operator has line number, it will be shown after method signature in trace block. In some situations line numbering may be the easiest solution for identifying the failed operator.

  9. Conditional compilation. HuntERR uses conditional compilation feature of VB to adjust source code to specific environment, like use/not use COM+. Enumerations ENUM_ERRMAP and ENUM_ERROR_ACTION are not commented and by default included into compiled code, so HuntERR30.Bas is ready for use right a way.

  10. Provision for concurrent access to error file. ErrSaveToFile function that appends error report to text file now makes provision for the situation when several components are trying to open it concurrently. If it fails to open error file for the first time it will keep trying for 100 milliseconds before giving up.

  11. Automatically logging notification to event log when failed to save error report to database. If ErrSaveToDB function fails to save error report to database it automatically logs failure notification message to event log.

  12. Improved Error Report Layout. The error report layout is changed to more compact and readable format.

  13. ADO Library Is Not Required anymore. All ADO objects in HuntERR code are declared using Object VB type. As a result, code is compilable in applications that don't use ADO, and thus don't reference ADO libraries in project file. The same is true for COM+ and IIS-related libraries and objects.

  14. General library's public interface improvements . Several unimportant and less relevant functions are removed. Some other useful functions are added.

  15. Structure of database table for error logging is simplified.

  16. Various internal code improvements.

  17. Commented list of public members is included at the beginning of HuntERR30.Bas module for easier references.

  18. HuntERR Reference. We provide HuntERR Reference where you can find detailed information about all public methods in the library.

Contents

A4. Version 3.1 New Features

  1. ErrorIn does not release system exception handler automatically. We recommend to set this handler once for the lifetime of the application.
  2. New flag EA_CONN_CLOSE; if flag is set in ErrorAction parameter ErrorIn automatically closes the connection.
  3. ErrorIn reports the result of attempts to abort the transaction and to close the connection.
  4. ErrorIn automatically reports connection string information.
  5. ADO Library Version is included into error report automatically.
  6. Parameter values longer than 40 chars, or containing CRLF are shown on separate lines in error report.
  7. Special formatting is automatically applied to XML strings, so that they are shown in error report with line breaks and proper indentation
  8. ErrorIn automatically detects loss of error information and puts a message into error description
  9. Check Sub functionality extensions: parameterized description, messages accumulation, and automatic retrieval of messages from application-provided MessageSource object.
  10. EA_WEBINFO flag is included into EA_DEFAULT flag set, so ErrorIn now automatically reports WEB-related information.
  11. Closing open files functionality.
  12. Releasing objects functionality.
  13. Stopping on error in IDE functionality.
  14. Error Logging to Oracle database.
  15. HuntERR predefines several exception numbers and ErrorAction constants for your convenience.

Note: Error Recovery functionality shown in 3.1 Preview version had been dropped in 3.1 Release.
Contents


Copyright URFIN JUS (www.urfinjus.net) , 2001-2002. All rights reserved.