Err.Number and Exception.HResult the same thing?

TrtnJohn

Member
Joined
Feb 8, 2006
Messages
19
Programming Experience
10+
I've recently converted some VB 6 code over that used the old non-structured exception handling. I also have some .NET classes that raise custom exception classes. When the code was VB6 I used to expose my .NET classes using COM interop and errors raised from .NET classes were being caught correctly in VB6. Now that everything is .NET the old non-structured exception handling is still there in the converted classes. But now the Err.Number is not reflecting the HResult of the error raised. I wrote a simple little Windows Application form with 2 buttons to simulate the problem. Here is the code snippet:
VB.NET:
  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Button1.Click        
      On Error GoTo ErrHandler        
      Err.Description = "Test Error"
      Err.Raise(-2147215379)        
      Exit Sub
 
  ErrHandler:
        MsgBox(Err.Number & " : " & Err.Description)
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Try
            Throw New TestException
        Catch ex As Exception
            MsgBox(Err.Number & " : " & Err.Description)
        End Try
    End Sub
 
    Private Class TestException
        Inherits ApplicationException
        Public Sub New()
            MyBase.New()
            MyBase.HResult = -2147215379
        End Sub 
 
        Public Overrides ReadOnly Property Message() As String
            Get
                Return "Test Exception"
            End Get
        End Property
    End Class

If you click Button1 you get the following message:
-2147215379 : Test Error

If you click Button2 you get the following message:
5 : Test Exception
 
Have you read exactly what the HResult property represents?
HRESULT is a 32-bit value, divided into three different fields: a severity code, a facility code, and an error code. The severity code indicates whether the return value represents information, warning, or error. The facility code identifies the area of the system responsible for the error. The error code is a unique number that is assigned to represent the exception. Each exception is mapped to a distinct HRESULT. When managed code throws an exception, the runtime passes the HRESULT to the COM client. When unmanaged code returns an error, the HRESULT is converted to an exception, which is then thrown by the runtime.
I've never heard mention of anyone using the HResult property in a Pure .NET environment. What exactly does using it do for you now? SEH seems to negate any need for it.
 
jmcilhinney said:
Have you read exactly what the HResult property represents?I've never heard mention of anyone using the HResult property in a Pure .NET environment. What exactly does using it do for you now? SEH seems to negate any need for it.

The only reason it is an issue is because I previously had a mix of VB.NET and VB6 code. In the old environment my VB.NET code was raising exceptions via COM interop to the VB6 code. (Using HResult). I recently converted all of the old VB6 code to .NET. In my new environment I understand that the old HResult is not necessary because all of the code is now .NET and exceptions are now typed. But, the problem is I have thousands of lines of converted VB6 code that used the old un-structured exception handling and in many cases was checking HResult directly using Err.Number. Since the converted code is now VB.NET, I don't see how my .NET custom exceptions can effect what Err.Number returns. I don't quite understand this statement in the description of HResult:

"When unmanaged code returns an error, the HRESULT is converted to an exception, which is then thrown by the runtime. "
 
When you call a method in unmanaged code, which means COM, and it returns an error, .NET creates a new exception object, presumably with that HRESULT in its HResult property, and throws it like it would an exception from managed code. From the first line of that quote, I'd guess that HResult contains the error number but other information as well. You just need to seperate the error code from the severity code and facility code by zeroing out the appropriate bits.
 
jmcilhinney said:
When you call a method in unmanaged code, which means COM, and it returns an error, .NET creates a new exception object, presumably with that HRESULT in its HResult property, and throws it like it would an exception from managed code. From the first line of that quote, I'd guess that HResult contains the error number but other information as well. You just need to seperate the error code from the severity code and facility code by zeroing out the appropriate bits.

