Trying to call a C DLL from VB. Can't get one of the parameters working - dll

Trying to set up a USB power strip.
Here's the documentation:
Initializes the Power USB API.
Name: InitPowerUSB
Parameters: model:returns the model number(1:basic, 2:digIO, 3:watchdog, 4:Smart), firmware: returns firmware version in ?.? format in a character string (major revision and minor revision)
Return: >0 if successful. Returns number of PowerUSB devices connected
C++ Example:
if (!m_pwrUSBInit)
{
int model; char firmware[8];
if ((ret=InitPowerUSB(&model, firmware)) > 0)
{
m_pwrUSBInit = 1;
m_numDevices = ret;
}
}
I have been trying to get this working with my VB6 code for around an hour now with no luck. The program either crashes, displays an error like Bad Calling Dll Convention, type mismatch, et cetera.
Here's what I have:
Public Declare Function InitPowerUSB Lib "PwrUSBDll.dll" (ByRef model As Integer, ByVal firmware As String) As Integer
Dim model As Integer
model = 0
Dim firmware As String
firmware = ""
If (InitPowerUSB(model, firmware)) > 0) Then
EndIf
I have tried changing firmware to byte arrays, byref, string, integer, long, etc. It just doesn't seem to want to run.
Does anyone know of a solution to this problem? Thanks

I can't answer the rest of your function signature woes since I don't have any documentation for your PwrUSBDll.dll.
However "Bad DLL calling convention" errors generally mean you have a CDecl entrypoint and VB6 can only call those with some help.
There are a couple of fixes.
The obvious one is to modifiy the source and recompile that DLL using StdCall instead.
Another is to create a type library for that DLL, which helps inform VB6 about the issue and resolves it.
Then you have the option of using VB6's undocumented CDecl decorator:
Public Declare Function InitPowerUSB CDecl Lib "PwrUSBDll.dll" ( _
ByRef model As Integer, _
ByVal firmware As String) As Integer
However the downside is that this will not work when run within the IDE, nor will it work when compiled to p-code. The p-code interpreter doesn't process this keyword.
So you could just bypass it in IDE runs and supply dummy results for testing, or you can create a small wrapper DLL in VB6 that you separately compile to native code.
Caveats:
For this to solve your problem we'd have to assume you are passing correct data types in that argument list. A C++ int is a VB6 Long. You are probably better off passing a VB6 Byte array ByRef for that char[8] unless this is a Unicode DLL entrypoint. The function return value is also most likely Long.

Related

VB GetAsyncKeyState' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature.

I'm working on a small program but when running my program in visual studio I get the following error:
GetAsyncKeyState' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature.
But when I build it to an .exe it runs just fine. But that makes it a bit hard to debug the program.
This is the code snipper that throws the error:
If InGame And Not GetAsyncKeyState(Keys.Tab) Then
If Settings.SkinChangera Then SkinChanger.Skinchanger()
End If
and this is GetAsyncKeyState()
Public Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
I advice you to never ever look for, or use, Declare Lib statements you got from the internet. Most of them out there are for VB6 and are therefore almost always not fully compatible with VB.NET.
Instead, stick to the solutions that use the DllImport attribute. The site pinvoke.net is a great place to look for P/Invoke declarations. If you can't find a VB.NET version of a P/Invoke declaration, take the C# version and run it through a converter such as Telerik.
Having that said, you are receiving the error because the parameter and the return value are not of the correct data types. The GetAsyncKeyState() function's parameter should be Integer and its return value should be Short.
Use the DllImport version of the function instead, with the correct data types, and it should work:
<DllImport("user32.dll")> _
Public Shared Function GetAsyncKeyState(ByVal vKey As System.Windows.Forms.Keys) As Short
End Function
Note: The System.Windows.Forms.Keys enumeration is of type Integer.

No binary compatibility although a declaration is kept identical

