VBA 64-bit Excel not passing pointers to DLL functions (C++) - c++

I have VBA program running on Excel 64-bit calling some DLL functions (C++). The problem is (apparently) it can't pass pointers to C++ program. The program works with Excel 32-bit.The operating system is Windows 8. For Windows 7 both 32-bit and 64-bit versions of Excel are working well.
C++ :
double test(long* v, long i)
{
if (v == NULL)
return -88;
else
return *((long*)v);
}
VBA:
Private Declare PtrSafe Function hamid_test Lib "...\CVode.dll" (ByVal v As LongPtr, ByVal i As Long) As Double
Dim x As LongLong
Dim z As Double
z = test(x, 1)
It returns -88.

Dim x As LongPtr
Dim z As Double
z = test(x, CLng(1))
LongPtr evaluates to Long in 32-Bit environments and to LongLong in 64-Bit environments so defining x as such should work.
Addtionally, CLng will ensure 1 is evaluated as a Long type. If this is not added VBA will assume you mean Integer

Related

Excel-VBA with C++ DLL sometimes crashes

I made really simple c++ dll with only one function:
int DLL_EXPORT __stdcall foo(double *source){return 0;}
and I'm trying to use it like that:
Option Explicit
Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long
Private Declare PtrSafe Function foo Lib "MyLibrary.dll" (ByRef arr As Double) As Long
Sub test_foo(n As Long)
Dim i As Long
Dim library_address As LongPtr
Dim library_path As String
library_path = "global_path\MyLibrary.dll"
library_address = LoadLibrary(library_path)
Dim arr() As Double
ReDim arr(1 To n) As Double
For i = 1 To n
arr(i) = CDbl(Cells(i, 1).Value)
Next
foo arr(1)
Do Until FreeLibrary(library_address) = 0
Loop
End Sub
and it usually works, but sometimes it crashes (Excel dies).
Faulting application name: EXCEL.EXE, version: 16.0.8625.2139, time stamp: 0x5a162a41
Faulting module name: MyLibrary.dll_unloaded, version: 0.0.0.0, time stamp: 0x000000e2
Exception code: 0xc0000005
Fault offset: 0x00001230
Faulting process id: 0x1828
I've tested on Excel 2016 on Windows 10 and Excel 2013 on Windows 8
Please tell me what is wrong? Do you have any example of non-crashing usage of C++ DLL working on arrays?
WORKAROUND:
Replace
Do Until FreeLibrary(library_address) = 0
Loop
with
FreeLibrary library_address
I've put that in the loop because sometimes FreeLibrary doesn't work, but i don't care anymore. Related question here
Integer in VBA is from -32768 to 32767. In C++ it is a way bigger, equivalent to the VBA Long. Thus, try declaring like this:
Private Declare PtrSafe Function foo Lib "MyLibrary.dll" (ByRef arr As Double) As Long
Let me show what I did so far, which worked for me (if this was the way you wanted it). Anyhow, I have changed a few things, it will be better to use some text comparer to see a bit).
I have used this article to build the dll library (disclaimer - it is my own).
The cpp and the def:
int __stdcall SimpleSlowMath(double *source)
{
return 0;
}
The *.def looks like this:
LIBRARY "SomeLibrary"
EXPORTS
SimpleSlowMath
The VBA:
Option Explicit
Public Const myPathDll = "C:\Users\your-own-path\Debug\vityata051217.dll"
Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" _
(ByVal lpLibFileName As String) As Long
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" _
(ByVal hLibModule As LongPtr) As Long
Private Declare PtrSafe Function SimpleSlowMath Lib "vityata051217.dll" _
(ByRef arr() As Double) As Long
Sub Try(n As Long)
Dim i As Long
Dim library_address As Long
Dim library_path As String
library_path = myPathDll
library_address = LoadLibrary(library_path)
Dim arr() As Double
ReDim arr(1 To n) As Double
For i = 1 To n
arr(i) = CDbl(Cells(i, 1).Value)
Next
Debug.Print SimpleSlowMath(arr)
End Sub
Public Sub TestMe()
Dim n As Long
For n = 1 To 50
Try n
Debug.Print n
Next n
End Sub
As you see, the difference is that I declare the array with () here - ByRef arr() As Double, but there are some other as well. Give it a try, for me it was working for 2000 samples.

Wrong DLL calling convention

