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.
Related
I know it's unusual, but I have to port some code from VB.NET to VB6, and I need to convert 2 APIs to VB6.
To be honest, I have never written an API declaration in VB6, I have only copied it from websites and other code.
Could somebody tell me how I could convert the following code from VB.NET to VB6?
<DllImport("XInput1_4", EntryPoint:="XInputGetState", setlasterror:=True)> _
Private Shared Function XInputGetState_1_4(ByVal dwUserIndex As UInteger, ByRef pState As XINPUT_STATE) As UInteger
End Function
<DllImport("XInput9_1_0", EntryPoint:="XInputGetState", setlasterror:=True)> _
Private Shared Function XInputGetState_9_1_0(ByVal dwUserIndex As UInteger, ByRef pState As XINPUT_STATE) As UInteger
End Function
Is the following VB6 code correct?
Private Declare Function XInputGetState_1_4 Lib "XInput1_4.dll" (ByVal dwUserIndex As Long, ByRef pState As XINPUT_STATE) As Long
Private Declare Function XInputGetState_9_1_0 Lib "XInput9_1_0.dll" (ByVal dwUserIndex As Long, ByRef pState As XINPUT_STATE) As Long
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.
I'm facing a problem importing a DLL on different environments.
I have to check Windows platform and import the third-party DLL that will be placed in C:\Program Files\ (for 32-bit) or C:Program Files (x86)\ (for 64-bit).
Before the code was written like this:
Declare Function RDRCConnect Lib "c:\program files\TP-DLL\RDRCAP32.DLL" (ByVal lpszServerName As String, ByVal lNetConnType As Integer, ByVal lpszParam1 As String, ByVal lpszParam2 As String, ByVal lpszParam3 As String, ByRef lNetConn As Integer, ByRef lNetErr As Integer) As Integer
Declare Function RDRCDisconnect Lib "c:\program files\TP-DLL\RDRCAP32.DLL" (ByVal lNetConn As Integer, ByRef lNetErr As Integer) As Integer
...and I changed to use attributes:
Private Const CheminDLL As String = "C:\Program Files\TP-DLL\RDRCAP32.DLL"
<System.Runtime.InteropServices.DllImport(CheminDLL)>
Private Shared Function RDRCConnect(ByVal lpszServerName As String, ByVal lNetConnType As Integer, ByVal lpszParam1 As String, ByVal lpszParam2 As String, ByVal lpszParam3 As String, ByRef lNetConn As Integer, ByRef lNetErr As Integer) As Integer
End Function
How can I change the DLL path dinamically in this scenario, once the DLLImport expects a Constant as parameter?
There's no way to pass anything other than a constant into the attribute, since attributes, by definition are evaluated at compile-time, not at runtime. There may be better alternatives, but one option I can give you would be to create separate imports for each version:
<DllImport("C:\Program Files\TP-DLL\RDRCAP32.DLL", EntryPoint := "RDRCConnect")>
Private Shared Function RDRCConnect32(ByVal lpszServerName As String, ByVal lNetConnType As Integer, ByVal lpszParam1 As String, ByVal lpszParam2 As String, ByVal lpszParam3 As String, ByRef lNetConn As Integer, ByRef lNetErr As Integer) As Integer
End Function
<DllImport("C:\Program Files (x86)\TP-DLL\RDRCAP32.DLL", EntryPoint := "RDRCConnect")>
Private Shared Function RDRCConnect64(ByVal lpszServerName As String, ByVal lNetConnType As Integer, ByVal lpszParam1 As String, ByVal lpszParam2 As String, ByVal lpszParam3 As String, ByRef lNetConn As Integer, ByRef lNetErr As Integer) As Integer
End Function
Then you would need to choose which one to call appropriately each time you call the method.
I just stumbled upon this a couple of days ago when I had a similar issue to yours and it put me on the right path. Look for it here.
If the dlls are the same (same name and signatures) but just in different locations then you can p/invoke LoadLibrary explicitly with the full path of the dll as determined at runtime. As long as you do this before you invoke any of the exported APIs then it will use the one already loaded as long as the name of the dll in the import is the same.
I´m having some problem to convert my VB6 project to VB.NET
I don't understand how this "AddressOf" function should be in VB.NET
My VB6 code:
Declare Function MP4_ClientStart Lib "hikclient.dll" _
(pClientinfo As CLIENT_VIDEOINFO, ByVal abab As Long) As Long
Public Sub ReadDataCallBack(ByVal nPort As Long, pPacketBuffer As Byte, _
ByVal nPacketSize As Long)
If Not bSaved_DVS Then
bSaved_DVS = True
HW_OpenStream hChannelHandle, pPacketBuffer, nPacketSize
End If
HW_InputData hChannelHandle, pPacketBuffer, nPacketSize
End Sub
nn1 = MP4_ClientStart(clientinfo, AddressOf ReadDataCallBack)
You are probably seeing this error:
'AddressOf' expression cannot be
converted to 'Long' because 'Long' is
not a delegate type.
What you probably want to do is create a delegate then change the type of adab to that delegate type. Add this to the class:
Public Delegate Sub ReadDataCallBackDelegate(ByVal nPort As Long, _
ByVal pPacketBuffer As Byte, ByVal nPacketSize As Long)
Then change your P/Invoke declaration to:
Declare Function MP4_ClientStart Lib "hikclient.dll" (ByVal pClientinfo As _
CLIENT_VIDEOINFO, ByVal abab As ReadDataCallBackDelegate) As Long
Do not delete/change your ReadDataCallBack Sub, you still need that.
At that point he compiler should be happy. However, the point made by others is important. The length of Integers and Longs is different in VB6 than in VB.NET. So in .NET you need to use Integer anytime you used a Long in VB6.
Regarding callbacks in unmanaged code see if this similar post helps you.
Regarding your question - I don't think you need callback functions or the example you posted is not correct/complet - see the post indicated above and clarify your code sample.
I assume that the second parameter to MP4_ClientStart is supposed to be the address of a callback function. Likely the problem is that you've defined it here as a Long, which in VB6 is a 32-bit value, but in VB.NET is a 64-bit value. You'll probably have some success by changing your declaration to:
Declare Function MP4_ClientStart Lib "hikclient.dll" _
(pClientinfo As CLIENT_VIDEOINFO, ByVal abab As Integer) As Integer
Here is the VB.NET implementation:
Declare Function MP4_ClientStart Lib "hikclient.dll" (ByRef pClientinfo As _
CLIENT_VIDEOINFO, ByVal abab As ReadDataCallBackDelegate) As Integer
Public Delegate Sub ReadDataCallBackDelegate(ByVal nPort As Long, _
ByRef pPacketBuffer As Byte, ByVal nPacketSize As Long)
Public Sub ReadDataCallBack(ByVal nPort As Integer, ByRef pPacketBuffer As _
Byte, ByVal nPacketSize As Integer)
If Not bSaved_DVS Then
bSaved_DVS = True
HW_OpenStream(hChannelHandle, pPacketBuffer, nPacketSize)
End If
HW_InputData(hChannelHandle, pPacketBuffer, nPacketSize)
End Sub
MP4_ClientStart(clientinfo, AddressOf ReadDataCallBack)
I have code that needs to run on both Excel 2003 and Excel 2007, and there are a few spots where changes in the versions cause the code to halt. I tried separating these lines out with If-Else statements, but the code won't compile on either because it doesn't recognize the code used for the other. Is there any way I could tell one version to ignore a block of code, similar to a C or C++-style #ifdef, in VBA?
This is a good starting point, but it won't work with the version of Excel that its running on, since that can only be figured out at run-time, not compile time.
If you need to branch your code based on information only discoverable at run time you might consider late binding as a solution. There are two ways you can sneak around version problems.
The first way can be used if you need to Access a property or method that only exists in certain versions, you can use CallByName. The advantage of call by name is that it allows you to preserve early binding (and intellisense) for your objects as much as possible.
To give an example, Excel 2007 has a new TintAndShade property. If you wanted to change the color of a range, and for Excel 2007 also ensure TintAndShade was set to 0 you would run into trouble because your code won't compile in Excel 2003 which does not have TintAndShade as a property of the range object. If you access the property that you know is not in all versions using CallByName, you code will compile in all versions fine, but only run in the versions you specify. See below:
Sub Test()
ColorRange Selection, Excel.Application.version, 6
End Sub
Sub ColorRange(rng As Excel.Range, version As Double, ParamArray args() As Variant)
With rng.Interior
.colorIndex = 6
.Pattern = xlSolid
If version >= 12# Then
'Because the property name is stored in a string this will still compile.
'And it will only get called if the correct version is in use.
CallByName rng.Interior, "TintAndShade", VbLet, 0
End If
End With
End Sub
The second way is for classes that have to be instantiated via "New" and don't even exist in old versions. You won't run into this problem with Excel, but I will give a quickie demo so you can see what I mean:
Imagine that you wanted to do File IO, and for some bizarre reason not all of the computers had the Microsoft Scripting Runtime on them. But for some equally bizarre reason you wanted to make sure it was used whenever it was available. If set a reference to it and use early binding in your code, the code won't compile on systems that don't have the file. So you use late binding instead:
Public Sub test()
Dim strMyString As String
Dim strMyPath As String
strMyPath = "C:\Test\Junk.txt"
strMyString = "Foo"
If LenB(Dir("C:\Windows\System32\scrrun.dll")) Then
WriteString strMyPath, strMyString
Else
WriteStringNative strMyPath, strMyString
End If
End Sub
Public Sub WriteString(ByVal path As String, ByVal value As String)
Dim fso As Object '<-Use generic object
'This is late binding:
Set fso = CreateObject("Scripting.FileSystemObject")
fso.CreateTextFile(path, True, False).Write value
End Sub
Public Sub WriteStringNative(ByVal path As String, ByVal value As String)
Dim lngFileNum As Long
lngFileNum = FreeFile
If LenB(Dir(path)) Then Kill path
Open path For Binary Access Write Lock Read Write As #lngFileNum
Put #lngFileNum, , value
Close #lngFileNum
End Sub
There is a comprehensive list of all Adds and Changes to Excel Object Model since 2003:
http://msdn.microsoft.com/en-us/library/bb149069.aspx
For changes between 1997 and 2000 go here:
http://msdn.microsoft.com/en-us/library/aa140068(office.10).aspx
Yes it is possible to do conditional compilation in Excel VBA. Below is a brief resource and some example code:
Conditional Compilation
#If Win32 Then
' Profile String functions:
Private Declare Function WritePrivateProfileString Lib "KERNEL32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long
Private Declare Function GetPrivateProfileString Lib "KERNEL32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As Any, ByVal lpKeyName As Any, ByVal lpDefault As Any, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
#Else
' Profile String functions:
Private Declare Function WritePrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Integer
Private Declare Function GetPrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As Any, ByVal lpReturnedString As String, ByVal nSize As Integer, ByVal lpFileName As String) As Integer
#End If
Can you post the offending lines of code?
If it is a constant like vbYes or xlFileFormat or whatever, use the corresponding numeric value.
Show me what you got, I'll see if I can refactor it.
Bill