I'm trying to call a DLL written in C++ from a VB6 application.
Here's the C++ example code for calling the DLL.
char firmware[32];
int maxUnits = InitPowerDevice(firmware);
However, when I try to call it from VB6 I get the error bad DLL calling convention.
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" (ByRef firmware() As Byte) As Long
Dim firmware(32) As Byte
InitPowerDevice(firmware)
Edit: The C++ Prototype:
Name: InitPowerDevice
Parameters: firmware: returns firmware version in ?.? format in a character string (major revision and minor revision)
Return: >0 if successful. Returns number of Power devices connected
CLASS_DECLSPEC int InitPowerDevice(char firmware[]);
Been a long time, but I think you also need to change your C function to be stdcall.
// In the C code when compiling to build the dll
CLASS_DECLSPEC int __stdcall InitPowerDevice(char firmware[]);
' VB Declaration
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" _
(ByVal firmware As String) As Long
' VB Call
Dim fmware as String
Dim r as Long
fmware = Space(32)
r = InitPowerDevice(fmware)
I don't think VB6 supports calling cdecl functions in any normal way - there may be hacks for doing it. May be you can write a wrapper dll which wraps the cdecl function with a stdcall function and just forwards the call.
These are some hacks - but I haven't tried it.
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=49776&lngWId=1
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=62014&lngWId=1
You need to pass a pointer to the beginning of the array contents, not a pointer to the SAFEARRAY.
Perhaps what you need is either:
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
or
Public Declare Function InitPowerDevice CDecl Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
The CDecl keyword only works in a VB6 program compiled to native code. It never works in the IDE or in p-code EXEs.
Since your error is "bad calling convention", you should try changing the calling convention. C code use __cdecl by default, and IIRC VB6 had a Cdecl keyword you could use with Declare Function.
Otherwise you can change the C code to use __stdcall, or create a type library (.tlb) with the type information and calling convention. That might be better than Declare Function because you use the C datatypes when defining the type library, but VB6 recognizes them just fine.
As far as the argument type is concerned, firmware() As Byte with ByVal (not ByRef) should be fine.
Related
I have a DLL written in Delphi that I need to call from a C++ code. The signature of the procedure in Delphi is:
procedure GetDeviceName( No: integer;
Name: PChar;
Len: integer;
var Error: integer); stdcall;
From the code from the DLL I believed that I could call it with this block of code:
typedef void (__stdcall *GetDeviceNamePtr)(int, char*, int, int*);
GetDeviceNamePtr GetDeviceName = (GetDeviceNamePtr) GetProcAddress(m, "GetDeviceName");
char DeviceName[256];
int Error;
GetDeviceName(1, DeviceName, 256, &Error);
However, I've got an access violation. Should the last parameter of the procedure signature be a pointer to an integer or an integer ? I'm confused about the "var" keyword in the declaration of the procedure. I've checked the exported symbols of DLL and the procedure considered is properly exported. What's wrong in my procedure ?
Some more info regarding the environment:
It's a dll compiled with delphi 6.
ANSI only.
I'm debugging on the C++ side and the exception is right on the call to the function.
Nothing is returned from the function calling the DLL API.
From the fairly small amount of information you have provided, my initial thought is that you have declared the array DeviceName as a local variable so it's on the stack, and then overrun the stack giving you an AV when you try to return.
In modern versions of Delphi, Char is wchar_t and PChar is wchar_t* (so a Len of 256 might either mean 256 bytes or 128 characters).
var in the prototype indicates a parameter passed by reference, so I would expect int*.
Have you checked for variable sizes in the version of Delphi the DLL is written in and the calling application? If they think that Integer and int are different sizes (32bit vs. 64bit) your pointers will be garbage.
Are you able to step into the DLL in your debugger? If you can look at how the variables are being assigned when you enter the routine that should identify if you are passing what it expects.
And as a final thought - are you sure the Delphi DLL actually works? (I have been caught out by this before, and you could spend days on something that's not your problem!)
We have unmanaged 32bit c++ dll and we want to wrap it in COM code which is 64bit and written in VB.net. The unmanaged dll method has parameter with pointer datatype and we are not able to fetch required response from the methods. Also, code behind the dll is unknown
We have tried different types of datatypes in VB to match with C++ pointer datatype: WE tried - BYTE(), String, long, arraylist, intptr, uintptr, progress.open4GL.memptr, byte, integer etc..
This is how we import dll and parameters are (input string,input_output ptr,input_output ptr) and the return type is long. Issue is with ptr variables
<DllImport("wp2.dll")>
Public Shared Function WPStartJob(<MarshalAs(UnmanagedType.LPWStr)> typAvlista As String,
ByRef mPVariabelLista As IntPtr,
ByRef mPSubrapportLista As IntPtr) As Long
End Function
output of the method(for argument with pointer type) should be list of string values ex. "ListaS_Sub.rpt"
But we are getting null value with all types tried.
I am using Delphi7 and I am new in it.
I want to use function of Dll(Implemented in C++) in my Delphi Project.
I have a function declaration in C++ like- (given by third party)
Syntax
LPTSTR GetErrorString(LONG lErrorNumber)
Arguments
LONG lErrorNumber Error number
Result
LPTSTR Error string
But when I am passing a value in Delphi7 like
GetErrorString(310);
I am declaring it in my unit-
Function GetErrorString(lErrorNumber : LongInt): String;StdCall;
implementation
Function GetErrorString;external 'Third-Party.DLL';
I am receiving blank string instead of actual Error String. I don't know the exact data type of LPTSTR.
Also tell me the proper steps to use it in my project.
LPTSTR is just a pointer to raw character data. Delphi's equivilent is either PAnsiChar or PWideChar, depending on whether the DLL was compiled for Ansi or Unicode. LPTSTR is always Ansi in Delphi 2007 and earlier (which includes Delphi 7) and always Unicode in Delphi 2009 and later, so you may need to account for that. If the DLL was compiled for Unicode, you would have to ue PWideChar instead of LPTSTR. As such, it is better to use PAnsiChar and PWideChar directly instead of LPTSTR to avoid mismatches between different environments (unless the DLL exports separate versions of the function for both types, like most Win32 API functions do).
Also, depending on the actual calling convention being used by the DLL, the function may be using cdecl or stdcall. In the absence of an explicit calling convention, most C/C++ compilers use cdecl, but they could just as easily be using stdcall and just not document it. So you need to find out, because it makes a BIG difference because cdecl and stdcall have different semantics for stack management and parameter passing.
So, with that said, the correct function declaration will be either:
function GetErrorString(lErrorNumber: Integer): PAnsiChar; cdecl; external 'filename.dll';
Or:
function GetErrorString(lErrorNumber: Integer): PWideChar; cdecl; external 'filename.dll';
Or:
function GetErrorString(lErrorNumber: Integer): PAnsiChar; stdcall; external 'filename.dll';
Or:
function GetErrorString(lErrorNumber: Integer): PWideChar; stdcall; external 'filename.dll';
You will have to do some research to find out whether the DLL is using Ansi or Unicode, and whether it is using cdecl or stdcall, if the documentation does not specifically state that information.
First, a Delphi string is refcounted, and thus something else than a pointer to char (LPTSTR). I suggest you avoid those traps as beginner, and go for straight pointers.
Second LPTSTR is a pointer to a one byte char (LPSTR), or a pointer to a two byte char (LPWSTR) depending on if UNICODE is defined.
So the correct solution is to make the function return pansichar or pwidechar, depending on how UNICODE was defined in your C++ program.
If you start passing character buffers between different languages, make sure they use the same allocator to (de)allocate them, or make sure that each module frees the allocations that it makes.
What is the correct VB6 declaration for this C++ function?
LPCWSTR* MW_ListReaders(_ULONG Context, int* NumberOfReaders);
The following gave me "Bad DLL calling convention":
Private Declare Function ListReaders Lib "MyDLL.dll" (ByVal Context As Long, _
ByRef NumberOfReaders As Integer) As Long
There is no calling convention specified in that C++ declaration. Most C/C++ compilers default to __cdecl. If the function does actually use __cdecl then you will not be able to call it in VB6:
How To Call C Functions That Use the _cdecl Calling Convention
It is not possible to directly call a C function in a DLL if that function uses the _cdecl calling convention. This is because Visual Basic uses the _stdcall calling convention for calling functions. This is a problem because if _cdecl is used, the calling function is responsible for cleaning up the stack. However, if _stdcall is used, the called function is responsible for cleaning up the stack.
NOTE: An .EXE file created in Visual Basic will allow you to call a DLL function that has been declared with the _cdecl calling convention without an error. It is only when you try to call such a function when running a program from the Visual Basic IDE, that Visual Basic generates the following error:
Run-time Error '49':
Bad DLL Calling Convention
The fact that the EXE version allows you to call such functions has been confirmed to be a bug by Microsoft. You should not rely on this behavior as this might change in future versions of Visual Basic.
In addition to Remy's answer, you have also got the Vb declaration slightly wrong:
Private Declare Function ListReaders Lib "MyDLL.dll" (ByVal Context As Long, ByRef NumberOfReaders As Long) As Long
"Integer" is a 2 byte integer in vb.
I have tried a lot of things but I cannot get this to work. I can pass and receive ordinary strings (char*) to C++, but I cannot receive Unicode strings (w_char_t *) in C++.
Here are some bits of the code on c++
__declspec(dllimport) int __stdcall readFile(const w_char_t *file_path)
on VB.net
Public Declare Function readFile Lib "MyDll.dll" Alias "_readFile#4" (ByVal file_path As String) As Integer
when I change w_char_t to char I receive the correct string.
I have found a lot of material on VB6, but not for VB.net
Any help greatly appreciated.
Leon
Public Declare Unicode Function readFile Lib "MyDll.dll" Alias "_readFile#4" (ByVal file_path As String) As Integer
Also, your C++ code has dllexport not dllimport, right?
Try converting UnicodeString to String (by using some System Defined Function), and then pass that string or char* to C++ DLL.
Hope this will work.