4
\$\begingroup\$

HRESULTs are the common way for Microsoft technologies like COM and Win32 to return either an error code or messages that mean a specific type of success. They are usually returned as the result of a function. They have various pieces of information encoded in their bits, such as the OS facility that erred, the error code, etc. Since I am not someone who can intuitively decode bits in my head, I wanted to have a class to encapsulate the logic so I can call it whenever I need to know what an HRESULT really says.

References:

https://msdn.microsoft.com/en-us/library/cc231198.aspx

https://blogs.msdn.microsoft.com/heaths/2005/07/21/deciphering-an-hresult/

https://blogs.msdn.microsoft.com/eldar/2007/04/03/a-lot-of-hresult-codes/

Public Structure HResult
    Implements IEquatable(Of HResult), IEquatable(Of Integer)

Public Enum SeverityCode
    Success = 0           'STATUS_SEVERITY_SUCCESS
    Informational = &H1   'STATUS_SEVERITY_INFORMATIONAL
    Warning = &H2         'STATUS_SEVERITY_WARNING
    [Error] = &H3         'STATUS_SEVERITY_ERROR
End Enum


Private Enum BitMasks As Integer
    IsError = &H80000000
    SeverityShifted = &H3
    Customer = &H20000000
    ISNTError = &H10000000
    Facility = &HFFF0000
    Code = &HFFFF

End Enum


Public Sub New(hresult As Integer)
    _Hresult = hresult
End Sub



Public ReadOnly Property Hresult As Integer

Public ReadOnly Property ErrorCode As Integer
    Get
        Return Hresult And BitMasks.Code
    End Get
End Property

Public ReadOnly Property IsError As Boolean
    Get
        Return (Hresult And BitMasks.IsError) <> 0
    End Get
End Property

Public ReadOnly Property IsNTError As Boolean
    Get
        Return (Hresult And BitMasks.ISNTError) <> 0
    End Get
End Property

Public ReadOnly Property Severity As SeverityCode
    Get
        Return DirectCast((Hresult >> 30) And BitMasks.SeverityShifted, SeverityCode)
    End Get
End Property

Public ReadOnly Property MicrosoftDefined As Boolean
    Get
        Return (Hresult And BitMasks.Customer) = 0
    End Get
End Property

Public ReadOnly Property Facility As Integer
    Get
        Return (Hresult And BitMasks.Facility) >> 16
    End Get
End Property


Public Shared Widening Operator CType(v As Integer) As HResult
    Return v.AsHResult
End Operator

Public Shared Widening Operator CType(v As HResult) As Integer
    Return v.Hresult
End Operator


#Region "IEquatable"

Public Overloads Function Equals(other As HResult) As Boolean Implements IEquatable(Of HResult).Equals
    Return (Hresult = other.Hresult)
End Function

Public Overloads Function Equals(other As Integer) As Boolean Implements IEquatable(Of Integer).Equals
    Return (Hresult = other)
End Function

Public Overrides Function Equals(obj As Object) As Boolean
    If obj Is Nothing Then Return False
    If TypeOf obj Is HResult Then Return Equals(DirectCast(obj, HResult))
    If TypeOf obj Is Integer Then Return Equals(DirectCast(obj, Integer))
    Return False
End Function

Public Overrides Function GetHashCode() As Integer
    Return Hresult.GetHashCode
End Function

Public Shared Operator =(itm1 As HResult, itm2 As HResult) As Boolean
    If IsNothing(itm1) And IsNothing(itm2) Then Return True
    If IsNothing(itm1) Then
        Return False
    Else
        Return itm1.Equals(itm2)
    End If
End Operator

Public Shared Operator <>(itm1 As HResult, itm2 As HResult) As Boolean
    Return Not (itm1 = itm2)
End Operator

#End Region

End Structure

Public Module HResultExtensions

<Runtime.CompilerServices.Extension()>
Public Function AsHResult(hresult As Integer) As HResult
    Return New HResult(hresult)
End Function

<Runtime.CompilerServices.Extension()>
Public Function AsHResult(hresult As [Enum]) As HResult
    If [Enum].GetUnderlyingType(hresult.GetType) <> GetType(Integer) Then Throw New ArgumentException("Argument provided does not represent a 32 bit integer.")
    Return New HResult(Convert.ToInt32(hresult))
