Search code examples
vb.netpinvokewininetkernel32

PInvoke FormatMessage Message ByRef lpBuffer As [String] Is Nothing


Declaring this function:

    <DllImport("kernel32.dll", EntryPoint:="FormatMessageW", SetLastError:=True, CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.StdCall)>
Public Shared Function FormatMessage(ByVal dwFlags As Integer,
                                     ByRef lpSource As IntPtr,
                                     ByVal dwMessageId As Integer,
                                     ByVal dwLanguageId As Integer,
                                     ByRef lpBuffer As [String],
                                     ByVal nSize As Integer,
                                     ByRef Arguments As IntPtr) As Integer
End Function

Based on the definition here: https://pinvoke.net/default.aspx/kernel32/FormatMessage.html

Scenario
I am currently PInvoking wininet.dll to perform FTP transactions against a server. If an error is encountered I get a error code back from Err.LastDllError. I have a function defined that gets the the dll error and returns a message based on the error code, however it is not working as expected. Here is the function that I use to throw dll errors:

Private Sub ThrowLastDllError()
    Dim iLastErrorID As Integer = Err.LastDllError
    Dim iMessageBuffer As IntPtr = Nothing
    Dim iModuleHandle As IntPtr = GetModuleHandle("wininet.dll")

    Dim sMessageBuffer As String = Nothing

    If iLastErrorID > 12000 And iLastErrorID < 12157 Then
        FormatMessage(FORMAT_MESSAGE_FROM_HMODULE Or
                     FORMAT_MESSAGE_IGNORE_INSERTS Or
                     FORMAT_MESSAGE_ALLOCATE_BUFFER,
                     iModuleHandle,
                     iLastErrorID,
                     0,
                     sMessageBuffer,
                     256,
                     Nothing)

        Debugger.Break()
        'TODO: Throw exception with error code message here
    End If
End Sub

Based on the technique described here: https://learn.microsoft.com/en-us/windows/desktop/wininet/appendix-c-handling-errors I am expecting to get some kind of string message based on the error code for this particular dll, for example if I get an error code of 12110 (ERROR_FTP_TRANSFER_IN_PROGRESS. Reference: https://support.microsoft.com/en-au/help/193625/info-wininet-error-codes-12001-through-12156) I would expect to get a message back (in variable sMessageBuffer) similar to the following if not the same "The requested operation cannot be made on the FTP session handle because an operation is already in progress.". However sMessageBuffer is never assigned a value and remains nothing. I can only assume I am misusing this technique somehow, I've tried various ways described on online forums and this site itself but I haven't been successful.


Solution

  • Here some sample code that works:

    Sub Main()
    
        Dim h As IntPtr = LoadLibrary("wininet.dll") ' or GetModuleHandle ...
        Dim sb = New StringBuilder(1024)
        FormatMessage(Format_Message.FORMAT_MESSAGE_FROM_HMODULE Or Format_Message.FORMAT_MESSAGE_IGNORE_INSERTS,
            h,
            12002,
            0,
            sb,
            sb.Capacity,
            Nothing)
    
        Console.WriteLine(sb) ' prints "The operation timed out"
       ' FreeLibrary, etc.
    End Sub
    
    Enum Format_Message
        FORMAT_MESSAGE_IGNORE_INSERTS = &H200
        FORMAT_MESSAGE_FROM_SYSTEM = &H1000
        FORMAT_MESSAGE_FROM_HMODULE = &H800
    End Enum
    
    <DllImport("Kernel32", SetLastError:=True, CharSet:=CharSet.Unicode)>
    Public Function FormatMessage(ByVal dwFlags As Format_Message, ByVal lpSource As IntPtr, ByVal dwMessageId As Integer, ByVal dwLanguageId As Integer, lpBuffer As StringBuilder, ByVal nSize As Integer, ByVal Arguments As IntPtr) As Integer
    End Function
    
    <DllImport("kernel32", SetLastError:=True, CharSet:=CharSet.Unicode)>
    Public Function LoadLibrary(ByVal lpFileName As String) As IntPtr
    End Function