First call to SetupDiGetDeviceInterfaceDetail will always fail with ERROR_INVALID_USER_BUFFER - vb.net

I'm going little crazy about this and I already spent few hours to find the problem so I guess I need some WinAPI guru here :) I use VB .NET so please be kind :)
Trying to communicate with some USB device and at SetupDiGetDeviceInterfaceDetail() step, the (first) call to function will fail with ERROR_INVALID_USER_BUFFER. The second call will have non sense now :) The problem is why ? The DeviceInfoTable handle is correct since I use'it in few more functions with success and I tested Err.LastDllError after each call.
The InterfaceDataStructure I presume is also correct since otherways SetupDiEnumDeviceInterfaces() will fails. I suspect incorrect declarations of dll imports or structure.
If Not SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, Nothing, 0, StructureSize, Nothing) Then
ErrorStatus = Err.LastDllError ' <-- always 0x6F8, ERROR_INVALID_USER_BUFFER
End If
Here are declarations
Dim DeviceInfoTable As IntPtr = INVALID_HANDLE_VALUE
Dim InterfaceDataStructure As SP_DEVICE_INTERFACE_DATA = New SP_DEVICE_INTERFACE_DATA
Dim InterfaceIndex As Integer = 0
Dim ErrorStatus As Integer = 0
Dim DevInfoData As SP_DEVINFO_DATA = New SP_DEVINFO_DATA
Dim dwRegType As Integer
Dim dwRegSize As Integer
Dim DetailedInterfaceDataStructure As SP_DEVICE_INTERFACE_DETAIL_DATA = New SP_DEVICE_INTERFACE_DETAIL_DATA
Dim StructureSize As Integer = 0
'Structures declarations
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVINFO_DATA
Public cbSize As UInteger
Public InterfaceClassGUID As Guid
Public DevInst As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DATA
Public cbSize As UInteger
Public InterfaceClassGuid As Guid
Public Flags As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
Public cbSize As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public DevicePath As String
End Structure
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByRef deviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
Thanks very much in advance,

If Not SetupDiGetDeviceInterfaceDetail(..., Nothing, 0, StructureSize, Nothing)
You are struggling with basic VB.NET usage here, the keyword Nothing doesn't mean what you hope it does. What the api function wants you to do is pass a null pointer. IntPtr.Zero. That could be Nothing, but it is not in this case. You declared the argument types as structures. They are value types. Nothing means something else in the case of value types, it means "default value". So you are actually passing a pointer to a structure here, a structure that's zero initialized. The function is unhappy about that and tells you so.
You cannot pass IntPtr.Zero when you use these pinvoke declarations. You can cheat and declare an overload of the function, one that uses different argument types. Like this:
<DllImport("setupapi.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(
ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal mustPassIntPtrZero As IntPtr,
ByVal mustPassZero As Int32,
ByRef RequiredSize As Int32,
ByVal mustPassIntPtrZero2 As IntPtr) As Boolean
Now you can make the first call to the function:
If Not SetupDiGetDeviceInterfaceDetail(..., IntPtr.Zero, 0, StructureSize, IntPtr.Zero)
You should use the returned RequiredSize to allocate memory with Marshal.AllocHGlobal(). Pass the returned pointer as the deviceInterfaceDetailData argument. Which must now be declared as ByVal IntPtr. And use Marshal.PtrToStructure() to convert it to the struct after the call. Yes, Pack is an issue on 64-bit operating systems.
Fix the string problem by declaring the structure with CharSet:=CharSet.Auto so you'll get the Unicode conversion instead of the Ansi conversion.

Now I figured out, in fact half working :) Maybe someone can en light me.
At least on VB .NET 2010, the SetupDiGetDeviceInterfaceDetail() doesn't line NULLs (aka nothing) as parameters, so I had to first pass something dummy:
DetailedInterfaceDataStructure.cbSize = 6
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, 1000, StructureSize, DevInfoData)
The cbSize must be <> 0 and on 32 bit systems must be 6 (4 for DWORD and 2 for null terminated wide string). The 1000 it's just a big number, so SetupDiGetDeviceInterfaceDetail() won't return even ERROR_INSUFFICIENT_BUFFER
Now the second pass supposed to get the real stuff:
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, StructureSize, StructureSize, Nothing)
Which doesn't return any system error, but path is only a backslash character "\" and this isn't good.... it supposed to be a valid USB path
"\\?\usb#vid_01E5&pid_00A2#5&1d4952dc&0&2#{18d0A210-85D2-........"
Anyone can help me ?...

