Calling C++ DLL from VB6 passes garbage data for arguments? - vb6

VC functions all use _stdcall
functions exported using .def file
(e.g. AliasFuncName = _FuncName#NumCallArgBytes)
Existing C DLL was revised to have some new call arguments
(revised function names, built to new dll name)
Functions with unrevised call arguments work when calling the new DLL
Functions with revised call arguments do not work when calling the new DLL
(all call arguments are garbage on entry)
Call arguments are several input doubles and a few return double*
Prototype.h call definition matches c source code definition
Visual Basic declarations to new DLL match in style those to the old DLL
(several ByVal double input args and a few ByRef return args)
Arguments look good in VB Debugger before calling VC debugger where they are garbage (e.g. 1.34867e-308, 3.49732e-88, etc.).
I would appreciate any thoughts on possible causes. I have been struggling with this for a few days. By the way, I don't choose to work in legacy code!
Below are the C header prototype, the .DEF definition and the VB declaration.
Header file definition:
LONG _stdcall SYSDll_FRoulSlideXa(
double ATest, double Hc, double Hivr,
double Eeq, double Rx, double Rk,
double L, double U, double SlRol,
double R, double Wlc, double Wpc,
double Mu, double MuOil, double Cor2AL,
double Fs, double Ft,
double *FRoul, double *FSlid);
.DEF file definition:
LIBRARY "SYSx32d10a"
DESCRIPTION 'SYSx Dlls'
EXPORTS
SYSDll_FRoulSlideXa = _SYSDll_FRoulSlideXa#144
VB6 declaration:
Declare Function SYSDll_FRoulSlideXa Lib "SYSX32D10A.DLL" ( _
ByVal ATest As Double, ByVal Hc As Double, ByVal Hivr As Double, _
ByVal Eeq As Double, ByVal rx As Double, ByVal Rk As Double, _
ByVal L As Double, ByVal U As Double, ByVal SlRol As Double, _
ByVal r As Double, ByVal Wlc As Double, ByVal Wpc As Double, _
ByVal Mu As Double, ByVal MuOil As Double, ByVal Cor2AL As Double, _
ByVal Fs As Double, ByVal Ft As Double, _
FRoul As Double, FSlid As Double)
Note: I have already tried explicit ByRef on the last two arguments instead of relying on default passing convention being ByRef.

You VB Declare doesn't include a return type for the function. Unless there's a DEFxxx statement that you don't show, that means VB expects a Variant. Because Variant functions return their value using a hidden parameter, the stack will be misaligned. That alone can cause what you're seeing.
The solution is to add the correct return type to the VB Declare.

Related

Returning "String" value from Console Application to Batch File

Currently, my console application can return Integer values from the console application via the kernel32 function ExitProcess.
Public Declare Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long)
How do I return string values from the console application to the batch file?
I want to return string values like Successfully transformed 100 batches... etc.
On most platforms (and also in Windows) process exit codes are integer values, but you could write string data to the standard output stream by using the GetStdHandle and WriteFile functions.
Update As requested, I´ll serve you an example.
First, you´ll need to import some more Windows functions and define the required constants. In addition to the before-mentioned GetStdHandle and WriteFile methods, the AttachConsole and FreeConsole methods are also required.
Private Const ATTACH_PARENT_PROCESS As Long = -1
Private Declare Function AttachConsole Lib "Kernel32" ( _
ByVal dwProcessId As Long) As Long
Private Declare Function FreeConsole Lib "Kernel32" () As Long
Private Const STD_OUTPUT_HANDLE As Long = -11&
Private Declare Function GetStdHandle Lib "Kernel32" ( _
ByVal nStdHandle As Long) As Long
Private Declare Function WriteFile Lib "Kernel32" ( _
ByVal hFile As Long, _
ByVal lpBuffer As String, _
ByVal nNumberOfBytesToWrite As Long, _
ByRef lpNumberOfBytesWritten As Long, _
lpOverlapped As Any) As Long
In my sample project, I just added a Module and defined a Sub Main method - this serves as the entry point for the app. Please notice, that you don´t get any output from the app when running it from the VB6 IDE debugger. You´ll need to compile it, and run it from a terminal window (for instance cmd.exe).
The first thing to do is to attach to the console of the parent process (which is the console of the terminal window). Otherwise, the GetStdHandle method will return zero.
Dim handle As Long
AttachConsole (ATTACH_PARENT_PROCESS)
handle = GetStdHandle(STD_OUTPUT_HANDLE)
Once the console handle is obtained, the WriteFile method can be used to print text to the console.
Dim s As String
Dim numberOfBytesWritten As Long
s = "Hello World."
WriteFile handle, s, Len(s), numberOfBytesWritten, ByVal 0&
Before finally calling ExitProcess, the FreeConsole method is used to detach the process from the parent console.

Excel 2016 conditional compilation with 32-bit flags

