Breaking Down Windows Error Codes
Did you ever wonder what those hexadecimal error codes were that you see from time to time in VBA, such as 0x80070005
?
The structure of those codes is defined in Windows Protocol [MS-ERREF]: Windows Error Codes.
Error Code Structure
The most informative part of the document is section 2.1, which describes the structure of an HRESULT code.
The HRESULT numbering space has the following internal structure. Any protocol that uses NTSTATUS values on the wire is responsible for stating the order in which the bytes are placed on the wire.
S (1 bit): Severity. If set, indicates a failure result. If clear, indicates a success result.
R (1 bit): Reserved. If the N bit is clear, this bit MUST be set to 0. If the N bit is set, this bit is defined by the NTSTATUS numbering space (as specified in section 2.3).
C (1 bit): Customer. This bit specifies if the value is customer-defined or Microsoft-defined. The bit is set for customer-defined values and clear for Microsoft-defined values.<1>
N (1 bit): If set, indicates that the error code is an NTSTATUS value (as specified in section 2.3), except that this bit is set.
X (1 bit): Reserved. SHOULD be set to 0. <2>
Facility (11 bits): An indicator of the source of the error. New facilities are occasionally added by Microsoft.
Code (2 bytes): The remainder of the error code.
Several error codes–encoded in the 2-byte "Code" section at the end of the HRESULT–are defined in Section 2.2 Win32 Error Codes.
Common Facilities
The above link to the [MS-ERREF] protocol specification includes a table of facility codes along with brief descriptions of what they represent.
F0r a less exhaustive but more descriptive list of facilities, check out the table in Structure of COM Error Codes:
HRESULT: A Misleading Name
In the world of COM, a leading "H" in a variable name usually indicates a "handle" (i.e., memory address) to an object.
That's not the case with an HRESULT, though. An HRESULT is simply a coded number–it is NOT a handle to an object.
Why the leading "H" then?
According to Microsoft's Raymond Chen, the original COM design called for returning objects (rather than long integers) as the standard return from a function or method call. This allowed doing some very interesting things, but ultimately it was determined to be overkill. For simplicity and performance, the object became a simple 32-bit integer.
The original name stuck, leaving us with a term that literally defies convention.
How Does vbObjectError Fit In?
In the documentation for the VBA Err.Number property, the Remarks section includes the following note:
When returning a user-defined error from an object, set Err.Number by adding the number you selected as an error code to the vbObjectError constant.
What exactly is vbObjectError?
Well, first of all, it is a built-in VBA constant with a decimal value of -2147221504:
But what happens if we convert that decimal number to a 32-bit hexadecimal value?
Thus, the vbObjectError value is simply a bit mask with the following information encoded:
- Severity bit: 1 (indicating an error)
- Facility code: 4 (indicating the error originated from COM/OLE interface management)
Let's Decode Our Original Example: 0x80070005
Using the "Programmer" mode of the built-in Windows calculator is a great way to view numbers as decimal, hexadecimal, or binary all at once:
Note that the hexadecimal 8
converts to a binary 1000
.
This leaves us with the following values for the first five entries:
- S: 1 (indicates a severity of "Failure")
- R: 0 (set to 0 because N [below] is also 0)
- C: 0 (this is not a Customer code)
- N: 0 (this is not an NTSTATUS)
- X: 0
- Facility: 7 (FACILITY_WIN32)
Used to provide a means of handling error codes from functions in the Windows API as an HRESULT. Error codes in 16-bit OLE that duplicated system error codes have also been changed to FACILITY_WIN32. - Code: 5 (ERROR_ACCESS_DENIED)
Access is denied.