Wrong DLL calling convention - c++

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.

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.

Invalid procedure call or argument when passing parameters from VBA to WIndows DLL

I have 64 bit windows 10 with MS Office 64 bit. I am trying to get the VBA for PowerPoint to load and execute a function in a self-written 64 bit windows DLL.
The problem I am having is that I get an exception - which I think is due to stack handling.
The dll contains (exports done in def file):
void _stdcall jastrNR(LPCWSTR strg1, LPCWSTR strg2, LPCWSTR strg3, LPCWSTR strg4)
The VBA contains:
Private Declare PtrSafe Sub jastrNR Lib "<DLL path>" (ByVal arg1 As LongPtr, ByVal arg2 As LongPtr, ByVal arg3 As LongPtr, ByVal arg4 As LongPtr)
Sub Play1()
Dim s1 As String
Dim p1 As LongPtr
Dim y As Long
y = 0
MsgBox y
s1 = "123abc"
p1 = StrPtr(s1)
jastrNR p1, p1, p1, p1
p4 = StrPtr(s4)
MsgBox y *** here I get exception or "invalid procedure call or argument" message
End Sub
If I print out the test string in the DLL, I do see correct data, ie 123abc
If the parameters are long, ints, there is no problem.
Also there is no problem if I make jastrNR return a long.
I have also tried to match the parameters in the C++:
void _stdcall jastrNR(LONG_PTR strg1, LONG_PTR strg2, LONG_PTR strg3, LONG_PTR strg4)
I get the same exceptions.
In Visual Studio 2017, I have set (under C++ settings) the calling convention to be __stdcall (/Gz).
I have noticed in the linker map output:
?jastrNR##YAX_J000#Z (void __cdecl jastrNR(__int64,__int64,__int64,__int64))
exported name: jastrNR
The __cdecl concerns me as I thought I should have seen __stdcall
Any help gratefully received.

VBA 64-bit Excel not passing pointers to DLL functions (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

Strange behavior of Excel or VBA

I´m creating a .dll library in C/C++ for VBA. It will contain functions for communication via RS232 serial port and data will be processed in Excel. Everything works fine but I´m confused of strange behavior of VBA that works under Excel. I have 2 functions. One for writing to port one for reading. When I´m sending a port number e.g. 3 from VBA to one of them, doesn´t matter which one and print it exactly after it was received by function it shows decimal value of 3 what is correct. But when I send exactly the same variable that consists number 3 to second one, function receive 51 what is a decimal value of "3" char. So at first VBA send integer then it changes somehow and send decimal value of "3" char. There is no code before printing received variable in my functions that can change value.
Here is simplified code of my functions just to show.
int __stdcall PortRead(short int & Port){
printf("%d %c\n",Port,Port);
return 0;
}
int __stdcall PortWrite(short int & Port, BSTR & Message){
printf("%d %c\n",Port,Port);
return 0;
}
Here is VBA code:
Declare Function PortRead Lib "rs232_r.dll" (ByRef x As Integer) As Integer
Declare Function PortWrite Lib "rs232_w.dll" (ByRef x As Integer, ByRef y As String) As Integer
Dim Message As String
Dim PortNumber As Integer
Sub Example()
PortNumber = 3
Message = ":trac:data?"
aa = PortWrite(PortNumber, Message)
Debug.Print aa
xx = PortRead(PortNumber)
Debug.Print xx
End Sub
As I said, passed values will be different when I'm sending one variable to 2 functions but when I change it like the next example both functions will receive the same correct value.
Declare Function PortRead Lib "rs232_r.dll" (ByRef x As Integer) As Integer
Declare Function PortWrite Lib "rs232_w.dll" (ByRef x As Integer, ByRef y As String) As Integer
Dim Message As String
Dim PortNumber1 As Integer
Dim PortNumber2 As Integer
Dim Number As Integer
Sub Example()
Number = 3
PortNumber1 = Number
PortNumber2 = Number
Message = ":trac:data?"
aa = PortWrite(PortNumber1, Message)
Debug.Print aa
xx = PortRead(PortNumber2)
Debug.Print xx
End Sub
I apology if this question has been already asked or if it is question for programmers from kindergarten but I am very curious. Thanks.
In VBA try changing ByRef to ByVal
The code posted maps "integer = int" and "integer = short int". Which is it? (Ar you compiling 32bit cpp or 64bit cpp?).
Excel will repair the stack if your declarations are invalid, and Excel will not crash, so you should not depend on crashing Excel to find invalid declarations.
When you call a C++ function from VBA ByRef, you might expect to receive a reference in the C++ function, like
short int & Port
But ByRef in VBA means "ByPointer" in C++.
So I found an issue. As you suggested I took closer look on parameters sent to function ByRef. After few experiments I discovered my mistake. I sent a number to function by reference that means I sent only address of memory not a value. Then my first function made few calculations because of conversion to char and creating string and saved a new changed value because of calculations to previous memory address. So when I was sending variable to second function also by reference, the value saved on memory address was already changed by my first function. So I was rewriting value of variable by myself. I should apology to Microsoft for my rude words that I said about its products while I was solving this problem. :) Thanks mates