yes I am struggling with VB since I'm coming from pure C99 and assembler for MCU :) In fact, after few hours of errors I got some snippet in C# that worked like a charm but I had to know why isn't working in VB :) I finally solved, partially like your advices
I overloaded SetupDiGetDeviceInterfaceDetail() with another one, just to accept a ptr to some unmanaged buffer:
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal deviceInterfaceDetailData As IntPtr,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
End Function
Now the problem was I used ByRef deviceInterfaceDetailData As IntPtr instead of ByVal deviceInterfaceDetailData As IntPtr, was my mistake from VB's quite simple perspective.
Indeed, now I can pass some unmanaged buffer at 2'nd call of SetupDiGetDeviceInterfaceDetail()
Dim PUnmanagedDetailedInterfaceDataStructure As IntPtr = IntPtr.Zero
PUnmanagedDetailedInterfaceDataStructure = Marshal.AllocHGlobal(StructureSize)
DetailedInterfaceDataStructure.cbSize = 6 ' cbSize = 4 bytes for DWORD + 2 bytes for Unicode null terminator
Marshal.StructureToPtr(DetailedInterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, False) ' Copy the contents of the structure, to an unmanaged memory space
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, StructureSize, StructureSize, DevInfoData)
And this is finally working. I expect some points for struggling :)
I saw a lot of similar threads on internet but none with a complete solve.
Merry Christmas to all

Related

GetTempFileName throwing 'System.AccessViolationException' vb.net

