Results 1 to 4 of 4

Thread: (VB6) Err.Raise using HRESULT_FROM_WIN32 and vbObjectError

  1. #1

    Thread Starter
    Junior Member
    Join Date
    Dec 2017
    Posts
    29

    (VB6) Err.Raise using HRESULT_FROM_WIN32 and vbObjectError

    Convert Win32 error using vbObjectError and HRESULT_FROM_WIN32

    HRESULT_FROM_WIN32 is the name of a macro used in C to convert a WIN32 error into an HRESULT error (although since XP it has been replaced by an inline function).

    The Win32 errors are easily converted into HRESULT errors. Win32 errors are 16 bit values, whilst HRESULT errors are 32 bit values. The additional 16 bits define the Facility and the Severity of the error. HRESULT encapsulates Win32 errors when the Facility code is set to FACILITYT_WIN32 and severity set to SEVERITY_ERROR and the low 16 bits contain the Win32 error number.

    vbObjectError is the exact value of an HRESULT with the facility code set to FACILITY_ITF.

    Visual Basic will recognize a returning HRESULT from a function and process it automatically using Err.Raise. When you raise an error in Visual Basic with "Err.Raise vbObjectError Or nMyError," you are unknowingly using an HRESULT with a status code of nMyError.

    It is a small step to convert from a FACILITY_ITF used in vbObjectError to a FACILITY_WIN32 required to encapsulate Win32 API errors. Win32 errors are then recognised by Visual Basic as an HRESULT and the Err.Description includes the description of the Win32 error.

    The conversion is to take the 16 bit Win32 errors and combine this with FACILITY_WIN32 and SEVERITY_ERROR. It is not necessary to use the vbObjectError but because of the HRESULT connection, it illustrates how Visual Basic can use HRESULT errors.

    The constant vbObjectError is derived from the FACILITY_ITF code together with the Error severity code.
    vbObjectError = FACILITY_ITF or Severity_Error
    = (4 << 16) or 0x80000000


    This vbObjectError can be used to create a new constant vbWin32Error by adding an adjustment for the different FACILITY codes FACILITY_ITF and FACILITYT_WIN32.
    vbWin32Error = vbObjectError + ((FACILITY_WIN32 - FACILITY_ITF) * (2 ^ 16))

    The Win32API error can then be converted to an HRESULT error by adding this constant vbWin32Error to the error, and the error can be trapped using the Visual Basic Err.Raise method, and the error description can be accessed using Err.Description.

    The resulting Err.Description identifies the error as a Win32 HRESULT by starting the error description with "Automation error"

    The function below HRESULT_FROM_WIN32 converts a Win32 Error into an HRESULT, and the function WIN32_FROM_HRESULT converts HRESULT error back into Win32 Error.

    Code:
    Private Const FACILITY_ITF = 4
    Private Const FACILITY_WIN32 = 7
    Private Const vbWin32Error = vbObjectError + ((FACILITY_WIN32 - FACILITY_ITF) * (2 ^ 16))
    
    Public Function HRESULT_FROM_WIN32(LastDllError As Long) As Long
        If (((LastDllError And (Not &HFFFF&)) = 0) and (LastDllError <>0)) Then
            HRESULT_FROM_WIN32 = (LastDllError Or vbWin32Error)
        Else
            HRESULT_FROM_WIN32 = LastDllError
        End If
    End Function
    
    Public Function WIN32_FROM_HRESULT(HRESULT As Long) As Long
        If ((HRESULT And (Not &HFFFF&)) = vbWin32Error) Then
            WIN32_FROM_HRESULT = (HRESULT And &HFFFF&)
        Else
            WIN32_FROM_HRESULT = HRESULT
        End If
    End Function
    These functions can be used to raise a VB error from a Win32 api function, using the err.raise method:

    Code:
    Private Sub UsageExample()
    Dim RetApi As Long
        On Error GoTo EH
    '    RetApi = Win32ApiFunction() ' returns 0 if error, <> 0 if successful
        If RetApi = 0 Then
            Err.Raise HRESULT_FROM_WIN32(Err.LastDllError)
        End If
        Exit Sub
    EH:
        MsgBox "Error Nos : " & WIN32_FROM_HRESULT(Err.Number) _
        & vbCrLf & "Error : " & Err.Description
    End Sub

    References
    • Q189134 HOWTO: Raise an Error in Visual Basic From Your C DLL
    • MSDN OldNewThing blog - How do I convert an HRESULT to a Win32 error code
    • WinError.h
    • Platform SDK: COM - Using Macros for Error Handling
    • MSDN blog - The evolution of HRESULT_FROM_WIN32


    Update July 6th 2018
    Last edited by DavidUK; Jul 6th, 2018 at 06:21 AM.

  2. #2
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: (VB6) Err.Raise using HRESULT_FROM_WIN32 and vbObjectError

    just an FYI, you can simplify your error test by checking if the value is less than zero. (the error bit is the sign bit).
    So my version of HRESULT_FROM_WIN32 looks like this

    Code:
    Public Function HRESULT_FROM_WIN32(ByVal x As Long) As Long
        If (x <= 0) Then
            HRESULT_FROM_WIN32 = x
        Else
            HRESULT_FROM_WIN32 = ((x And &HFFFF&) Or (FACILITY_WIN32 * &H10000) Or &H80000000)
        End If
    End Function
    similarly I use this.

    Code:
    Private Sub ThrowIfWin32Err(ByVal Source As String, _
                                ByVal Win32Err As Long _
                                )
        Dim HResult As Long
    
        If Win32Err Then
            ' HResult = HRESULT_FROM_WIN32(Win32Err)
            HResult = (Win32Err And &HFFFF&) Or &H80070000
            Err.Raise HResult, Source
        End If
    End Sub
    which allows API calls to look like this.

    Code:
                ThrowIfWin32Err FUNC_NAME, _
                RegQueryValueEx(m_hKey, _
                                StrPtr(Name), _
                                0&, _
                                Ref(Kind), _
                                Ref(ValueQWord), _
                                Ref(Size))
    Last edited by DEXWERX; Jul 5th, 2018 at 01:26 PM.

  3. #3

    Thread Starter
    Junior Member
    Join Date
    Dec 2017
    Posts
    29

    Re: (VB6) Err.Raise using HRESULT_FROM_WIN32 and vbObjectError

    Yes I agree this can be done, and I have also used similar simplified code based on the HRESULT_FROM_WIN32 Macro. However there is a flaw with this macro. It assumes the error to be converted is either a 16 bit Win32 error, or an error with the sign bit set. But the LastDllError can have any possible 32 bit value, particularly if the SetLastError function has been used to set this value.

    In the list of references, I included the OldNewThing blog article “How do I convert an HRESULT to a Win32 error code” which raised the question of why did an application incorrectly returned the error "The directory cannot be removed" when this error message was not relevant to the function being processed.

    This question illustrates how using the HRESULT_FROM_WIN32 Macro can create misleading error messages. With the few extra bytes of code to test the high 16 bits of the LastDllError, this particular problem can be isolated, and, more importantly, the original error code is preserved.

    The sign bit test used by the HRESULT_FROM_WIN32 Macro will allow any HRESULT error with the sign bit set to be left unchanged, but the HRESULT value does not have to have the sign bit set. Similarly the NTSTATUS value does not have to have the sign bit set, and the LSTATUS code returned by RegOpenKeyEx is a signed number so if the sign bit was set (unlikely), the remaining bits would be in two’s compliment form. Any of these could be in the LastDllError value producing unexpected results.

    I also wanted to ensure the result of the WIN32_FROM_ HRESULT function would return the original value. With the HRESULT_FROM_WIN32 Macro this is not guaranteed, whilst with the HRESULT_FROM_WIN32 function and the high 16bit test, it will.

    The fundamental problem with the Macro is it is setting the high 16 bits to 0x80000000 without first checking if any of these bits are non-zero value and consequently the WIN32_FROM_ HRESULT function cannot restore the original value because these bits are lost.

    But clearly there is scope to reduce the code after testing to make sure the error code is only used with 16 bit values.

    After reading the OldNewThing blog article “How do I convert an HRESULT to a Win32 error code”, I decided to investigate further how HRESULT supports WIN32 Errors, and also how the Visual Basic ERR function supports both Visual Basic Errors, and vbObjectError errors. Part of the reason for the code I have used is to show how these are all connected, and how the constants used in the HRESULT_FROM_WIN32 Macro can be derived from the WinError.h definitions and from the vbObjectError value.

    For this reason I introduce the vbObjectError constant, not because it is needed, but to illustrate the connection of vbObjectError with HRESULT as this helps explain why the Visual Basic ERR function can process HRESULT errors as well as Visual Basic errors. As HRESULT is a COM standard, and Visual Basic is very dependent on COM, it is not surprising the Visual basic error processing includes HRESULTS.

    The vbObjectError constant has a value which is the same as the FACILITY_ITF. HRESULTS provides this FACILITY for any applications to use for customized errors. Visual Basic has used this FACILITY_ITF for its own set of errors with the HRESULT code in the range 0x0 to 0x1FF, and for user-defined errors in the range 0x200 to 0xFFFF.

    Also you say to test
    if the value is less than zero
    i.e. just testing the sign bit, but your code and the HRESULT_FROM_WIN32 macro also test for zero value. In my example I have unintentionally removed the zero test and this should be added back.

  4. #4
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: (VB6) Err.Raise using HRESULT_FROM_WIN32 and vbObjectError

    Interesting points. I've definitely read that Old New Thing post, but somehow at least in for the last 20 years, I never saw the need to "fix" it.
    Typically any API errors are well defined - so I've never actually seen code that ends up in those corner cases.

    Also your use of vbObjectError is creative. I can see how it would help someone who has a limited understanding of the different error mechanisms used in Windows (COM, Shell and Other APIs), and VB. A much needed post, considering I haven't really seen anyone address this from a VB specific angle (except maybe McKinney's incomplete treatment in Hardcore VB) Most of the experts on here just use their Win32 knowledge from C/C++, and don't help the rest of the VBers.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width