Imported native function doesn't work in .NET 4.0 - vb.net

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.

Related

Error lib kernel32.dll 'PInvokeStackImbalance' VB.Net [duplicate]

This question already has answers here:
PInvokeStackImbalance exception when using IntPtr in .NET 4? (Works in .NET 3.5)
(3 answers)
Closed 4 years ago.
I have a problem with my code, which it can be run on vb6 but somehow it crashed on vb.net
Public Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
And here is how i call that function:
Dim lRetval As Long
'Get Host IP Address in host.ini
sHostIP = Space(128)
lRetval = GetPrivateProfileString("HOSTPATH", "HOSTIP", "", sHostIP, Len(sHostIP), "Host.ini")
The error says:
Managed Debugging Assistant 'PInvokeStackImbalance' : 'A call to PInvoke function 'BDS ByPass!CreateBulkCIF.GlobalClass::GetPrivateProfileString' 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.'
Please help... I cant find any something useful anywhere.
The most common cause of this issue is using a function signature that was intended for VB6 rather than VB.NET. If you have a parameter or return of type Long then that is almost certainly the case. Windows API functions and other unmanaged APIs generally work with 32-bit integers. In VB6, the Long type was 32-bit and the Integer type was 16-bit. In VB.NET, the Long type is 64-bit and the Integer type is 32-bit. You should almost always be using Integer rather than Long in VB.NET.
Pinvoke.net shows the signature for that function as the following:
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function GetPrivateProfileString(ByVal lpAppName As String,
ByVal lpKeyName As String,
ByVal lpDefault As String,
ByVal lpReturnedString As StringBuilder,
ByVal nSize As Integer,
ByVal lpFileName As String) As Integer
End Function
You should use that site as your first choice for VB.NET API signatures.

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.

How do I read cells of another program's TStringGrid control?

How can I extract items from other application's gridview? The class name of the control is TStringGrid.
I can get the handle of the TStringGrid window with FindWindowEx using these declarations:
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
ByVal childAfter As IntPtr, _
ByVal lclassName As String, _
ByVal windowTitle As String) As IntPtr
End Function
code:
Dim TheMainForm As Integer = FindWindow("form", "fname")
Dim GV As Integer = FindWindowEx(TheMainForm, 0, "TStringGrid", "")
How can I extract the items from GV (TStringGrid handle)?
(I have to finish this project by tomorrow.)
A Delphi string grid is not a windows control. It's a custom Delphi control. As such it doesn't respond to windows messages asking for its content. Without the source of the app you would need to reverse engineer the app to work out where the content is stored.
Realistically the most effective way to do this will be to inject a thread into the target application. That thread can then do the work of reading the information and can then use some IPC to get the data back to your VB process.
In order to do this you will, ideally, need:
Knowledge of the exact version of Delphi used to build the app.
A deep understanding of the Delphi compiler and RTL.
The Delphi VCL source code for TStringGrid.
I've no idea how you'll be able to synchronize your reading thread with the Delphi app.
Anyway, whilst what you ask for is, in theory possible, in reality it is completely impractical. The sensible solution is to ask the authors of the Delphi program to provide an automation interface.

First call to SetupDiGetDeviceInterfaceDetail will always fail with ERROR_INVALID_USER_BUFFER

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

pInvoke, .net 4 vs 3.5

I've an issue regarding p/invoke from managed to unmanaged code. See my original post at the MSDN forum (as brief summary is seen later in this post). Before I go on, I just want to explain a couple of things: I have a wrapper assembly in net 3.5 that does the actual interop to the unmanaged code. Then I have my console "host app" which uses the wrapper assembly.
The solution I came up (I refer to my MSDN post) works when the host app is using .net 4, but it doesn't work when changing the host app to use .net 3.5. When changing, I get a AccessViolationException.
host app: 4.0, wrapper assembly: 3.5
-> works
host app: 3.5, wrapper assembly: 3.5 -> throws
AccessViolationException
Do anyone have a clue to why I get an AccessViolationException? Most importantly, how do I get it working with .net 3.5?
Brief summary on the MSDN post I refered to. I have this messy p/Invoke I need to solve. The C declaration looks like this:
long TBAPI TbeAndring (short,
short,
PTB_PU,
PTB_ANDRING_INFO,
PTB_PARAMS,
PTB_PREMIE_NIVA_INFO,
PTB_PREMIE,
PTB_FORMAN_INFO,
PTB_FORMAN,
PTB_FUNK,
PTB_PARAMS,
PTB_PREMIE_NIVA_INFO,
PTB_PREMIE,
PTB_FORMAN_INFO,
PTB_FORMAN,
PTB_FUNK);
Where PTB means that each argument is a struct pointer to an array of arbitrary length. The structs mostly contains strings, doubles, char and short. Anyway, I ended up with this DllImport statement:
<DllImport(NativeLibraryName, CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Ansi, SetLastError:=True)>
Public Shared Function TbeAndring(ByVal sAntMoment As Short, _
ByVal sAntPU As Short, _
<[In]()> ByVal atbpu As PTB_PU(), _
<[In]()> ByVal atbandringinfo() As PTB_ANDRING_INFO, _
<[In]()> ByVal atbparamsEfter() As PTB_PARAMS, _
<[In]()> ByVal aNivaInfoEfter() As PTB_PREMIE_NIVA_INFO, _
<[In](), Out()> ByVal atbpremieEfter() As PTB_PREMIE, _
<[In]()> ByVal atbFormanInfoEfter() As PTB_FORMAN_INFO, _
<[In](), Out()> ByVal atbFormanEfter() As PTB_FORMAN, _
<[In](), Out()> ByVal atbfunkEfter() As PTB_FUNK, _
<[In]()> ByVal atbparamsFore() As PTB_PARAMS, _
<[In]()> ByVal aNivaInfoFore() As PTB_PREMIE_NIVA_INFO, _
<[In](), Out()> ByVal atbpremieFore() As PTB_PREMIE, _
<[In]()> ByVal atbFormanInfoFore() As PTB_FORMAN_INFO, _
<[In](), Out()> ByVal atbFormanFore() As PTB_FORMAN, _
<[In](), Out()> ByVal atbfunkFore() As PTB_FUNK) As Int32
End Function
As you see some of the arguments are changed by the unmanaged code as well.
I don't know if it's the only reason for AccessViolationException, but I've seen that exception come from unmanaged code that was running with a corrupt C runtime library heap. In particular, some memory which was meant to contain a valid pointer came to contain garbage: when dereferenced, it pointed to nonexistant memory.
If you have an issue like this, then the change in .NET version may simply have moved the problem around, such that the problem has been seen under .NET 3.5 but has not yet been seen under .NET 4.0.
I recommend running the code under the debugger, and including Native code debugging. You may find the original exception.