Unhandled AccessViolationException Error calling a DLL into VB.Net

I'm trying to call a C++-compiled DLL from VB.net and I'm running into some problems. Seems like there's an obvious solution, but I can't figure it out.
Here is the function declaration in C++:
MyFunction(int trailingaveragesize, double sigmasize, int myflag, int sizeSeries, double *Xdata, double *Ydata, int sizeinputparameter, int *averagePairs, double *PositionsSize, double *PnLSize)
Here is how I'm calling it in VB.Net:
Call MyFunction(200, 1, 1, 230, a_PriceSeries(0), a_PriceSeries(0), 1, a_Averages(0), a_PositionSeries(0), a_PnLs(0))
The maximum size of the input matrices are defined by sizeSeries (230), and the size of all my input matrices are 10000 (just so I won't accidentally overflow), yet still i'm getting an unhandled AccessViolationException error
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
My question is - If I'm not exceeding the bounds of my matrices, what other reasons would throw this error? Is it because I'm only passing the first entry in my matrices ByReference then it's trying to access other elements of that matrix? If so, how would I fix that?
EDIT:
Here's how I'm declaring it in VB
Declare Function MyFunction Lib "C:\Dev\asdf.dll" (ByVal trailingaveragesize As Long, ByVal sigmasize As Double, ByVal myflag As Long, ByVal sizeSeries As Long, ByRef Xdata As Double, ByRef Ydata As Double, ByVal sizeinputparameter As Long, ByRef averagePairs As Long, ByRef PositionsSize As Double, ByRef PnLSize As Double) As Double
Declare Function MyFunction Lib "C:\Dev\asdf.dll" (ByVal trailingaveragesize As Long, _
ByVal sigmasize As Double, ByVal myflag As Long, ByVal sizeSeries As Long,
ByRef Xdata As Double, ByRef Ydata As Double, ByVal sizeinputparameter As Long,
ByRef averagePairs As Long, ByRef PositionsSize As Double, ByRef PnLSize As Double) As Double
The declaration is simply wrong, this resembles a vb6 declaration. An int in C code is an Integer in vb.net, not a Long. The Xdata and Ydata are highly likely to be arrays, not a byref double. Declare them as ByVal Double(). The other byref args are harder to guess.
p-invoke is platform invoke, and is how you call into native APIs with .NET. Your declaration is not currently setup to pass arrays, and it should not be done ByRef.
Try changing the ByRef to ByVal for your array variables and declare them with the () to signify an array.
The error is that while I was declaring a variable as type long but it should be of type integer. The variabe rc was the one wrongly declared:
Here the code of my form Form1 (it has one Button and one TextBox):
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Here the code for Button.Click (it initialize some variables used to call a function in a dll):
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim arg1 As String
Dim arg2 As String
Dim ret As String
Here is the variable rc that I declared as long. It should be integer
Dim rc As long
ret = "123456789012345678901234567890123"
arg1 = "123456"
arg2 = "1234567890123456"
rc = myFunction(arg1, arg2, ret)
If (rc = 0) Then
TextBox1.Text = ret
Else
TextBox1.Text = "Result not OK"
End If
End Sub
End Class
myFunction was declared as integer in myDll:
extern __declspec(dllexport) int __stdcall myFunction(unsigned char * pszArg1, unsigned char * pszArg2, char * pszReturn)
This is how I declared MyFunction in my Visual Basic Express 2010:
Module Module1
Public Declare Function myFunction Lib "C:\Users\me\MyProjects\myDll\bin\Debug\myDll.dll" (ByVal Arg1 As String, ByVal Arg2 As String, ByVal ret As String) As Integer
End Module