I'm trying to call a C++ COM DLL from within VB6.
The C++ code is:
STDMETHODIMP CSonic::sonicChangeShortSpeed(
SHORT* samples,
LONG *numSamples,
FLOAT speed,
FLOAT pitch,
FLOAT rate,
FLOAT volume,
LONG useChordPitch,
LONG sampleRate,
LONG numChannels
)
I call it from VB6 like this:
Private Declare Function sonicChangeShortSpeed Lib "SonicLIB.dll" Alias "#1" (
ByRef samples As Integer,
ByRef numSamples As Long,
ByVal speed As Double,
ByVal pitch As Double,
ByVal rate As Double,
ByVal volume As Double,
ByVal useChordPitch As Long,
ByVal sampleRate As Long,
ByVal numChannels As Long)
As Long
In my code, I use:
Dim nIntegers() As Integer
ReDim nIntegers(2047)
Dim lSamples As Long
Dim dblSpeed As Double
Dim dblPitch As Double
Dim dblRate As Double
Dim dblVol As Double
Dim lUseChordPitch As Long
Dim lSampleRate As Long
Dim lNumChannels As Long
lSamples = 2048
dblSpeed = 0.5
dblPitch = 0
dblRate = 1
dblVol = 1
lUseChordPitch = 1
lSampleRate = 48000
lNumChannels = 1
Dim lRet As Long
lRet = sonicChangeShortSpeed(nIntegers(0), lSamples, dblSpeed, dblPitch, dblRate, dblVol, lUseChordPitch, lSampleRate, lNumChannels)
The last line produces the "Wrong calling convention" error.
Does anybody see my mistake?
Thank you!
If this is a COM DLL, you need to add it to your VB6 project as a reference, and then you can access the classes and other COM definitions contained in the DLL.
Alternately you could just register the DLL without adding it as a reference and then use 'late binding' with CreateObject() calls.
But if you are not sure if it is a COM DLL, you could check by:
Try to register it on the command like using regsvr32.exe (may need to be admin). If this reports success, that means it was a successfully registered COM DLL.
Drag and drop it into the program Oleview which comes with Visual Studio. If the DLL is COM it will normally have a type library which Oleview will display. If it is not COM, this will produce an error.
If it is NOT a COM DLL then I do not think your approach will necessarily work. You are trying to call a class method as though it were a normal 'C' style function. Perhaps that would work for a static C++ method -- but that does not appear to be the case here.
The traditional import/export table of a DLL knows nothing about classes. You may need to write a C++ wrapped function around your class object which can then be exported and used in VB6. The wrapper would have to handle object creation, destruction, etc.

vb Integer to c++ to ML32 to c++ to vb UInteger

