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
Related
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.
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.
I have the following c++ code :
#include <OAIdl.h> // for VARIANT, BSTR etc
__declspec(dllexport) void __stdcall updatevar(VARIANT * x)
{
double nb = 3.0;
++nb;
}
with a function doing (almost) nothing (to avoid warnings)
and a .def file :
LIBRARY tmp0
EXPORTS
updatevar #1LIBRARY
I compile this with visual studio 2013 into a dll that I reference as follows in excel-2013's VBA :
Declare Sub updatevar Lib "C:\path\to\thedll.dll" (ByRef x As Variant)
and that I use like this :
Public Sub DoIt()
Dim r As Range
Set r = ActiveWorkbook.Sheets("Sheet1").Range("B2:C4")
Dim var As Variant
var = r.Value
Call updatevar(var)
End Sub
where the 6-cells excel range B2:C4 contains strings, dates, doubles and ints/longs.
I put a breakpoint in c++ code to inspect the variant pointed to that I receive, as remarked that the type of its 6 elements is always rightly resolved : dates go to a vt (variant type VT_DATE) equal to 7, doubles to a vt (variant type VT_R8) equal to 5, strings go to a vt (variant type VT_BSTR) of 8, except for ints/longs, that are mapped to VT_R8 and treated as doubles.
At the beginning I thought it was a c++ problem but, already inspecting the range r in the VBA code, and its Value2 field, showed to me that all ints/longs were treated in VBA as Variant/Double and not Variant/Long, and I have no idea why this is happening.
Note. I put c++ and dll tags as the people interested in these tags may also help given the context involving exchanging VARIANT's between c++ and VBA.
Remark. "Downcasting" from double to int is not an option, especially as VARIANT is supposed to know about ints/longs (VT_I4 = 2 and VT_I4 = 3 do exist.)
I'm trying to write a function that will reinterpret a set of bytes as a float. I have viewed a Stackoverflow question which lead me to try reinterpret_cast<>() on an array of characters, and I started experimenting with splitting a float into chars and then reassembling it again, but that only gives me seemingly random numbers rather than what I think the value should be, as the output is different each time. A few different examples are:
1.58661e-038
3.63242e-038
2.53418e-038
The float variable should contain the value 5.2.
Compiling the code:
float f = 5.2;
unsigned char* bytes = reinterpret_cast<unsigned char*>(&f);
float f1 = reinterpret_cast<float&>(bytes);
std::cout << f1 << std::endl;
gave me
1.75384e-038
And then of course, a different number each time the program is run. Interestingly, however, if I put the code in a loop and execute it several times in a single run, the output stays consistent. This leads me to think it might be a memory location, but if so, I'm not sure how to access the real value of the variable - the dereference operator doesn't work, because it's not a pointer.
So my question is - How can I split a variable of type float (and later on, a double) into individual bytes (allowing me to modify the bits), and then reassemble it.
Any help would be greatly appreciated.
bytes is a pointer.
Change
float f1 = reinterpret_cast<float&>(bytes);
to
float f1 = *reinterpret_cast<float*>(bytes);
// Cast to a different pointer... ^
// ^ ...and dereference that pointer.
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;
}