How to Raise Errors


You probably already know the tedious techniques required to raise your own errors. It’s covered in the manuals, so I’ll be brief. Let’s say we wanted to
create and raise the bozo error from the GUtility module of the VBCore component. We might do it like this:

Err.Raise vbObjectError + 22000, “VBCore.Utility”, “You are a bozo”

You’re supposed to raise errors by adding the mysterious vbObjectError constant to your error number. But what exactly is vbObjectError? You can get a good idea by checking its hexadecimal value: &H80040000. To programmers who have worked with COM in other languages, this looks a lot like the status bits of an HRESULT. The 8 sets the severity bit, indicating that it is an error. The 4 sets the facility code to a standard value that C programmers know as FA­CI­LITY_ITF (ITF is an abbreviation for interface). There are other facility codes for errors from Windows, Win32, and some other sources, but since everything made public by Visual Basic is an interface, there’s no need to get creative.


The key point is that you’re combining the bits of your error code with the bits of a severity code and a facility code. Normally, you combine or test bits with logical operators, not arithmetic operators. That’s why I think it’s more accurate to code your errors like this:

Err.Raise 22000 Or vbObjectError, “VBCore.Utility”, “You are a bozo”

So what happens if, ignoring all warnings in the documentation, you simply raise your errors directly without vbObjectError?

Err.Raise 22001, “MyProject.MyModule”, “I am a bozo”

Will your program suddenly disappear in a puff of smoke? Will the error police break down your door in the middle of the night? No, but behind your back Visual Basic will set the facility code
of error to FACILITY_CONTROL. In other words, your code will actually do this:

Err.Raise 22001 Or &H800A000,...

COM documentation says that FACILITY_CONTROL errors should be raised for control-related errors. I’m not sure, but I think they mean the system code that creates controls rather than the code in controls. As always, ignore documented instructions at your own risk.


For clients, the only thing that matters about these bits is that you have to get rid of them to get an intelligible error message. You can do that easily by masking out the high bits (the bit flags) of the error, leaving only the low bits containing the code. I use the following functions for masking out the irrelevant bits or masking in the relevant ones:

Function BasicError(ByVal e As Long) As Long
BasicError = e And &HFFFF&
End Function

Function COMError(e As Long) As Long
COMError = e Or vbObjectError
End Function

So if you raise the bozo error shown earlier, the client can get back the error number you set (22000 in this case) like this:

e = VBError(Err.Number)

You’re also supposed to raise errors with integer values greater than 512 in order to avoid conflicts with Visual Basic’s errors. That leaves me and all other component developers the range of 512 to 32768. I hereby claim the number 13000 as my unlucky number and order other component developers to cease and desist from using this number as the base for their error numbers. In exchange, I promise not to use up any more precious numbers than I have to. OK? Well,
I know it’s a hopeless request. It’s inevitable that component error numbers
will overlap occasionally. If you use two components with the same numbers, you’ll have to figure out who’s who from the context.


Notice also that you’re supposed to return the source in a project.module format. If you don’t fill in this optional parameter, Visual Basic will provide the project but not the module.


If you don’t provide an error message, one will be provided for you, but you probably won’t like it. This is pretty mechanical stuff, and we ought to be able to automate at least part of it. The module ERRORS.CLS provides procedures that allow you to shorten error raising code to this:

ErrRaise eeBozo

My system depends on convention. Each module gets an Enum for all the error messages it can raise. Here’s the one for UTILITY.CLS (and it’s the same in UTILITY.BAS):

Public Enum EErrorUtility
eeBaseUtility = 13000 ‘ Utility
eeNoMousePointer ‘ HourGlass: Object doesn’t have mouse pointer
eeNoTrueOption ‘ GetOption: None of the options are True
eeNotOptionArray ‘ GetOption: Not control array of OptionButton
End Enum

Here’s the procedure that makes it work in the ERRORS.CLS module of VBCore:

#If fComponent Then
Sub ErrRaise(e As Long)
MErrors.ErrRaise e
End Sub
#End If

Wait a minute! The global class version of ErrRaise is just delegating to a standard module version using the technique described in “Creating Global Procedures.” Delegation works better here because it avoids the requirement to qualify every single call with a global object. There’s no instance data in the error functions, and they aren’t called in situations where performance is critical. Most calls to ErrRaise will be from internal modules anyway. Therefore, the real work is done in the delegated standard module ERRORS.BAS:

#If fComponent Then
Sub ErrRaise(e As Long)
Dim sText As String, sSource As String
If e > 1000 Then
sSource = App.ExeName & “.” & LoadResString((e \ 10) * 10)
sText = LoadResString(e)
Err.Raise e, sSource, sText
Else
‘ Raise standard Visual Basic error
Err.Raise e, sSource
End If
‘ Challenge: Enhance to use help files
End Sub
#End If

The component side of the code (where fComponent is True) loads error message strings out of the VBCore resource file. Here’s part of the VBCORE.RC file from which VBCORE.RES is built:

// VBCore.RC - Resource script for VBCore

STRINGTABLE
BEGIN
#if !defined(idLang)
13000 “Utility”
13001 “HourGlass: Object doesn’t have mouse pointer”
13002 “GetOption: None of the options are True”
13003 “GetOption: Not a control array of OptionButton”
§
// #elif idLang == idOtherLang
// Add other languages here
#endif
END

Each module gets slots for ten strings. The first string is reserved for the name of the module, which will be returned as part of the source. If you really need more than nine errors per module (rare in my experience), you’ll need to duplicate the module string for each error block.


All you have to do to make VBCore work in Bulgarian is translate the error messages to Bulgarian and add them to VBCORE.RES. I’ll explain more about resources later.


One problem—I can’t make the same ErrRaise procedure work when ERRORS­.BAS is used as a stand-alone module because I don’t know how the client wants to handle error strings. If users of UTILITY.BAS and other standard modules want to use resource strings, that’s up to them. They can write their own error resource files and their own ErrRaise function. The best I can do is provide private ErrRaise functions in the standard modules so that they’ll at least work in English as the default. Here, for example, is the one from UTILITY.BAS:

#If fComponent = 0 Then
Private Sub ErrRaise(e As Long)
Dim sText As String, sSource As String
If e > 1000 Then
sSource = App.EXEName & “.Utility”
Select Case e
Case eeBaseUtility
sText = “Utility”
Case eeNoMousePointer
sText = “HourGlass: Object doesn’t have mouse pointer”
Case eeNoTrueOption
sText = “GetOption: None of the options are True”
Case eeNotOptionArray
sText = “GetOption: Argument is not a control array” & _
“of OptionButtons”
End Select
Err.Raise COMError(e), sSource, sText
Else
‘ Raise standard Visual Basic error
Err.Raise e, sSource
End If
End Sub
#End If

Maintaining the error Enums and error strings is a nuisance. You have to update each error in three different places—the error Enum, the standard module Select Case block, and the VBCore resource file. This kind of mechanical work is unfit for human beings. If you’re going to use errors in this format frequently, consider writing a wizard to automate the process. I considered it, but unfortunately for you it never reached the top of my project stack.