I'm experimenting with changing a vb 2015 Integer into a vb 2015 UInteger via a direct bit-by-bit transfer in ML32. From vb2015, I call a vc++ 2015 function in a dll which uses the inline assembler to effect the change.
By doing this, I can take a negative Integer such as -633593090 = &HDA3C22FE = 11011010001111000010001011111110b
And change it to a UInteger = 11011010001111000010001011111110b = &HDA3C22FE = 3661374206
As opposed to something like the seemingly simple "uTest1 = CUInt(iTest1)" which throws a System.OverflowException for iTest1 negative.
I have code which works properly, but I'm interested in the possibility of a small improvement.
In vb 2015, I declare the access to the vc++ 2015 dll like this:
' vb Integer --> c++ signed long --> c++ unsigned long --> vb UInteger -- Works!
' by direct copy of bits through x86 ML32
' 4 bytes = 32 bits
<DllImport("StringTest.dll", EntryPoint:="bitConvert", SetLastError:=True, CharSet:=CharSet.Ansi, ExactSpelling:=True, CallingConvention:=CallingConvention.Cdecl)>
Private Shared Function bitConvertTester(ByRef varSInt As Integer, ByRef varUInt As UInteger) As Integer
' Must be "Shared"
' Must be "ByRef"
' Do not try varSInt As "Signed Integer" or varUInt As "Unsigned Integer" --> Syntax Error
' Leave the body of the function empty
End Function
And I use this vb 2015 code to actually call the vc++ 2015 function in the dll:
' Third Method - Partial - Works!
Dim pCTest As Color
Dim iTest As Integer
Dim uTest As UInteger
Dim returnCode As Integer
pCTest = Color.FromArgb(&HDA, &H3C, &H22, &HFE)
iTest = pCTest.ToArgb
System.Diagnostics.Debug.Write(iTest.ToString + vbCrLf)
System.Diagnostics.Debug.Write(Hex(iTest) + vbCrLf)
' vb Integer --> c++ signed long --> c++ unsigned long --> vb UInteger
' by direct copy of bits through x86 ML32
' 4 bytes = 32 bits
' Note: Function prototype is "ByRef", but this MUST
' NOT go in this test call (causes a syntax error).
returnCode = bitConvertTester(iTest, uTest)
System.Diagnostics.Debug.Write(uTest.ToString + vbCrLf)
System.Diagnostics.Debug.Write(Hex(uTest) + vbCrLf)
And here is the vc++ 2015 dll function itself:
#define EXPORT_VB extern "C" __declspec(dllexport)
// vb Integer --> c++ signed long --> c++ unsigned long --> vb UInteger -- Works!
// by direct copy of bits through x86 ML32
// 4 bytes = 32 bits
EXPORT_VB long __cdecl bitConvert(signed long *varSInt, unsigned long *varUInt)
{
// Intermediate variables are required for ML32 access.
signed long varSInt1;
unsigned long varUInt1;
varSInt1 = *varSInt;
__asm
{
mov EAX, varSInt1
mov varUInt1, EAX
}
*varUInt = varUInt1;
return 1; // Success Code
}
I call the vc++ 2015 dll function from vb 2015 with the variables ByRef so that the dll function can use pointers to change the vb 2015 variables as needed via *varSInt and *varUInt.
My question is: In the vc++ __asm block, is there any way for the ML32 code to access *varSInt and *varUInt directly, without going through the varSInt1 and varUInt1 intermediate variables?
BTW, the vb 2015 code uses:
Imports System
Imports System.IO
Imports System.Text
Imports System.Runtime.InteropServices
And the vc++ 2015 code uses:
#include "stdafx.h"
#include <stdexcept>
#include <string.h>
(One thing I find frustrating with Microsoft documentation is the failure to include a list of the "Imports" or "#include" statements required for use of the feature the documentation is describing).
I realise that you said you are just doing this as an exercise, but there is a relatively easy way to get the result you want in VB (or C# for that matter).
Function ToUInt(value As Integer) As UInteger
Return CUInt(If(value < 0, UInteger.MaxValue + value + 1, value))
End Function

Switch Case with parameter passed from VBA - parameter not compatible

this is an odd behaviour which I encouter.
I have a Visual Basic Interface for the User.
Declare PtrSafe Sub getPEC _
Lib "C:\Users\...somePath...\OPunit0011PUMP.dll" _
(ByVal typeOfPump As Integer, _
ByVal outflow As Double, _
ByRef pec As Double, _
ByRef RV As Integer)
The user specifies a pump by the integer typeOfPump. I pass this parameter as ByVal typeOfPump into my C++ DLL.
Here according to which pump it is some predeclared parameters a,...g are initialized using switch case
extern "C"
{
double PEC_backUp;
void __stdcall getPEC(int typeOfPump, double outflow, double &PEC, int &RV)
{
//polynomal trendline for different pumps
//y = a x^6 + b x^5 + c x^4 + d x^3 + e x^2 + f x^1 + g x^0
if (typeOfPump < 1)
{
RV = -1;
return;
}
double a, b, c, d, e, f, g;
#pragma region switch case
switch (typeOfPump)
{
//150 bar
case 1:
a = 1.1186E-08;
b = -1.49172E-05;
//...
break;
case 2:
//...
}
My problem is that switch case does NOT work. My default value is set to nine, but also every other case does NOT work. It simply neglects the switch case code.
Note also: The same odd behaviour can be seen in the If condition:
if (typeOfPump > 1)
{
RV = -1;
return;
}
Despite the fact that typeOfPump is assigned to NINE which is obviously bigger than one my function getPEC does not return at this point. On the other hand if I write
if (typeOfPump < 1)
{
RV = -1;
return;
}
my function will return here. I then assigned the value of typeOfPump to RV to monitor it in VBA and RV was set to nine.
Moreover, to make things even stranger it automatically changes the value of pec to 7.00000000005821 (using watch function of VBA) when it returns with RV = -1.
I guess my parameter are somehow not compatible for operations in my DLL. Did anyone see this before and how can I fix it?
Thank you in advance!
EDIT: I can do operations like
RV = typeOfPump * (int)outflow;
and obtain correct values. However, pec still shows some change in its value.
SCD EDIT: I have 64bit, Excel is 32bit, I'm compiling with x86.
I wrote a similar program on another computer 64bit, Excel 64bit, compiling with x64. There it worked!
3rd EDIT: integer of value 9 in VBA results in -65526 in C++ environment, given size of integer in my C++ environment is 4byte. Assuming range of 16bit variable for integer is −32,768 to 32,767. Doubling 32,767 and subtracting 9 leads to 65525.
In general if you're not sure why your code isn't working, print out the values that you are sure of, and see if they are actually what you think they are. Especially if mixing 32bit and 64 bit code. There are all kinds of compiler options which will affect the data format when it's used.
Write your code as a standalone exe, make sure that it works; write the dll with handshaking as simply as possible, make sure that works, and then make your exe a dll. You're trying to do too much in one step.

C function in Excel 64 bit does not work as a worksheet function but works well in VBA

I am writing a 64 bit Dll in C which is then used in Excel 64 bit, and I am following a sample project from https://sites.google.com/site/jrlhost/links/excelcdll.
The example is simple. We write a function in C which is to return the square value of an input. The function is exported in DLL and then used in Excel 64 bit.
I am facing the exact same problem as in the example:
"However, when you use squareForEXL as a worksheet function, it results in errors. On my desktop, it returns the correct result (e.g., "= squareForEXL(10)" yields 100) but then gives an "Out of Stack Space" error, either at some point when calling the function or when Excel is closed. On my laptop, it returns an incorrect result (e.g., "= squareForEXL(10)" yields 0). On both, Excel sometimes crashes."
The C function (squareForEXL) works fine when used in VBA, but it does not work as a worksheet function. One workaround is proposed in the article but I still want to see if there is any way to resolve the issue directly.
Below is the C and VBA code:
double _stdcall squareForEXL (double *x)
{
return *x * *x;
}
Declare PtrSafe Function squareForEXL Lib "C:\Working\XLSquare\x64\Debug\XLSquare.dll" (ByRef x As Double) As Double
You need to pass a reference to the squareForExl given in the example. That is you need,
double _stdcall squareForEXL (double &x)
{
return x * x;
}