I understand that. In the old mixed version of my code I was actually going the other way. My managed code was raising exceptions to the VB6 classes. (Through COM Interop). That is why I was setting the HResult in my custom exceptions so the VB6 code would get the correct Err.Number. (This worked fine). But now, since I converted my old VB6 code to VB.NET, the HResult seems meaningless because the exceptions are not going through the COM Interop anymore. Unfortunately I have thousands of lines of converted code that use the unstructured exception handling. It may take months to manually convert them all to Try..Catch blocks :(
 
I still am struggling a bit with this issue. Does anyone know of any detailed documents that describe how the HResult property of the Exception class is used? So far, I can only deduce that it is used for COM Interop. Any help would be greatly appreciated!
 
This "Exception Management Architecture Guide" document looks to hold the answers to your questions:
link update: Handling and Throwing Exceptions

There is a section about interoperability and link to table of HRESULT to Exception mappings.

The baseline about exceptions is: Do not use exceptions as a means to provide your intended functionality. Do not use exceptions to control the normal flow of execution through your code. There is nothing exceptional about a normal error :)
 
Thanks JohnH. This link confirms what I already know. (I am screwed). Unfortunately, it looks like I need to redo my exception processing after my migration to .NET. The HResult is only used by COM interop and can no longer be relied upon when catching my custom exceptions using On Error Goto ErrorHandler unstructured handling. It seems the only way to give an error code to a structured exception is to set the Number property of the Err object before you call Err.Raise. I just did a search through all of my converted code for "On Error" and had 1407 hits in 475 files. Aaaaarg!
 
I've spent the last day in undestanding differences between throwing Errors and using Err.Raise
... I think this would be helpful to TrtnJohn (even if very late ... 4 years ;-)


It is right to set MyBase.HResult if you want to supply an Error-Number.
But bear in mind that in ".Net" Exceptions are meant to differ by their type (not their HResult). So what we do is only for backwards-compatability.

If you set the HResult one would expect to retrieve this value when reading Err.Number (as TrtnJohn tried).
This is wrong since the ErrObject tries to interpret the Exceptions-Type (not the Exceptions.HResult).

If you take some time for disassembling the ErrObject (using Red-Gate's .NET-Reflector) you will find the "Private Function MapExceptionToNumber" (under Microsoft.VisualBasic.ErrObject).
This function is involved when reading Err.Number
If Exception.GetType is not known by this function, you will get Err.Number=5.

So: How do we retrieve HResult?

  1. When using an own exception (like TrtnJohn wanted to do), we have to do some tricks since the Error-Object will not help in this case.
    (Tricks means: Use .NET-Reflections to look inside the Exception and read HReslut):

    VB.NET:
        Public Class OwnException
            Inherits Exception
            Public Sub New(ByVal message As String)
                MyBase.New(message)
                MyBase.HResult = vbObjectError + 22000
            End Sub
        End Class
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Button1.Click
            Try
                Throw New OwnException("Error-Description")
    
            Catch ex As Exception
                Dim flags As System.Reflection.BindingFlags = (System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
                Dim Field As System.Reflection.FieldInfo = GetType(System.Exception).GetField("_HResult", flags)
    
                MsgBox("HResult:" & Field.GetValue(ex) & " / Err.Number:" & Err.Number & " " & ex.Message)
            End Try
        End Sub
    The MsgBox will state the expected HResult and it will show that Err.Number is 5 (as mentioned above)


  2. (Best Practice) Use "COMException" for throwing.
    The type "COMException"s is handeled in a differnet manner by "ErrObject.MapExceptionToNumber".
    If you would take a second look on this function using .NET-Reflector, you will see that the ErrObject will not assing an own Error-Number (like #5) when encountering the Exception-Type "COMException". In this case it will look for HResult ... That's what TrtnJohn was looking for (I hope).
    Just try the above code with "COMException":

    VB.NET:
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Button1.Click
            Try
                Throw New System.Runtime.InteropServices.COMException("Error-Description", vbObjectError + 22000)
    
            Catch ex As Exception
                Dim flags As System.Reflection.BindingFlags = (System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
                Dim Field As System.Reflection.FieldInfo = GetType(System.Exception).GetField("_HResult", flags)
    
                MsgBox("HResult:" & Field.GetValue(ex) & " / Err.Number:" & Err.Number & " " & ex.Message)
            End Try
        End Sub
    The MsgBox will state the HResult in both ways (using Err.Number & using Relfections on the thrown Exception)
 
Back
Top