I'm stuck on trying generating a new version of a COM DLL with binary compatibility. I don't understand why I get this message :
'init' in the 'Logger' class module has arguments and/or a return type that is incompatible with a similar declaration in the version-compatible component.
Original definition:
Function init(aLOGDIR As String, Optional aListBox As Object, Optional aMAXLISTBOXLINES As Integer) As Boolean
Current definition:
Function init(aLOGDIR As String, Optional aListBox As Object, Optional aMAXLISTBOXLINES As Integer) As Boolean
I haven't change init as you can see...
Here's my steps :
First generation without compatibility
Set up a binary compatibility in Project properties (referencing the previous generated dll with or without renaming it)
Second generation
Warning occurs.
Is this because a parameter is an Object ? Thanks for your help.
In my experience, when trying to get VB6 working with COM or C++, one must pay careful attention to the differences in data-types. I'm guessing this might be your issue. I apologize if you are already familiar with this:
aLOGDIR As String implies a char**; consider changing to ByVal aLOGDIR As String, though I doubt this is relevant to your issue.
Optional aMAXLISTBOXLINES As Integer implies a short and not an int. Certain padding issues could arise, but simply changing it to As Long might be sufficient and fix the issue.
As Boolean implies a short and not a bool on systems. It might be safer to just use As Long.

Using C++ DLL in VB6 app

I am following the guide on: https://msdn.microsoft.com/en-us/library/ms235636.aspx
I have created a DLL as it was stated above and now i want to include it in my VB6 app
I have declared a function in the code:
Private Declare Function Add Lib "C:\WINDOWS\system32\Win32Project1.dll" (ByVal a As Double, ByVal b As Double)
In the form load I have declared some variables and called a function
Dim a As Double
Dim b As Integer
a = 7.4
b = 99
Call Add(a, b)
Each time I run program I get an error that file not found.
File is present in C:\WINDOWS\system32\ and I did try to register dll using:
regsvr32 Win32Project1.dll
But I keep getting error:
LoadLibrary("Win32Project1.dll") failed - The specified module could not be found.
How can this be resolved?
Update:
I have looked further into the issue, please refer to step 2 of "Martin Schlott" answer, and it happens that VC++ encodes DLL functions and because of that i would have a funny name of ever function within DLL.
Using dumpbin.exe, which can be found in the $\Visual Studio\VC\bin, i was able to read the function names that are stored in the DLL. This is what I would get originally:
1 0 00001050 ?Add#MyMathFuncs#MathFuncs##SANNN#Z = ?Add#MyMathFuncs
#MathFuncs##SANNN#Z (public: static double __cdecl MathFuncs::MyMathFuncs::Add(d
ouble,double))
"?Add#MyMathFuncs#MathFuncs##SANNN#Z" - is what I would have to call as a function in VB6 for things to work... and it wouldn't... so I had to decode the name which is done by creating a definition file (.def) within C++ DLL app
In the new Source.def file I have defined that I want to have "Add" function:
LIBRARY
EXPORTS
Add
After rebuilding solution in the "Release" mode dumpbin gave me a much better result. However, __cdecl is automatically added to every function in VC++, unless specified otherways, and it happens that __cdecl doesn't allow function to be used in the VB6. If you run VB6 app right now you would receive a new error "Bad DLL calling convention".
For this to be resolved I had to amend "MyMathFuncs.h" file where I have defined functions to export and change
"static MATHFUNCSDLL_API double Add(double a, double b);"
to
"static MATHFUNCSDLL_API double __stdcall Add(double a, double b);"
After that dumpbin.exe returned much nicer values:
1 3 00001050 Add
This however haven't resolved my problem fully, I would still get "Bad DLL calling convention" and it was due to how I have referenced library in the VB6
Private Declare Function Add Lib "Win32Project1.dll" (ByVal a As Double, ByVal b As Double)
The function "Add" returns a double and in the abovee declaration I declare that values should be pushed and no return given. So I have followed the function format that I have in C++ and added a return with a correct data type that matches the data type in C++ file:
Private Declare Function Add Lib "Win32Project1.dll" (ByVal a As Double, ByVal b As Double) As Double
Now when I run:
Dim a As Double
Dim b As Integer
a = 7.4
b = 99
MsgBox (Add(a, b))
I have a message box stating that result is 106.4!
There are several points wrong in that what you are doing. Funny thing is,nevertheless it should work.
If you specify a DLL directly, you do not need to register it with regsvr32.
Read:
https://msdn.microsoft.com/en-us/library/aa716201(v=vs.60).aspx
resvr32 expect special exported functions to COM Conform register the DLL in the registry. A COM able DLL can be used via for e.g. with CoCreateInstance not with your declare.
If you didn't know this, regsvr is failing.
Also do not copy DLL belonging to your Project into the system32 folder. It do not belong there and it is bad behavior nowadays. For disk memory reasons it was maybe necessary centuries ago, but not today.
Copy the DLL in the same folder where the VB program is. Correct the path to
Private Declare Function Add Lib "Win32Project1.dll" (ByVal a As Double, ByVal b As Double)
and as someone already suggest, use dependency walker. If a dll starting with "MS" is missing. Read my following answer:
https://stackoverflow.com/a/18600033/1922748
to install the proper redist.
UPDATE
You wrote you are missing the "MSVCR120D.DLL". This happens if you compiled the DLL in debug mode, not release mode. You cannot install the MSVCR120D.DLL without the Visual Studio. You need to compile your DLL in release mode and install the redist.
2. Second Update
Regarding to your last comment, the entry point is missing.
You have to export the function! Add
__declspec(dllexport)
before the function you want to export. Look here
https://msdn.microsoft.com/de-de/library/3y1sfaz2.aspx
Your declaration in C++ should look like:
__declspec(dllexport) double Add(double val);
You can forget about dllimport, you do not need that in your case.