End Function

End Module

And also the tests that I used to check for correctness:

<TestClass>
Public Class TestHResult

<TestMethod()> Public Sub Test_ErrorCode_NullPointer()

    Dim ex = New ArgumentNullException
    Dim HR = ex.HResult.AsHResult
    Assert.IsTrue(HR.ErrorCode = 16387 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Warning AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_NULL
                  )
End Sub

<TestMethod()> Public Sub Test_ErrorCode_Argument()

    Dim ex = New ArgumentException
    Dim HR = ex.HResult.AsHResult

    Assert.IsTrue(HR.ErrorCode = 87 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Warning AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_WIN32
                  )
End Sub

<TestMethod()> Public Sub Test_ErrorCode_FileNotFound()

    Dim ex = New System.IO.FileNotFoundException
    Dim HR = ex.HResult.AsHResult
    Assert.IsTrue(HR.ErrorCode = 2 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Warning AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_WIN32
                  )
End Sub

<TestMethod()> Public Sub Test_ErrorCode_Overflow()

    Dim ex = New OverflowException
    Dim HR = ex.HResult.AsHResult
    Assert.IsTrue(HR.ErrorCode = 5398 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Warning AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_URT
                  )
End Sub

<TestMethod()> Public Sub Test_ErrorCode_Auditing()

    Dim HR = KnownHResults.ERROR_AUDITING_DISABLED.AsHResult
    Assert.IsTrue(HR.ErrorCode = 1 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Error AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_SSPI
                  )
End Sub

<TestMethod()> Public Sub Test_Informational()

    Dim HR = KnownHResults.TEST_INFORMATIONAL.AsHResult
    Assert.IsTrue(HR.ErrorCode = 0 AndAlso
                  HR.IsError = False AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Informational AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_NULL
                  )
End Sub

<TestMethod()> Public Sub Test_Customer()

    Dim HR = KnownHResults.TEST_CUSTOMER.AsHResult
    Assert.IsTrue(HR.ErrorCode = 0 AndAlso
                  HR.IsError = True AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Warning AndAlso
                  HR.MicrosoftDefined = False AndAlso
                  HR.Facility = KnownFacilities.FACILITY_NULL
                  )
End Sub

<TestMethod()> Public Sub Test_Success()
    Dim HR = KnownHResults.S_OK.AsHResult
    Assert.IsTrue(HR.ErrorCode = 0 AndAlso
                  HR.IsError = False AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Success AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_NULL
                  )
End Sub

<TestMethod()> Public Sub Test_False()
    Dim HR = KnownHResults.S_FALSE.AsHResult
    Assert.IsTrue(HR.ErrorCode = 1 AndAlso
                  HR.IsError = False AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Success AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_NULL
                  )
End Sub

<TestMethod()> Public Sub Test_OLE_First()
    Dim HR = KnownHResults.OLE_S_FIRST.AsHResult
    Assert.IsTrue(HR.ErrorCode = 0 AndAlso
                  HR.IsError = False AndAlso
                  HR.IsNTError = False AndAlso
                  HR.Severity = HResult.SeverityCode.Success AndAlso
                  HR.MicrosoftDefined = True AndAlso
                  HR.Facility = KnownFacilities.FACILITY_ITF
                  )
End Sub


Enum KnownHResults As Integer
    S_OK = 0                                '&H00000000
    S_FALSE = 1                             '&H00000001
    OLE_S_FIRST = 262144                    '&H00040000
    COR_E_NULLREFERENCE = -2147467261       '&H80004003
    E_INVALIDARG = -2147024809              '&H80070057 
    ERROR_AUDITING_DISABLED = -1073151999   '&HC0090001
    TEST_INFORMATIONAL = 1073741824         '&H40000000
    TEST_CUSTOMER = -1610612736             '&HA0000000
End Enum

Enum KnownFacilities As Integer
    FACILITY_NULL = 0
    FACILITY_ITF = 4
    FACILITY_WIN32 = 7
    FACILITY_SSPI = 9
    FACILITY_URT = 19
End Enum


End Class
\$\endgroup\$
1
\$\begingroup\$

Intro