In the new 64-bit version of Excel 2016 on OSX I obtained through update today, the conditional compilation doesn't seem to be followed when checking for function definitions that don't have PtrSafe defined (as would be the case for 32-bit platforms). In this example, we have different definitions of the same function for different platforms, and when Excel loads the add-in it dies and complains about the third definition not having a PtrSafe in the function declaration (but of course it doesn't because it is for a 32-bit platform).
Is there any way of making Excel not die when it hits this code in VBA? Or is this just a bug in 64-bit Excel 2016 on OSX? Seems like an obvious bug to me. Where do I report bugs in Excel?
#If Mac Then
' Even though the functions are exported with a leading underscore, Excel 2011 for Mac doesn't want the leading underscore as part of name
Private Declare PtrSafe Function get_global_param_string_private Lib "libCoolProp.dylib" Alias "get_global_param_string" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#ElseIf Win64 Then
Private Declare PtrSafe Function get_global_param_string_private Lib "CoolProp_xls_x64.dll" Alias "get_global_param_string" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#Else
Private Declare Function get_global_param_string_private Lib "CoolProp_xls_std.dll" Alias "_get_global_param_string#12" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#End If
Unless the API function itself is different for 64 and 32 bit windows it suffices to use the VBA7 switch (which starts at Office 2010) for Windows:
#If Mac Then
' Even though the functions are exported with a leading underscore, Excel 2011 for Mac doesn't want the leading underscore as part of name
Private Declare PtrSafe Function get_global_param_string_private Lib "libCoolProp.dylib" Alias "get_global_param_string" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#ElseIf VBA7 Then
Private Declare PtrSafe Function get_global_param_string_private Lib "CoolProp_xls_x64.dll" Alias "get_global_param_string" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#Else
Private Declare Function get_global_param_string_private Lib "CoolProp_xls_std.dll" Alias "_get_global_param_string#12" (ByVal param As String, ByVal Output As String, ByVal n As Integer) As Long
#End If

Convert 8-bytes array into Double

(Talking about Visual Basic 6)
I was able to find how to convert Double into 8-bytes array, but not the viceversa.
Before I start to try to code it, is there some routine to do it (like the "CopyMemory" described in the linked question)? Can the "CopyMemory" be used in this case?
Use the same code as the answer you linked to but swap the source and destination around:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef Destination As Any, _
ByRef Source As Any, _
ByVal Length As Long)
Function BytesToDbl(ByRef Bytes() As byte) As Double
Dim D As Double
CopyMemory D, Bytes(0), LenB(D)
BytesToDbl = D
End Function
I've skipped any error checking for this example but you'll want to make sure that your byte array is actually 8 bytes long otherwise you'll get an access violation.
Note that this assumes the byte array was created using the linked to question. Floating point values from other sources may well be using a different binary representation which means this will not work.

SetTimer IDEvent return Value Problems

Currently I am trying to implement a timer class in VBA. For that purpose I use the SetTimer and KillTimer functions of the Windows API...
This is the interface from Msdn:
UINT_PTR WINAPI SetTimer(
__in_opt HWND hWnd,
__in UINT_PTR nIDEvent,
__in UINT uElapse,
__in_opt TIMERPROC lpTimerFunc
);
And this is the way I declared the function wihtin my VBA-Module:
Private Declare Function SetTimer Lib "user32" (ByVal Handle As Long, _
ByVal TimerIDHandle As Any, _
ByVal ElapseTime As Long, _
ByVal AddressOfAndYourHandlerFunctionName As Long) As Long
'TimerIDHandle is of Type Any so I can pass Nothing to the function
I then Call the function this way:
Dim TimerID As Long
TimerID = SetTimer(Application.hWndAccessApp, ByVal 0&, Timer.Timeout, AddressOf TimeOutHandler)
As Vba does not accept a "Null" I have tried implementing "ByVal 0&". Is this the right way to do it?
Anyway...I call this function several times from the same Application and the function always returns 1 as an Identifier even though, according to Msdn, the function should return a unique ID for each timer that is created in the Window handle of the current Access Application.
Furthermore when I created only one timer the Callback function gets Called, but the Timer ID is given as 0, whereas the Settimer-Function returned a 1 at the time of initialization.
Here is my Callback Function Header:
Private Sub TimeOutHandler(ByVal WindowHandle As Long, _
ByVal TimerMessage As Long, _
ByVal TimerID As Long, _
ByVal ElapsedTime As Long)
Where am I wrong?
Any help is greatly appreciated of course ;-)
It worked. If it failed then it would have returned 0. You get a 0 for TimerID in the callback because you passed a 0 for the nIDEvent argument when you created the timer. You'll need to use the value that SetTimer returned to call KillTimer(). Think of it as a timer 'handle'.
You'll never get this code working in 64-bit mode so just declare the 2nd argument as Long.

Calling a simple VC DLL from VB6

I have a simple DLL written with VC6 with one function:
__declspec(dllexport) int myfunc(long a, unsigned char *b, unsigned char *c, unsigned char *d, unsigned char *e)
And im calling it from vb6 using:
Declare Function myfunc Lib "mylib.dll" (ByVal a As Long, ByVal b As String, ByVal c As String, ByVal d As String, ByVal e As String) As Long
....
dim a as long
dim b as string
dim c as string
dim d as string
dim e as string
dim r as long
r=myfunc(a,b,c,d,e)
Im getting "bad dll calling convention" error but I cant figure out why. Any ideas?
Generally speaking, 'bad DLL...' means what it says. VB6 requires the _stdcall convention (like the Win API) for any external functions it calls.
Try adding __stdcall to the C function prototype and see what happens.
Check out the Universal DLL function caller, by Paul Caton:
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=70195&lngWId=1
It will allow you to call pretty much any type of function from VB6.

Resources