-
Feb 24th, 2015, 09:20 AM
#1
Thread Starter
New Member
C++ DLL to VB6
Hi.
I have an relay card control dll and also header file for it for c++. I would like to use it in VB6. May you please help how to make a definitions module for the dll. Please find the header below:
#ifndef USB_RELAY_DEVICE_H__
#define USB_RELAY_DEVICE_H__
#pragma comment(lib, "setupapi.lib")
#ifdef __cplusplus
extern "C" {
#endif
//#pragma comment(lib, "setupapi.lib")
#ifdef _WIN32
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API
#endif
enum usb_relay_device_type
{
USB_RELAY_DEVICE_ONE_CHANNEL = 1,
USB_RELAY_DEVICE_TWO_CHANNEL = 2,
USB_RELAY_DEVICE_FOUR_CHANNEL = 4,
USB_RELAY_DEVICE_EIGHT_CHANNEL = 8
};
/*usb relay board info structure*/
struct usb_relay_device_info
{
unsigned char *serial_number;
char *device_path;
usb_relay_device_type type;
usb_relay_device_info* next;
};
/*init the USB Relay Libary
@returns: This function returns 0 on success and -1 on error.
*/
int EXPORT_API usb_relay_init(void);
/*Finalize the USB Relay Libary.
This function frees all of the static data associated with
USB Relay Libary. It should be called at the end of execution to avoid
memory leaks.
@returns:This function returns 0 on success and -1 on error.
*/
int EXPORT_API usb_relay_exit(void);
/*Enumerate the USB Relay Devices.*/
struct usb_relay_device_info EXPORT_API * usb_relay_device_enumerate(void);
/*Free an enumeration Linked List*/
void EXPORT_API usb_relay_device_free_enumerate(struct usb_relay_device_info*);
/*open device that serial number is serial_number*/
/*@return: This funcation returns a valid handle to the device on success or NULL on failure.*/
/*e.g: usb_relay_device_open_with_serial_number("abcde", 5")*/
int EXPORT_API usb_relay_device_open_with_serial_number(const char *serial_number, unsigned len);
/*open a usb relay device
@return: This funcation returns a valid handle to the device on success or NULL on failure.
*/
int EXPORT_API usb_relay_device_open(struct usb_relay_device_info* device_info);
/*close a usb relay device*/
void EXPORT_API usb_relay_device_close(int hHandle);
/*open a relay channel on the USB-Relay-Device
@paramter: index -- which channel your want to open
hHandle -- which usb relay device your want to operate
@returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
*/
int EXPORT_API usb_relay_device_open_one_relay_channel(int hHandle, int index);
/*open all relay channel on the USB-Relay-Device
@paramter: hHandle -- which usb relay device your want to operate
@returns: 0 -- success; 1 -- error
*/
int EXPORT_API usb_relay_device_open_all_relay_channel(int hHandle);
/*close a relay channel on the USB-Relay-Device
@paramter: index -- which channel your want to close
hHandle -- which usb relay device your want to operate
@returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
*/
int EXPORT_API usb_relay_device_close_one_relay_channel(int hHandle, int index);
/*close all relay channel on the USB-Relay-Device
@paramter: hHandle -- which usb relay device your want to operate
@returns: 0 -- success; 1 -- error
*/
int EXPORT_API usb_relay_device_close_all_relay_channel(int hHandle);
/*
status bit: High --> Low 0000 0000 0000 0000 0000 0000 0000 0000, one bit indicate a relay status.
the lowest bit 0 indicate relay one status, 1 -- means open status, 0 -- means closed status.
bit 0/1/2/3/4/5/6/7/8 indicate relay 1/2/3/4/5/6/7/8 status
@returns: 0 -- success; 1 -- error
*/
int EXPORT_API usb_relay_device_get_status(int hHandle, unsigned int *status);
#ifndef EXPORT_DLL
int EXPORT_API usb_relay_device_set_serial(int hHandle, char serial[5]);
#endif
#ifdef __cplusplus
}
#endif
#endif //end of ifdef __cplusplus
-
Feb 24th, 2015, 11:40 AM
#2
Re: C++ DLL to VB6
You have to declare the functions exported by the DLL in your VB6 application. For example:-
c Code:
int EXPORT_API usb_relay_device_get_status(int hHandle, unsigned int *status);
The above should look like this in VB6:-
vb Code:
Declare Function usb_relay_device_get_status Lib "libname.dll" (ByVal hHandle as Long, ByRef status As Long) As Long
-
Feb 24th, 2015, 04:27 PM
#3
Re: C++ DLL to VB6
Try this:
Code:
Option Explicit 'In a standard (.BAS) module
Public Enum USB_Relay_Device_Type
USB_RELAY_DEVICE_ONE_CHANNEL = 1
USB_RELAY_DEVICE_TWO_CHANNEL = 2
USB_RELAY_DEVICE_FOUR_CHANNEL = 4
USB_RELAY_DEVICE_EIGHT_CHANNEL = 8
End Enum
Public Type USB_Relay_Device_Info
Serial_Number As Long
Device_Path As Long
Type As USB_Relay_Device_Type
Next As Long
End Type
Public Declare Function usb_relay_device_close_all_relay_channel Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long) As Long
Public Declare Function usb_relay_device_close_one_relay_channel Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long, ByVal Index As Long) As Long
Public Declare Function usb_relay_device_enumerate Lib "FileName_Of_Your_C++_DLL" () As Long
Public Declare Function usb_relay_device_get_status Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long, ByRef Status As Long) As Long
Public Declare Function usb_relay_device_open Lib "FileName_Of_Your_C++_DLL" (ByRef Device_Info As USB_Relay_Device_Info) As Long
Public Declare Function usb_relay_device_open_all_relay_channel Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long) As Long
Public Declare Function usb_relay_device_open_one_relay_channel Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long, ByVal Index As Long) As Long
Public Declare Function usb_relay_device_open_with_serial_number Lib "FileName_Of_Your_C++_DLL" (ByVal Serial_Number As String, ByVal Length As Long) As Long
Public Declare Function usb_relay_device_set_serial Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long, ByVal Serial As Any) As Long
Public Declare Function usb_relay_exit Lib "FileName_Of_Your_C++_DLL" () As Long
Public Declare Function usb_relay_init Lib "FileName_Of_Your_C++_DLL" () As Long
Public Declare Sub usb_relay_device_close Lib "FileName_Of_Your_C++_DLL" (ByVal hHandle As Long)
Public Declare Sub usb_relay_device_free_enumerate Lib "FileName_Of_Your_C++_DLL" (ByRef Device_Info As USB_Relay_Device_Info)
BTW, the VB6 manual has a chapter that teaches VB6 programmers how to work with standard DLLs written in other languages (such as C/C++). See Accessing DLLs and the Windows API.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)
-
Jan 9th, 2021, 12:30 PM
#4
Lively Member
Re: C++ DLL to VB6
This thread is almost 6 years old.. but I have the same issue/question. The original thread starter apparently never replied, so I don't know if e was successful with the answer.
I want to drive these cheap AliExpres USB relay cards, such as https://www.aliexpress.com/item/4000...archweb201603_
I found C++ sources including the usb_relay_device.dll
Now I'm struggling in using this dll with VB6.
I've tried as suggested in previous reply:
Code:
Public Declare Function usb_relay_device_open_with_serial_number Lib "usb_relay_device.dll" _
(ByVal Serial_Number As String, ByVal Length As Long) As Long
And call the function as:
Code:
MsgBox usb_relay_device_open_with_serial_number("BITFT", 5)
But this results in the error message "Bad DLL calling convention"
Anybody any idea if I can use this dll with VB6??
_Wim_
-
Jan 9th, 2021, 12:46 PM
#5
Re: C++ DLL to VB6
It sounds like you've got the c++ code, can you post the function header from the c++ code? Also, you might try changing the 5 to 5& to use a long value explicitly.
-
Jan 9th, 2021, 12:58 PM
#6
Lively Member
Re: C++ DLL to VB6
The header file was already included in original message. Bottom-line the important stuff is:
Code:
#define EXPORT_API __declspec(dllexport)
int EXPORT_API usb_relay_device_open_with_serial_number(const char *serial_number, unsigned len);
adding the "&" declaring the variable explicitly as long, doesn't help
Code:
MsgBox usb_relay_device_open_with_serial_number("BITFT", 5&)
_Wim_
-
Jan 9th, 2021, 01:17 PM
#7
Re: C++ DLL to VB6
Sorry, I didn't notice that the original post contained the header. I blame the lack of code formatting in the post and not my own laziness.
You should find your answer here: https://www.vbforums.com/showthread....C-dll-s-in-VB6
Good luck.
-
Jan 9th, 2021, 01:39 PM
#8
Lively Member
Re: C++ DLL to VB6
Hmmm, if I read that thread correctly, it says:
VB only supports stdcall, but the default calling convention in c/c++ is cdecl
... and it seems that this dll uses cdecl..
So it can't be done??
_Wim_
-
Jan 9th, 2021, 02:53 PM
#9
Re: C++ DLL to VB6
Originally Posted by _Wim_
Hmmm, if I read that thread correctly, it says:
... and it seems that this dll uses cdecl..
So it can't be done??
_Wim_
No idea, but it seemed like that other thread encompassed your scenario fairly closely, and had much more discussion than this one. Someone else with more experience with calling functions from C++ dll's will need to chime in to assist further.
Good luck.
-
Jan 9th, 2021, 03:20 PM
#10
Re: C++ DLL to VB6
I don't know vb6 but the VB.NET equivalent is this:
Code:
<DllImport("usb_relay_device.dll", EntryPoint:="usb_relay_device_open_with_serial_number", SetLastError:=True, CharSet:=CharSet.Ansi, ExactSpelling:=True, CallingConvention:=CallingConvention.Cdecl)>
Public Shared Function usb_relay_device_open_with_serial_number(
<MarshalAs(UnmanagedType.LPStr)> ByVal serial_number As String, ByVal len As Integer) As Integer
ἄνδρα μοι ἔννεπε, μοῦσα, πολύτροπον, ὃς μάλα πολλὰ
πλάγχθη, ἐπεὶ Τροίης ἱερὸν πτολίεθρον ἔπερσεν·
-
Jan 9th, 2021, 04:53 PM
#11
Lively Member
Re: C++ DLL to VB6
As far as I know, there is no 'DLLImport' function in VB6.. But correct me if I'm wrong.
I have some source code for the dll (friom GitHub), that mentions:
LIBRARY USB_RELAY_DEVICE
VERSION 1.0
EXPORTS
; All exports are CDECL
The thread pointed to me, showed actually:
VB only supports stdcall, but the default calling convention in c/c++ is cdecl
(actually VB also supports cdecl, but for that you must compile your project to native code).
...is that really an option to use dll's that are using CDECL ?? That is "compile to native code"? How? Project properties are standard set to "Compile to native code".. Any expert out there who can help me?
_Wim_
-
Jan 9th, 2021, 05:06 PM
#12
Re: C++ DLL to VB6
The thread pointed to you also notes how you should be passing a String to a C++ function that expects a String pointer. Did you make that change to your VB6 code?
The other note basically means that if you are encountering this error when you are running your VB6 program from within the VB6 IDE, then you should compile your program and try running the exe file and see if the issue persists, because it may well function properly at that point.
-
Jan 10th, 2021, 01:24 AM
#13
Re: C++ DLL to VB6
Originally Posted by _Wim_
...is that really an option to use dll's that are using CDECL ?? That is "compile to native code"? How? Project properties are standard set to "Compile to native code".. Any expert out there who can help me?
_Wim_
I recommend you PM either Schmidt or The Trick. If someone knows how to hack VB6 to do this, it would be one of them.
Originally Posted by OptionBase1
The thread pointed to you also notes how you should be passing a String to a C++ function that expects a String pointer. Did you make that change to your VB6 code?
The other note basically means that if you are encountering this error when you are running your VB6 program from within the VB6 IDE, then you should compile your program and try running the exe file and see if the issue persists, because it may well function properly at that point.
I'm a little rusty on this stuff but I'm not entirely sure that using StrPtr to pass the String is the right way. The reason is because that C function looks like expects a C style String. VB6 uses a COM type String referred to as a BSTR. I don't know if StrPtr would convert that String. I don't expect it would. If my memory serves me right, I think you're supposed to pass the String from VB6 by value as you normally would to any VB6 function except in the case of a function that was imported from a DLL, VB6 would automatically marshal it correctly as a pointer to an ANSI String. For Unicode Strings, you have to do a little magic with StrConv but my memory is very fuzzy on the details. One of the VB6 regulars should probably chime in on this. I used to know this stuff quite well but I haven't use VB6 seriously in like 6 years.
Last edited by Niya; Jan 10th, 2021 at 01:28 AM.
-
Jan 10th, 2021, 03:24 AM
#14
Re: C++ DLL to VB6
Originally Posted by _Wim_
...is that really an option to use dll's that are using CDECL ?...
You can call cdecl-defined functions also without compiling to a native-binary first,
when you use the DispCallFunc-API (no "hacking" needed) - e.g. via the module below:
Code:
Option Explicit
Private Declare Function DispCallFunc Lib "oleaut32" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function lstrlenA Lib "kernel32" (ByVal lpString As Long) As Long
Private Declare Function lstrlenW Lib "kernel32" (ByVal lpString As Long) As Long
Private Declare Sub RtlMoveMemory Lib "kernel32" (Dst As Any, Src As Any, ByVal BLen As Long)
Private Enum CALLINGCONVENTION_ENUM
CC_FASTCALL
CC_CDECL
CC_PASCAL
CC_MACPASCAL
CC_STDCALL
CC_FPFASTCALL
CC_SYSCALL
CC_MPWCDECL
CC_MPWPASCAL
End Enum
Private LibHdls As New Collection, VType(0 To 63) As Integer, VPtr(0 To 63) As Long
Public Function stdCallW(sDll As String, sFunc As String, ByVal RetType As VbVarType, ParamArray P() As Variant)
Dim i As Long, V(), HRes As Long
V = P 'make a copy of the params, to prevent problems with VT_Byref-Members in the ParamArray
For i = 0 To UBound(V)
If VarType(P(i)) = vbString Then V(i) = StrPtr(P(i))
VType(i) = VarType(V(i))
VPtr(i) = VarPtr(V(i))
Next i
HRes = DispCallFunc(0, GetFuncPtr(sDll, sFunc), CC_STDCALL, RetType, i, VType(0), VPtr(0), stdCallW)
If HRes Then Err.Raise HRes
End Function
Public Function cdeclCallW(sDll As String, sFunc As String, ByVal RetType As VbVarType, ParamArray P() As Variant)
Dim i As Long, pFunc As Long, V(), HRes As Long
V = P 'make a copy of the params, to prevent problems with VT_Byref-Members in the ParamArray
For i = 0 To UBound(V)
If VarType(P(i)) = vbString Then V(i) = StrPtr(P(i))
VType(i) = VarType(V(i))
VPtr(i) = VarPtr(V(i))
Next i
HRes = DispCallFunc(0, GetFuncPtr(sDll, sFunc), CC_CDECL, RetType, i, VType(0), VPtr(0), cdeclCallW)
If HRes Then Err.Raise HRes
End Function
Public Function stdCallA(sDll As String, sFunc As String, ByVal RetType As VbVarType, ParamArray P() As Variant)
Dim i As Long, pFunc As Long, V(), HRes As Long
V = P 'make a copy of the params, to prevent problems with VT_Byref-Members in the ParamArray
For i = 0 To UBound(V)
If VarType(P(i)) = vbString Then P(i) = StrConv(P(i), vbFromUnicode): V(i) = StrPtr(P(i))
VType(i) = VarType(V(i))
VPtr(i) = VarPtr(V(i))
Next i
HRes = DispCallFunc(0, GetFuncPtr(sDll, sFunc), CC_STDCALL, RetType, i, VType(0), VPtr(0), stdCallA)
For i = 0 To UBound(P) 'back-conversion of the ANSI-String-Results
If VarType(P(i)) = vbString Then P(i) = StrConv(P(i), vbUnicode)
Next i
If HRes Then Err.Raise HRes
End Function
Public Function cdeclCallA(sDll As String, sFunc As String, ByVal RetType As VbVarType, ParamArray P() As Variant)
Dim i As Long, pFunc As Long, V(), HRes As Long
V = P 'make a copy of the params, to prevent problems with VT_Byref-Members in the ParamArray
For i = 0 To UBound(V)
If VarType(P(i)) = vbString Then P(i) = StrConv(P(i), vbFromUnicode): V(i) = StrPtr(P(i))
VType(i) = VarType(V(i))
VPtr(i) = VarPtr(V(i))
Next i
HRes = DispCallFunc(0, GetFuncPtr(sDll, sFunc), CC_CDECL, RetType, i, VType(0), VPtr(0), cdeclCallA)
For i = 0 To UBound(P) 'back-conversion of the ANSI-String-Results
If VarType(P(i)) = vbString Then P(i) = StrConv(P(i), vbUnicode)
Next i
If HRes Then Err.Raise HRes
End Function
Public Function vtblCall(pUnk As Long, ByVal vtblIdx As Long, ParamArray P() As Variant)
Dim i As Long, V(), HRes As Long
If pUnk = 0 Then Exit Function
V = P 'make a copy of the params, to prevent problems with VT_ByRef-Members in the ParamArray
For i = 0 To UBound(V)
VType(i) = VarType(V(i))
VPtr(i) = VarPtr(V(i))
Next i
HRes = DispCallFunc(pUnk, vtblIdx * 4, CC_STDCALL, vbLong, i, VType(0), VPtr(0), vtblCall)
If HRes Then Err.Raise HRes
End Function
Public Function GetFuncPtr(sDll As String, sFunc As String) As Long
Static hLib As Long, sLib As String
If sLib <> sDll Then 'just a bit of caching, to make resolving libHdls faster
sLib = sDll
On Error Resume Next
hLib = 0
hLib = LibHdls(sLib)
On Error GoTo 0
If hLib = 0 Then
hLib = LoadLibrary(sLib)
If hLib = 0 Then Err.Raise vbObjectError, , "Dll not found (or loadable): " & sLib
LibHdls.Add hLib, sLib '<- cache it under the dll-name for the next call
End If
End If
GetFuncPtr = GetProcAddress(hLib, sFunc)
If GetFuncPtr = 0 Then Err.Raise 453, , "EntryPoint not found: " & sFunc & " in: " & sLib
End Function
Public Function GetBStrFromPtr(lpSrc As Long, Optional ByVal ANSI As Boolean) As String
Dim SLen As Long
If lpSrc = 0 Then Exit Function
If ANSI Then SLen = lstrlenA(lpSrc) Else SLen = lstrlenW(lpSrc)
If SLen Then GetBStrFromPtr = Space$(SLen) Else Exit Function
Select Case ANSI
Case True: RtlMoveMemory ByVal GetBStrFromPtr, ByVal lpSrc, SLen
Case Else: RtlMoveMemory ByVal StrPtr(GetBStrFromPtr), ByVal lpSrc, SLen * 2
End Select
End Function
Public Sub CleanupLibHandles() 'not really needed - but callable (usually at process-shutdown) to clear things up
Dim LibHdl
For Each LibHdl In LibHdls: FreeLibrary LibHdl: Next
Set LibHdls = Nothing
End Sub
Using the drop-in-module above, your code could then look like:
Code:
Function OpenDeviceWithSerial(SerialNumber As String) As Long
OpenDeviceWithSerial = cdeclCallA(YourDllFilePath, "usb_relay_device_open_with_serial_number", _
vbLong, SerialNumber, Len(SerialNumber))
End Function
HTH
Olaf
Last edited by Schmidt; Jan 10th, 2021 at 03:31 AM.
-
Jan 10th, 2021, 04:37 AM
#15
Re: C++ DLL to VB6
You can use a typelibrary with cdecl declarations. This way is most fastest (direct call) but it doesn't work in the IDE. To use cdecl in the IDE you could use either DispCallFunc as Olaf demonstrated above or a stack fixer like that.
-
Jan 10th, 2021, 04:53 AM
#16
Lively Member
Re: C++ DLL to VB6
Fantastic! That seems to work. I copied all your "drop-in-code" into a cdecl.bas module (without even trying to understand what it does..), and used:
Code:
Public Function OpenDeviceWithSerial(SerialNumber As String) As Long
OpenDeviceWithSerial = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open_with_serial_number", _
vbLong, SerialNumber, Len(SerialNumber))
End Function
(Note: I have the dll copied into SysWOW64 directory to avoid absolute paths)
Then I tried:
Code:
MsgBox OpenDeviceWithSerial("BITFT")
(These USB relays apparently all have a 5 digit code. Mine happens to be BITFT..)
And it returns some high number, the hWnd handle I assume. Without the USB relay it returns 0, so it appears to work this way. Next will be the other functions, like enumerate, etc.. But I assume that should all be possible!! Thanks very much, Olaf
-
Jan 10th, 2021, 05:06 AM
#17
Re: C++ DLL to VB6
@Olaf: Do VB6 Declares call FreeLibrary on tear-down?
IMO the LibHdls collection can be safely replaced by a simple hLib = GetModuleHandle(sLib) and skip the CleanupLibHandles sub altogether.
This will allow OP to manually LoadLibrary the DLL in Sub Main/Form_Load (from App.Path or App.Path & "\External") and continue calling cdeclCallA with simple "usb_relay_device.dll" with no path specified without requiring a copy of the DLL somewhere in PATH.
This approach to DLL discovery works fine with VB6 Declares so might be useful to support it here too.
cheers,
</wqw>
-
Jan 10th, 2021, 05:16 AM
#18
Re: C++ DLL to VB6
I have to admit, I didn't know it could be done like this. My first instinct was to actually do the CDECL call manually through assembly. I was thinking of setting up a piece of assembly code(self modifying code) in memory that can be called using STDCALL and the assembly code would call the function using the CDECL calling convention. I've experimented with something similar in .Net and it's actually not as difficult as I imagined it would be.
Though come to think of it, as I'm sitting here right now I can't think of a way to call the assembly code from VB6. .Net's Marshal class can wrap an unmanaged function pointer in a delegate so it can be called like any other function. I can't remember if VB6 has a way to do that. Oh well. Interesting problem to brainstorm.
-
Jan 10th, 2021, 05:31 AM
#19
Re: C++ DLL to VB6
Originally Posted by Niya
I have to admit, I didn't know it could be done like this. My first instinct was to actually do the CDECL call manually through assembly. I was thinking of setting up a piece of assembly code(self modifying code) in memory that can be called using STDCALL and the assembly code would call the function using the CDECL calling convention. I've experimented with something similar in .Net and it's actually not as difficult as I imagined it would be.
The Trick's link above points to a thread here where several such trampolines were discussed recently, incl. self-modifying ones.
The trampolines seem to be the fastest approach to bridging stdcall and cdecl calling conventions and what I would personally implement but many people prefer to not trigger A/Vs with "virus-like" techniques so use DispCallFunc.
cheers,
</wqw>
-
Jan 10th, 2021, 07:02 AM
#20
Re: C++ DLL to VB6
Originally Posted by _Wim_
Fantastic! That seems to work. I copied all your "drop-in-code" into a cdecl.bas module (without even trying to understand what it does..), and used:
Code:
Public Function OpenDeviceWithSerial(SerialNumber As String) As Long
OpenDeviceWithSerial = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open_with_serial_number", _
vbLong, SerialNumber, Len(SerialNumber))
End Function
(Note: I have the dll copied into SysWOW64 directory to avoid absolute paths)
You could also place your Device-Dll in a "Bin-Path" - relative to the Executable...
(that's what I usually prefer, to make Apps XCopy-deployable from a Zip).
Your Dll-Path-specifier could then be e.g.: cdeclCallA(App.Path & "\Bin\usb_relay_device.dll", ...
Originally Posted by _Wim_
Then I tried:
Code:
MsgBox OpenDeviceWithSerial("BITFT")
(These USB relays apparently all have a 5 digit code. Mine happens to be BITFT..)
And it returns some high number, the hWnd handle I assume.
It's a Device-Handle (a so called "opaque type"... similar to a hWnd - but not "a hWnd").
Originally Posted by _Wim_
Without the USB relay it returns 0, so it appears to work this way.
Next will be the other functions, like enumerate, etc.. But I assume that should all be possible!!
I'd suggest to encapsulate this in a nice little Class...
(holding the Device-handle internally, so that you can avoid it in the Parameter-Defs of your Class-Methods).
Another advantage of a Class is, that it will "auto-cleanup" the internal Handle (closing the Device), when the Class-Instance is terminated.
E.g. (as a starting-point):
Code:
Option Explicit
Const retVoid As Long = 0
Private mDll As String, mHdl As Long
Private Sub Class_Initialize()
mDll = App.Path & "\Bin\usb_relay_device.dll" 'specify the Dll-Path
End Sub
Public Function OpenDeviceWithSerial(SerialNumber As String) As Boolean
CloseDevice
mHdl = cdeclCallA(mDll, "usb_relay_device_open_with_serial_number", _
vbLong, SerialNumber, Len(SerialNumber))
If mHdl Then OpenDeviceWithSerial = True 'now only returning a Boolean (the Handle is kept internally)
End Function
'... your other Public Methods ...
' which don't have to define the Handle as a Parameter, since it is kept Class-internally
Public Sub CloseDevice()
If mHdl = 0 Then Exit Sub
cdeclCallA mDll, "usb_relay_device_close", retVoid, mHdl 'note the void-return type
mHdl = 0
End Sub
Private Sub Class_Terminate()
CloseDevice 'auto-close when the class-instance goes out of scope
End Sub
As for performance (since others brought it up)...
It is true that the DispCallFunc-approach has a higher overhead, e.g. compared to using (cdecl-marked) TypeLib-defs,
but this is only relevant in scenarios, where you have to perform thousands of Dll-calls in a high frequency (in some inner loop).
To switch a few relais on and off occasionally, that's a scenario where the overhead is not really noticable.
@wqweto
AFAIK FreeLibrary is not really necessary, since all these "Module-handles" are cleaned up anyways, when a Process terminates.
IMO the API is needed only in "Plugin-like scenarios" (e.g. when you want to overwrite a Plugin-Dll in your App-specific Plugin-Directory, whilst the App-Process is still running).
Olaf
Last edited by Schmidt; Jan 10th, 2021 at 07:25 AM.
-
Jan 10th, 2021, 07:08 AM
#21
Re: C++ DLL to VB6
Originally Posted by wqweto
The trampolines seem to be the fastest approach to bridging stdcall and cdecl calling conventions and what I would personally implement but many people prefer to not trigger A/Vs with "virus-like" techniques so use DispCallFunc.
cheers,
</wqw>
You can use a precompiled code.
-
Jan 10th, 2021, 07:11 AM
#22
Re: C++ DLL to VB6
Originally Posted by wqweto
The Trick's link above points to a thread here where several such trampolines were discussed recently, incl. self-modifying ones.
The trampolines seem to be the fastest approach to bridging stdcall and cdecl calling conventions and what I would personally implement but many people prefer to not trigger A/Vs with "virus-like" techniques so use DispCallFunc.
cheers,
</wqw>
Wow, your Call_ultow and pvPatchTrampoline are very clever. It was very creative how you used the pointer to one of your own module's functions as a means of executing self-modified code. Not gonna lie, it's one of the most clever work-arounds I've seen in a while. I gotta come back and rep you for that piece of gymnastics.
-
Jan 10th, 2021, 08:14 AM
#23
Re: C++ DLL to VB6
Originally Posted by Niya
Wow, your Call_ultow and pvPatchTrampoline are very clever. It was very creative how you used the pointer to one of your own module's functions as a means of executing self-modified code. Not gonna lie, it's one of the most clever work-arounds I've seen in a while. I gotta come back and rep you for that piece of gymnastics.
All kudos to The Trick -- I just implemented the idea w/ the empty param.
cheers,
</wqw>
-
Jan 10th, 2021, 10:08 AM
#24
Lively Member
Re: C++ DLL to VB6
...ok, next 'challenge' ... One of the functions return a struct.
So I have this code in a module (.bas):
Code:
Public Enum USB_Relay_Device_Type
USB_RELAY_DEVICE_ONE_CHANNEL = 1
USB_RELAY_DEVICE_TWO_CHANNEL = 2
USB_RELAY_DEVICE_FOUR_CHANNEL = 4
USB_RELAY_DEVICE_EIGHT_CHANNEL = 8
End Enum
Public Type USB_Relay_Device_Info
Serial_Number As Long
Device_Path As Long
Type As USB_Relay_Device_Type
Next As Long
End Type
Public Function EnumerateDevice() As USB_Relay_Device_Info
Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_enumerate", vbUserDefinedType, 0)
End Function
But that doesn't work when I use:
Code:
Option Explicit
Dim DevInfo As USB_Relay_Device_Info
Private Sub Form_Load()
DevInfo = EnumerateDevice()
End Sub
So return type is a userdefined type, and there are no parameters, that's why I used 0 for the parameters..
What's wrong here??? Your help is very much appreciated...
_Wim_
-
Jan 10th, 2021, 10:33 AM
#25
Re: C++ DLL to VB6
Please post the C/C++ function declaration like this
pusb_relay_device_info_t USBRL_API usb_relay_device_enumerate(void)
So the retval is *pointer* to this USB_Relay_Device_Info UDT. You can treat pointers as glorified Longs like this
Dim lPtr As Long
lPtr = cdeclCallA("usb_relay_device.dll", "usb_relay_device_enumerate", vbLong, 0)
And then use CopyMemory to dereference it
Dim uRetVal As USB_Relay_Device_Info
Call CopyMemory(uRetVal, ByVal lPtr, Len(uRetVal))
cheers,
</wqw>
-
Jan 10th, 2021, 11:18 AM
#26
Lively Member
Re: C++ DLL to VB6
Hmm this is getting a bit complicated for me to understand. First the 'cdeclCallA' function doesn't return anything, as far as I see. [EDIT: I read from some helpfiles that "DispCallFunc" returns the function result. I'll try your suggestion]
So lPtr = cdeclCallA(.. won't do anything!?
As a matter of fact I'm also trying the GetStatus routine, that *returns* some value. So I used 'ByRef', but that crashes VB6 IDE...
Code:
Public Function GetStatus(ByVal hHandle As Long, ByRef Status As Long) As Long
Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_get_status", vbLong, hHandle, Status)
End Function
..could use some (more) help here ... :-)
_Wim_
Last edited by _Wim_; Jan 10th, 2021 at 12:29 PM.
-
Jan 10th, 2021, 12:41 PM
#27
Re: C++ DLL to VB6
There is an extra 0 in cdeclCallA call in my reply above that has to be removed.
If usb_relay_device_enumerate returns NULL then it probably means some kind of erroe occurred.
-
Jan 10th, 2021, 01:01 PM
#28
Lively Member
Re: C++ DLL to VB6
OK, what about this:
Code:
Public Function EnumerateDevice() As USB_Relay_Device_Info
Dim lPtr As Long, uRetVal As USB_Relay_Device_Info
lPtr = cdeclCallA("usb_relay_device.dll", "usb_relay_device_enumerate", vbLong)
Call CopyMemory(uRetVal, ByVal lPtr, Len(uRetVal))
EnumerateDevice = uRetVal
End Function
And I call it with:
Code:
Dim DevInfo As USB_Relay_Device_Info
DevInfo = EnumerateDevice
This crashes IDE. Single stepping reveiled that lPtr=0 after calling cdeclCallA. So the CopyMemory crashes...
Now what?
_Wim_
-
Jan 10th, 2021, 01:37 PM
#29
Re: C++ DLL to VB6
That's what I meant by if usb_relay_device_enumerate returns NULL then it probably means some kind of error occurred within the DLL.
NULL retval usually indicates "unable to retrieve info" for some reason.
Best practice is your wrapper to check for NULL and not crash but indicate error by returning false or raising an error on its own.
cheers,
</wqw>
-
Jan 10th, 2021, 01:51 PM
#30
Re: C++ DLL to VB6
Originally Posted by _Wim_
This crashes IDE. Single stepping reveiled that lPtr=0 after calling cdeclCallA. So the CopyMemory crashes...
Now what?
Did you call usb_relay_init?
-
Jan 10th, 2021, 01:59 PM
#31
Lively Member
Re: C++ DLL to VB6
Thanks, indeed the function returned 0, because I first has opened the device. Apparently you can't enumerate if device is opened... Makes sense. Indeed the wrapper should check for a non-zero value. I'll add. At least I have some values returned by the function, now I'll have to find out how to handle the device info struct...
Meanwhile any comments on why the "usb_relay_device_get_status" crashes? This DLL routine returns a long value back into the parameter 'Status', not as function result, as far as I can see. So I thought using ByRef instead of ByVal. But that causes to crashes VB6 IDE..
Code:
Public Function GetStatus(ByVal hHandle As Long, ByRef Status As Long) As Long
Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_get_status", vbLong, hHandle, Status)
End Function
... to be continued
-
Jan 10th, 2021, 02:17 PM
#32
Re: C++ DLL to VB6
Meanwhile any comments on why the "usb_relay_device_get_status" crashes? This DLL routine returns a long value back into the parameter 'Status', not as function result, as far as I can see. So I thought using ByRef instead of ByVal. But that causes to crashes VB6 IDE..
Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_get_status", vbLong, hHandle, VarPtr(Status))
-
Jan 10th, 2021, 03:15 PM
#33
Lively Member
Re: C++ DLL to VB6
Thank you, thank you, thank you... The varptr(status) did it. I can now enumerate, open device, get status, close ... Next is actually open and close the relay, but I'm sure it will be similar/same as all previous functions. Again many thanks to you experts!
So now I can work on the actual application...
_Wim_
-
Jan 10th, 2021, 11:04 PM
#34
Re: C++ DLL to VB6
Originally Posted by wqweto
Please post the C/C++ function declaration like this
pusb_relay_device_info_t USBRL_API usb_relay_device_enumerate(void)
So the retval is *pointer* to this USB_Relay_Device_Info UDT. You can treat pointers as glorified Longs like this
Dim lPtr As Long
lPtr = cdeclCallA("usb_relay_device.dll", "usb_relay_device_enumerate", vbLong, 0)
And then use CopyMemory to dereference it
Dim uRetVal As USB_Relay_Device_Info
Call CopyMemory(uRetVal, ByVal lPtr, Len(uRetVal))
cheers,
</wqw>
Minor point here. Isn't it a better practice to use LenB instead of just Len when requesting the size of a Structure?
-
Jan 11th, 2021, 02:25 AM
#35
Re: C++ DLL to VB6
Originally Posted by Niya
Minor point here. Isn't it a better practice to use LenB instead of just Len when requesting the size of a Structure?
True, my mistake -- Len on UDTs is evil!
And for vNext we might need LenB(USB_Relay_Device_Info) to work like sizeof in other languages at compile-time too :-))
I often find myself declaring additional Private Const sizeof_USB_Relay_Device_Info As Long = 4 * 4 manually just to be sure the sizes match.
cheers,
</wqw>
-
Jan 11th, 2021, 03:31 AM
#36
Lively Member
Re: C++ DLL to VB6
One other question:
What is the difference between cdeclCallA and cdeclCallW calls? It seems that it treats string type parameters differently!? Better question would be: when do I use one and when the other?
On the same subject, why are there fucntions for stdCallA (and stdCallW)? If a DLL uses stdcall convention, then there is no need for wrapping calls to the DLL??
_Wim_
-
Jan 11th, 2021, 04:04 AM
#37
Re: C++ DLL to VB6
Originally Posted by _Wim_
One other question:
What is the difference between cdeclCallA and cdeclCallW calls? It seems that it treats string type parameters differently!? Better question would be: when do I use one and when the other?
On the same subject, why are there fucntions for stdCallA (and stdCallW)? If a DLL uses stdcall convention, then there is no need for wrapping calls to the DLL??
_Wim_
I'm guessing he is following the Windows naming convention when it comes to functions that have String parameters. "W" means wide String which on modern Windows means a UTF-16 String. "A" means ANSI String which is the internal String format of Windows versions that don't use the Windows NT Kernel(Windows 95/98, 3.1 etc). It is very important when calling functions that take String parameters to know what format Strings are expected because if there is a mismatch between your code and the function you're calling, bad things will happen. For example the String "dog" as a UTF-16 String will occupy 6 bytes of memory while in ANSI format will occupy 3 bytes. You can use your imagination to figure out what can go wrong if you have a mismatch there. Encoding is another aspect of different String formats to take into consideration.
In the case of this function:-
Code:
int EXPORT_API usb_relay_device_open_with_serial_number(const char *serial_number, unsigned len);
That function prototype indicates that most likely what is expected is an ANSI String so you're going to want to use the "A" version. If it was expecting a wide String, you may have seen something like LPWSTR instead of char*.
Also another thing to be ware of is when such functions ask for a length. Sometimes a function will want a buffer size in terms of bytes and sometimes in terms of character length. Pay attention with such functions. In the case of the above function, they are synonymous since ANSI Strings are 1 byte per character so a byte length with be same as a character length. A UTF-16 String of character length 10 will have a byte length of 20 since in this case it's 2 bytes per character.
If the functions involved have no String parameters then it doesn't matter at all which version you use. They will effectively be identical.
Last edited by Niya; Jan 11th, 2021 at 04:18 AM.
-
Jan 11th, 2021, 04:36 AM
#38
Re: C++ DLL to VB6
Originally Posted by _Wim_
On the same subject, why are there fucntions for stdCallA (and stdCallW)? If a DLL uses stdcall convention, then there is no need for wrapping calls to the DLL??
The module is basically the same one, which works under the cover of the vbRichClient-COM-lib
(where these call-supporting Methods sit behind a COM-Class - and are accessible via New_c.stdCall(...)).
And yes, if you work in VB6 or VBA, then you will not need the stdcall-methods (because the Declare statement is supported) -
but there's scripting-languages which don't support Declare - as e.g. JavaScript, VBScript or *.asp-Pages,
where these stdcall-helper-methods will allow "external Dll-access" (via an appropriate COM-Class-instance, in case the language has COM-support).
Olaf
-
Jan 11th, 2021, 07:22 AM
#39
Lively Member
Re: C++ DLL to VB6
Originally Posted by _Wim_
... now I can work on the actual application...
_Wim_
Can you post a sample code and usb_relay_device.dll file ?
-
Jan 11th, 2021, 12:37 PM
#40
Lively Member
Re: C++ DLL to VB6
For the .dll refer to for instance http://www.giga.co.za/ocart/index.ph...product_id=229, or google on "usb-relay-2"
FWIW, so far I have this "wrapper" code, but it is 'under-construction'!!
Code:
Option Explicit
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal length As Long)
Public Enum USB_Relay_Device_Type
USB_RELAY_DEVICE_ONE_CHANNEL = 1
USB_RELAY_DEVICE_TWO_CHANNEL = 2
USB_RELAY_DEVICE_FOUR_CHANNEL = 4
USB_RELAY_DEVICE_EIGHT_CHANNEL = 8
End Enum
Public Type USB_Relay_Device_Info
Serial_Number As Long ' pointer to 5 digits serial number
Device_Path As Long ' I assume this is not implemented, or should return "NOTHING:"..!?
Type As USB_Relay_Device_Type
Next As Long ' original type is USB_Relay_Device_Info, so recursive use ..!
End Type
Public Function OpenDeviceWithSerial(SerialNumber As String) As Long ' return handle to device
'usage: hnd=OpenDeviceWithSerial("ABCDE")
OpenDeviceWithSerial = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open_with_serial_number", _
vbLong, SerialNumber, Len(SerialNumber))
End Function
Public Sub CloseDevice(ByVal hHandle As Long)
'Close entire relay board
If hHandle <> 0 Then Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_close", vbEmpty, hHandle)
End Sub
' TODO add another wrapper on top of EnumerateDevice to collect all devices in an array, and serial nr translated into string(s)
Public Function EnumerateDevice() As USB_Relay_Device_Info
'Get device info, return as "USB_Relay_Device_Info"
Dim lPtr As Long, uRetVal As USB_Relay_Device_Info
lPtr = cdeclCallA("usb_relay_device.dll", "usb_relay_device_enumerate", vbLong)
If lPtr <> 0 Then
Call CopyMemory(uRetVal, ByVal lPtr, LenB(uRetVal))
EnumerateDevice = uRetVal
End If
End Function
Public Function GetStatus(ByVal hHandle As Long, ByRef Status As Long) As Long
' get open/close status of the relay switches. Although long is returned, only LSB byte is used
' '3' = two switches are open
If hHandle <> 0 Then Call cdeclCallA("usb_relay_device.dll", "usb_relay_device_get_status", vbLong, hHandle, VarPtr(Status))
End Function
Public Function RelayInit() As Long
'Supposed to start the application with RelayInit, but appears not really necessary...!?
Call cdeclCallA("usb_relay_device.dll", "usb_relay_init", vbEmpty)
End Function
Public Function RelayExit() As Long
'Call on form unload
Call cdeclCallA("usb_relay_device.dll", "usb_relay_exit", vbEmpty)
End Function
Public Function RelayOpen(Device_Info As Long) As Long
'Open device using Device_Info obtained via EnumerateDevice
If Device_Info <> 0 Then RelayOpen = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open", vbLong, Device_Info)
End Function
Public Function OpenAllChannels(ByVal hHandle As Long)
'Activates all relay switches.. Funny name for closing the relay contacts.. :-)
If hHandle <> 0 Then OpenAllChannels = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open_all_relay_channel", vbLong, hHandle)
End Function
Public Function OpenOneChannel(ByVal hHandle As Long, ByVal Index As Long)
'Activates one relay contact, index = 1, 2, etc
If hHandle <> 0 Then OpenOneChannel = cdeclCallA("usb_relay_device.dll", "usb_relay_device_open_one_relay_channel", vbLong, hHandle, Index)
End Function
Public Function CloseOneChannel(ByVal hHandle As Long, ByVal Index As Long)
'Deactivate one relay contact, index = 1, 2, etc
If hHandle <> 0 Then CloseOneChannel = cdeclCallA("usb_relay_device.dll", "usb_relay_device_close_one_relay_channel", vbLong, hHandle, Index)
End Function
Public Function CloseAllChannels(ByVal hHandle As Long)
'Deactivates all relay switches.. Funny name for opening the relay contacts.. :-)
If hHandle <> 0 Then CloseAllChannels = cdeclCallA("usb_relay_device.dll", "usb_relay_device_close_all_relay_channel", vbLong, hHandle)
End Function
' FreeEnumerate crashes VB6 IDE
'Public Function FreeEnumerate(ByVal Device_Info As Long) As Long
' If Device_Info <> 0 Then FreeEnumerate = cdeclCallA("usb_relay_device.dll", "usb_relay_device_free_enumerate", vbLong, Device_Info)
'End Function
And here are some of my calls, but these are even more 'under-construction'...
Code:
Option Explicit
Dim hnd As Long
Dim DevInfo As USB_Relay_Device_Info
'
Private Sub Form_Load()
Dim SerNr As String, Status As Long, bstr(4) As Byte
Call RelayInit
' hnd = OpenDeviceWithSerial("BITFT")
' MsgBox hnd
DevInfo = EnumerateDevice()
With DevInfo
If .Serial_Number <> 0 Then
Call CopyMemory(bstr(0), ByVal .Serial_Number, 5)
SerNr = StrConv(bstr, vbUnicode)
End If
End With
'
MsgBox SerNr & " - " & DevInfo.Type
hnd = RelayOpen(VarPtr(DevInfo))
MsgBox hnd
Call GetStatus(hnd, Status)
MsgBox Status
End Sub
Private Sub Form_Unload(Cancel As Integer)
CloseDevice (hnd)
RelayExit
CleanupLibHandles
End Sub
Private Sub Check1_Click(Index As Integer)
With Check1(Index)
If .Value Then
Call OpenOneChannel(hnd, Index + 1)
Else
Call CloseOneChannel(hnd, Index + 1)
End If
End With
End Sub
Private Sub Command1_Click()
OpenAllChannels (hnd)
End Sub
Private Sub Command2_Click()
CloseAllChannels (hnd)
End Sub
... as said under construction, but open for any remarks of course!
_Wim_
Last edited by _Wim_; Jan 11th, 2021 at 03:13 PM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|