If you're new to VB.NET programming then I advice you to read up on "compile settings" and especially the strict option as this defaults to Off. Anything other than On is a strong indication that the author is a novice, dealing with legacy code or both.

Dim i As Integer = 2.3
Console.WriteLine(i = 2.3)
'yields false as 2 <> 2.3

FTR: I'd like to emphasize that I don't see any evidence of the strict compile option being set to Off, but it's such a common mistake that it seems worth mentioning.

The next thing you should do is to learn the difference between value type and reference type. It's correct (in this particular case) to define a structure, but treating it like a value type is not.

Dim hr As HResult = Nothing
Console.WriteLine(IsNothing(hr))
'yields false as the default value of HResult is not a null reference

You should also know that many of the types/members in the Microsoft.VisualBasic.dll assembly were added to provide backward compatibility for legacy code. To truly get to know .NET, you should uncheck Microsoft.VisualBasic from the "globally imported namespace" list and only import when really needed (in the code file).

Code

1)

Implements IEquatable(Of HResult), IEquatable(Of Integer)

Public Overloads Function Equals(other As Integer) As Boolean Implements IEquatable(Of Integer).Equals
    Return (Hresult = other)
End Function

AFAICT there's no need to implement the IEquatable(Of Integer) interface as this will end in an implicit conversion. You can remove the implementation and still invoke Equals with an integer because of the widening operator:

Dim hr = New HResult(0)
Dim i = 0I
Dim eq = hr.Equals(i)

2)

Public ReadOnly Property Hresult As Integer

Letting members have the same name as it's enclosing type is a bad practice. A common solution in similar cases is to name the member Value if it's a (public) property or _value if it's a (private) field.

Public ReadOnly Property Value As Integer

3)

Public Shared Widening Operator CType(v As Integer) As HResult
    Return v.AsHResult
End Operator

Public Shared Widening Operator CType(v As HResult) As Integer
    Return v.Hresult
End Operator

A short parameter/member name doesn't make your code execute faster. But it do make it less readable. I suggest the name value. Also, I've no idea why you need to invoke an extension method when all you need is:

New HResult(v)

Public Shared Widening Operator CType(value As Integer) As HResult
    Return New HResult(value)
End Operator

Public Shared Widening Operator CType(result As HResult) As Integer
    Return result.Value
End Operator

4)

Public Shared Operator =(itm1 As HResult, itm2 As HResult) As Boolean
    If IsNothing(itm1) And IsNothing(itm2) Then Return True
    If IsNothing(itm1) Then
        Return False
    Else
        Return itm1.Equals(itm2)
    End If
End Operator

Public Shared Operator <>(itm1 As HResult, itm2 As HResult) As Boolean
    Return Not (itm1 = itm2)
End Operator

Public Overrides Function Equals(obj As Object) As Boolean
    If obj Is Nothing Then Return False
    If TypeOf obj Is HResult Then Return Equals(DirectCast(obj, HResult))
    If TypeOf obj Is Integer Then Return Equals(DirectCast(obj, Integer))
    Return False
End Function

Public Overloads Function Equals(other As HResult) As Boolean Implements IEquatable(Of HResult).Equals
    Return (Hresult = other.Hresult)
End Function

You should avoid invokingEqual from inside the equality operator and visa versa. I will not repeat what I've said earlier so I go straight to this: Your comparison is to complex. All you need is to compare the original hresult.

Public Shared Operator =(left As HResult, right As HResult) As Boolean
    Return (left.Value = right.Value)
End Operator

Public Shared Operator <>(left As HResult, right As HResult) As Boolean
    Return (Not left = right)
End Operator

Public Overrides Function Equals(obj As Object) As Boolean
    Return ((Not obj Is Nothing) AndAlso Equals(DirectCast(obj, HResult)))
End Function

Public Overloads Function Equals(result As HResult) As Boolean Implements IEquatable(Of HResult).Equals
    Return (Value = result.Value)
End Function

5)

Public Overrides Function GetHashCode() As Integer
    Return Hresult.GetHashCode
End Function

You don't need to invoke GetHashCode on the integer as all it do is to return itself. Same story for "a string value".ToString().

Public Overrides Function GetHashCode() As Integer
    Return Value
End Function
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.