How to load DLL file from Jscript file?

So I'm writing a standalone JScript file to be executed by Windows Script Host (this file is not going to be used as a web application).
My goal is to load a dll file. Just like using LoadLibrary function in a C++ application.
I tried researching the subject but I didn't come up with anything useful. I'm so lost I don't have any piece of code to share. I understand using ActiveXObject may come to my rescue. if so, any idea how to use it?
Update:
If we all agree that loading is impossible, I'll settle for validity check. Meaning, don't try to load but check if it is loaded and functional.
You can export a specific function for this purpose.
Then, from your JScript, execute rundll32.exe and check that the function ran as expected.
You might also give Gilles Laurent's DynaWrap
ocx a chance.
This kind of dll needs to be registered on the target system like regsvr32 /s DynaWrap.dll.
It is restricted to 32-bit DLLs, and this might be inconvenient for you to use, but it works on a 64bit Windows. You can't access function exported by ordinal number and you can't directly handle 64bit or greater values/pointers.
Here's a sample to call MessageBoxA from JScript:
var oDynaWrap = new ActiveXObject( "DynamicWrapper" )
// to call MessageBoxA(), first register the API function
oDynaWrap.Register( "USER32.DLL", "MessageBoxA", "I=HsSu", "f=s", "R=l" )
// now call the function
oDynaWrap.MessageBoxA( null, "MessageBoxA()", "A messagebox from JScript...", 3 )
And here from VBScript:
Option Explicit
Dim oDynaWrap
Set oDynaWrap = CreateObject( "DynamicWrapper" )
' to call MessageBoxA(), first register the API function
UserWrap.Register "USER32.DLL", "MessageBoxA", "I=HsSu", "f=s", "R=l"
' now call the function
UserWrap.MessageBoxA Null, "MessageBoxA()", "A messagebox from VBScript...", 3
To use a function you need to "register" the exported function of your DLL.
To do this you need to call the register method with a first parameter containing a string object to the complete path of the DLL, a second parameter for the exported name of the function to use, and following three paremeters describing the functions declartion in a somehow obscure syntax.
i= describes the number and data type of the functions parameters.
f= describes the type of call: _stdcall or _cdecl. Default to _stdcall.
r= describes the return values data type.
The supported data types are:
Code Variant Description
a VT_DISPATCH IDispatch*
b VT_BOOL BOOL
c VT_I4 unsigned char
d VT_R8 8 byte real
f VT_R4 4 byte real
h VT_I4 HANDLE
k VT_UNKNOWN IUnknown*
l VT_I4 LONG
p VT_PTR pointer
r VT_LPSTR string by reference
s VT_LPSTR string
t VT_I2 SHORT
u VT_UINT UINT
w VT_LPWSTR wide string
Thus the Register method call used in the examples describes MessageBoxA like this:
_stdcall LONG MessageBoxA( HANDLE, LPSTR, LPSTR, UINT );
For a explanation of MessageBoxA look at the docs on MSDN.
Please read the DynaWrap docs for more sophisticated examples... But you might need Google translate, 'cos they are written in french ;-)
To be able to use a dll as ActiveXObject, it needs to be registered as COM object. There are some restrictions on this but if you have a code for this dll, it is certainly doable.
When you register your dll as COM object, it is assigned a name. You use this name to create an object. This example from MSDN uses excel since it is already registered if you installed office.
var ExcelApp = new ActiveXObject("Excel.Application");
var ExcelSheet = new ActiveXObject("Excel.Sheet");
// Make Excel visible through the Application object.
ExcelSheet.Application.Visible = true;
// Place some text in the first cell of the sheet.
ExcelSheet.ActiveSheet.Cells(1,1).Value = "This is column A, row 1";
// Save the sheet.
ExcelSheet.SaveAs("C:\\TEST.XLS");
// Close Excel with the Quit method on the Application object.
ExcelSheet.Application.Quit();
Apart from restriction of registering dll, using dll is no different from using it as c++ or c# dll. Note that, C# (or other .NET dlls) should be ComVisible to be used from javascript this way.
EDIT: The only other way of using C/C++ dll from javascript is swig interfaces. I have not used it, therefore I can only point you in that direction.
SWIG is a software development tool that connects programs written in
C and C++ with a variety of high-level programming languages. SWIG is
used with different types of target languages including common
scripting languages such as Javascript, Perl, PHP, Python, Tcl and
Ruby.