I am new to VB and is working on VB6 to VB.net migration. There is one API call which appends a prefix in temporary file name.
I have added the dll as
<DllImport("kernel32")> _
Private Shared Function GetTempFileName(ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
End Function
When I am calling this:
test = GetTempFileName(My.Application.Info.DirectoryPath, Prefix, 0, m_sTempfile)
It throws an exception:
An unhandled exception of type 'System.AccessViolationException' occurred in Forum.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I tried using Path.GetTempFileName() but I might need to perform several manipulation to get the file name prefixed with specific word and located to specific location.
I crossed checked the values and they are NOT bad data.
I tried mutiple resolutions, but none of it worked.
Can someone help in this? Thanks in advance!
Pinvoke declarations need to be rewritten when you move them to VB.NET. Many differences, like Long needs to be Integer and if the winapi function returns a string then you need to use StringBuilder instead of String. Required because String is an immutable type.
Proper declaration is:
<DllImport("kernel32", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function GetTempFileName(ByVal lpszPath As String, _
ByVal lpPrefixString As String, _
ByVal wUnique As Integer, _
ByVal lpTempFileName As StringBuilder) As Integer
End Function
And a proper call looks like:
Dim buffer As New StringBuilder(260)
If GetTempFileName("c:\temp", "xyz", 0, buffer) = 0 Then
Throw New System.ComponentModel.Win32Exception()
End If
Dim filename = buffer.ToString()
The pinvoke.net website tends to be a half-decent resource for pinvoke declarations. Not for this one though, the VB.NET version is pretty fumbled.

Why does capGetDriverDescription accept a buffer as a value type instead of a reference type?

Trying out some API programming, I encountered a problem with the capGetDriverDescription function of AVICAP32.dll:
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
As far as I know, the lpszName parameter is a buffer for getting the driver description, and I read somewhere that the buffer should be passed as a reference type (ByRef instead of ByVal), and of course it should be a reference because of storing the information I need after returning from the function. But in this function, it's a value type, and it works fine!
even I tried to change it to ByRef but the application didnt run!
What knowledge am I missing? How can this buffer store my information while it's being passed a value type?
This is how the code calls the function to get available webcams:
Private Sub LoadDeviceList()
On Error Resume Next
Dim strName As String = Space(100)
Dim strVer As String = Space(100)
Dim bReturn As Boolean
Dim x As Integer = 0
Do
bReturn = capGetDriverDescriptionA(x, strName, 100, strVer, 100)
If bReturn Then
lst1.Items.Add(strName.Trim)
End If
x += 1
Application.DoEvents()
Loop Until bReturn = False
End Sub
I'll start by saying I don't know much about this subject (marshaling data can be a deep subject), but I think Default Marshaling for Strings (MSDN) may help you. Scroll down to the part that says Fixed-Length String Buffers. According to this, when a string is marshaled to the API, even if passed byRef, it can't be modified by the callee.
I haven't tested, but according to the MSDN example, to get a value back from your function, the definition would become
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As StringBuilder, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
Changing lpszName from String to StringBuilder. Apparently StringBuilder will work as a buffer to get the string back from the function.
Then you would call your function like so,
StringBuilder sb = new StringBuilder(256)
capGetDriverDescription(Driver, sb, sb.Capacity + 1, ....
return sb.ToString()
I found a very similar question, How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?.

how to call TrackPopupMenu function from managed vb.net code

I am trying to call TrackPopupMenu function to display a menu at runtime from managed VB.NET code.
Below is the error I am getting :
PInvokeStackImbalance was detected Message: A call to PInvoke function
'UeWIPopupX!UeWIPopupX.mDeclares::TrackPopupMenu' has unbalanced
the stack. This is likely because the managed PInvoke signature does
not match the unmanaged target signature. Check that the calling
convention and parameters of the PInvoke signature match the target
unmanaged signature.
Below is the declaration I am using for TrackPopupMenu function :
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> _
Friend Function TrackPopupMenu(ByVal hMenu As Long, ByVal wFlags As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, ByVal hWnd As IntPtr, ByVal lprc As RECT) As Integer
End Function
Below is the code for calling TrackPopupMenu function :
dim lpRc as RECT
Dim tP As POINTAPI
Dim lR as Integer
Dim lUn as Integer
lUn = TPM_RIGHTBUTTON Or TPM_TOPALIGN Or TPM_LEFTALIGN Or TPM_RETURNCMD
tP.x = 50
tP.y = 100
'Here I am getting the error
lR = TrackPopupMenu(m_ppMenu.Tools(1).hMenu, lUn, tP.x, tP.y, 0, m_hWndOwner, lpRC)
Below is the declaration for rectangle RECT:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Structure RECT
Dim Left As Integer
Dim Top As Integer
Dim Right As Integer
Dim Bottom As Integer
End Structure
All the arguments during call of TrackPopupMenu has some values.
I tried different callingConvention but still getting the error.
I am not able to solve this. Does anyone know how to resolve this issue ?
Your declaration is wrong. The first argument is a handle to the menu, it must therefore be IntPtr. The last argument is a pointer to RECT. ByRef in VB.NET. Since it isn't actually used, you are better off declaring it ByVal IntPtr so you don't need the RECT declaration. Pass IntPtr.Zero in your call. The return value is Boolean, not Integer. Throw a Win32Exception if you get a False return. Fix:
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Friend Function TrackPopupMenu(ByVal hMenu As IntPtr, ByVal wFlags As Integer, _
ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, _
ByVal hWnd As IntPtr, ByVal ignored As IntPtr) As Boolean
End Function
There are some hints that you didn't get the menu right, hard to imagine how you came up with Long as the argument type. Do note that this function is already ably wrapped in .NET, having to pinvoke it should be extremely rare. TrackPopupMenu is already called by the .NET ContextMenu class. The ContextMenuStrip class gives a more modern version of it with better rendering options.

Imported native function doesn't work in .NET 4.0

I am migrating project from .net 3.5 to .net 4.0 and faced the following issue.
There are 2 DllImport statements:
<DllImport("hid64.dll")> _
Public Sub GenerateHardwareID( _
<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal Buffer As Byte(), _
ByVal BufferLength As Int32)
End Sub
<DllImport("hid64.dll")> _
Public Function BufferToString( _
<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal Buffer As Byte(), _
ByVal BufferLength As Int32) As <MarshalAs(UnmanagedType.LPWStr)> String
End Function
For the .NET 3.5 both functions work well. But for the .NET 4.0 the call of the BufferToString function breaks execution of the programm without raising any exception.
I played around with CallingConvention, CharSet and so on fields of the DllImport attribute:
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dllimportattribute.aspx
without success.
This variant:
<DllImport("hid64.dll", CharSet:=CharSet.Auto, PreserveSig:=False, SetLastError:=True)> _
Public Function BufferToString( _
<MarshalAs(UnmanagedType.LPArray)> ByVal Buffer As Byte(), _
ByVal BufferLength As Int32) As <MarshalAs(UnmanagedType.LPWStr)> String
End Function
does not break execution of the programm but the function returns 'Nothing'.
Here's what I believe to be the most likely explanation.
The BufferToString function has a return value that is a string. The p/invoke marshaller has to marshal that from native to managed. It does that by assuming that the native code returns a null-terminated character pointer that was allocated by the COM allocator. When it has finished transferring the content to a .net string it calls CoTaskMemFree on the pointer. If that memory was not allocated by the COM allocator then you may see failures at this point.
To get around the problem you have a few options. You could change the p/invoke for BufferToString to return IntPtr. Copy the contents to a .net string with Marshal.PtrToStringUni. This then leaves you with the responsibility of disposing of the unmanaged memory. Presumably the unmanaged library offers you a mechanism to do that.
If you wrote the unmanaged library then you can use an alternative solution. Leave the p/invoke exactly as it currently is but change the unmanaged library to allocate the return value using CoTaskMemAlloc. That then will match with the p/invoke marshaller's assumptions.

ReadProcessMemory keeps returning 0

I'm currently developing a little hobby project to display health information in a game on my G15 keyboard through VB.NET.
When I use ReadProcessMemory via an API call, it keeps returning zero. The MSDN documentation referred me to use the Marshal.GetLastWin32Error() call to find out what is wrong and it returns 1400: INVALID WINDOW HANDLE.
I am now confused about whether the first argument of the function wants a window handle or a process id. Regardless, I have tried both with FindWindow and hardcoding the process id while the application is running (getting it from task manager).
I have tried three different games, Urban Terror, Grand Theft Auto: SA and 3D pinball for windows, getting the memory addresses from an application called Cheat Engine; they all seem to fail.
Here is the code I'm using to do it:
API Call:
Private Declare Function ReadProcessMemory Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpBaseAddress As Integer, _
ByRef lpBuffer As Single, _
ByVal nSize As Integer, _
ByRef lpNumberOfBytesWritten As Integer _
) As Integer
Method:
Dim address As Integer
address = &HA90C62&
Dim valueinmemory As Integer
Dim proc As Process = Process.GetCurrentProcess
For Each proc In Process.GetProcesses
If proc.MainWindowTitle = "3D Pinball for Windows - Space Cadet" Then
If ReadProcessMemory(proc.Handle.ToInt32, address, valueinmemory, 4, 0) = 0 Then
MsgBox("aww")
Else
MsgBox(CStr(valueinmemory))
End If
End If
Next
Dim lastError As Integer
lastError = Marshal.GetLastWin32Error()
MessageBox.Show(CStr(lastError))
Could somebody please explain to me why it is not working? Thanks in advance.
First, your method signature is wrong, Single=Float while the original parameter is of the LPBUF type.
Use this method signature:
<DllImport("kernel32.dll", SetLastError=true)> _
Public Shared Function ReadProcessMemory( _
ByVal hProcess As IntPtr, _
ByVal lpBaseAddress As IntPtr, _
<Out()>ByVal lpBuffer() As Byte, _
ByVal dwSize as Integer, _
ByRef lpNumberOfBytesRead as Integer
) As Boolean
End Function
Second, I believe that the hProcess handle expects a handle opened by OpenProcess function, not the window handle.
Thank you arul, I have sort of fixed my problem.
Dim address As Integer
address = &HA90C62&
Dim floatvalueinmemory() As Byte
Dim proc As Process = Process.GetCurrentProcess
For Each proc In Process.GetProcesses
If proc.MainWindowTitle = "3D Pinball for Windows - Space Cadet" Then
Dim winhandle As IntPtr = OpenProcess(PROCESS_ACCESS.PROCESS_VM_READ, True, proc.Id)
If ReadProcessMemory(winhandle, address, floatvalueinmemory, 4, 0) = 0 Then
Dim lastError As Integer
lastError = Marshal.GetLastWin32Error()
MessageBox.Show(CStr(lastError))
MsgBox("aww")
Else
MsgBox("woo")
End If
CloseHandle(winhandle)
End If
Next
Now it believes the handle is valid and attempts to read the processes memory, but I get the error message 299 : Only part of a ReadProcessMemory or WriteProcessMemory request was completed.
Does anyone have any ideas as to how I should proceed to fix this issue?
message 299 : Only part of a ReadProcessMemory or WriteProcessMemory request was completed meant the memory I was trying to read was protected.
Thanks for all your help, I'm going to mark arul's answer as the answer.