Call a function in a C DLL from VB: Access Violation

I'm trying to call a Dll function which looks like this (in the C/C++ Dll):
__declspec(dllexport) void execute(char** ReturnMsg, char* serverAddress, char* commandLine)
The VB 'wrapper' function looks like:
<DllImport("TPClient.dll", EntryPoint:="execute", CallingConvention:=CallingConvention.Cdecl, CharSet:=CharSet.Auto, ExactSpelling:=True)> _
Public Shared Sub tg_execute(<Out()> <MarshalAs(UnmanagedType.LPStr)> ByRef returnString As System.Text.StringBuilder, _
<MarshalAs(UnmanagedType.LPStr)> ByVal serverAddress As String, _
<MarshalAs(UnmanagedType.LPStr)> ByVal commandLine As String)
End Sub
The parameters are:
returnString: a string I need to get back from the function, result of the command sent;
serverAddress: a string, input only (an IP or DNS name); and
commandLine: a string, input only (any command)
To call the function, I make a StringBuilder object with some sufficient capacity to use as the returnString variable:
Dim returnString As New System.Text.StringBuilder(128)
tg_execute(returnString, TextBox_serverName.Text.Trim, TextBox_Command.Text.Trim)
When I run the code, I do get the expected string in the returnString (as I can see in the debugger), however I also get an AccessViolationException. So, I get for example "2.6.30.8-x86" in returnString when I use the command "uname -r" in commandLine. But the code hangs due to the memory error.
Now I'm not too familiar with VB and P/Invoke, and I had to do some trial and error to get the arguments passed to the DLL (which I'm also writing and debugging). This is also how I ended up using the "MarshalAs(UnmanagedType.LPStr)" attributes. However now I don't know why I'm getting these memory errors.
I made some other attempts using IntPtr arguments, but I also couldn't get this working and gave up on that approach, as to my understanding the marshaling should be handled automatically (is that correct?).
Any help is much appreciated.
The return value char** ReturnMsg would suggest that ReturnMsg is a pointer to C string. This would imply that the native code was in charge of allocating the buffer. So StringBuilder is not appropriate here.
There is not actually enough information here to know how to call this function. What is missing is knowledge of which party is responsible for deallocating the string. It could be either party and I'm going to assume that the C code will do so, probably by means of the strings being statically allocated, e.g. constants.
Now, I have no experience with VB p/invoke so I hope you don't mind if I give you a C# version. I expect you can translate easily enough.
[DllImport("TPClient.dll", CallingConvention=CallingConvention.Cdecl,
CharSet=CharSet.Ansi, EntryPoint="execute", ExactSpelling=true)]
private static void tg_execute(out IntPtr returnString,
string serverAddress, string commandLine)
You call the function like this:
IntPtr returnStringPtr;
tg_execute(out returnStringPtr, serverAddress, commandLine);
string returnString = Marshal.PtrToStringAnsi(returnStringPtr);
Note that your character set was incorrect in the question. You have to use Ansi because the native code uses char. I also think that your MarshalAs attributes are spurious since you are just re-stating the default marshalling for those parameter types.
Now, if the native code expects the caller to deallocate the memory, then the native code would have to export a function to do so. If that's the case then you would call it passing returnStringPtr by value.