Under mac os x I have office 2011 and its excel and VBA, and I have gcc-5.3.0's g++.
I played a lot to passing arrays (of numerical built-in types) from VBA to the dylib (extension of dll's on mac os x) and updating them and sending them back to VBA, see for instance :
passing c/c++ dylib function taking pointer to VBA on mac
Now, I would like to do the same with strings, and first with just one string, not an array. I want to receive the string from VBA, modify it in C++, and send it back, updated, to VBA.
The code on the C++
side is :
#include <stdlib.h>
#include <ctype.h> //for toupper
extern "C"
{
void toupperfunc(char *vbstr)
{
size_t i = 0U;
char c;
do {
c = vbstr[i];
vbstr[i]=toupper(c);
++i;
} while(vbstr[i]!=0);
}
}
in the file thedylib.dylib
, compiled as follows (office 2011 for mac os x is 32 bits) :
g++ -m32 -Wall -g -c ./thedylib.cpp
g++ -m32 -dynamiclib ./thedylib.o -o ./thedylib.dylib
whereas on the VBA side (in excel 2011 for mac os x) I have this code :
Declare Sub toupperfunc Lib "/path/to/the/dylib/thedylib.dylib" (ByVal str As String)
Public Sub DoIt()
Dim str As String
str = "Ludwig von Mises"
Call toupperfunc(str)
MsgBox (str)
End Sub
and executing it pop's out "LUDWIG VON MISES" as expected.
Remark 1. Note the ByVal
in front of the string in the sub's signature. Putting a ByRef
instead would have produced a crash at runtime. Even more strange : imagine I add a tolowerfunc
in my dylib (same code as for toupperfunc
but relying on ctype.h
's tolower
C++ function). It also works as expected, but this time, putting a ByRef
in front of the string in the signature instead of a ByVal
doesn't provoke a runtime crash anymore. So, is there something different between C++ functions toupper
and tolower
? If so, what ? What's does explain this behaviour ?
Remark 2. As a corollary of the fact that what I described above works, we know now that Excel 2011 VBA on a mac os x does not exchange strings with a dylib using the same in-memory format that a COM BSTR uses. It uses null-terminated char*
strings instead.
Taking into account remark 2 and as "using VARIANT under os x" was my long-term goal, see for instance :
Passing a VARIANT from mac OS X Excel 2011 VBA to c++,
I finally mimicked a VARIANT
structure in a VARIANT.h
file as you can see in the following code :
#ifndef VARIANT_H
#define VARIANT_H
#include <inttypes.h> // needed for gcc analogues of __int64 and unsigned __int64
typedef unsigned short VARTYPE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
// typedef __int64 LONGLONG;
typedef int64_t LONGLONG;
// typedef unsigned __int64 ULONGLONG;
typedef uint64_t ULONGLONG;
typedef long LONG;
typedef unsigned char BYTE;
typedef short SHORT;
typedef float FLOAT;
typedef double DOUBLE;
/* 0 == FALSE, -1 == TRUE */
typedef short VARIANT_BOOL;
/* For backward compatibility */
typedef bool _VARIANT_BOOL;
typedef LONG SCODE;
typedef unsigned long ULONG;
typedef unsigned short USHORT;
typedef unsigned long ULONG;
typedef char CHAR;
typedef unsigned char byte;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned int * PUINT;
typedef union tagCY
{
struct _tagCY
{
ULONG Lo;
LONG Hi;
} DUMMYSTRUCTNAME;
LONGLONG int64;
} CY;
typedef double DATE;
/*#ifndef _MAC*/
//typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
typedef char WCHAR;
/*#else
// some Macintosh compilers don't define wchar_t in a convenient location, or define it as a char
typedef unsigned short WCHAR; // wc, 16-bit UNICODE character
#endif*/
typedef WCHAR OLECHAR;
typedef OLECHAR * BSTR;
typedef BSTR * LPBSTR;
typedef void * PVOID;
/*// #define POINTER_64 __ptr64
#define POINTER_64 unsigned long long*/
//typedef void *POINTER_64 PVOID64;
typedef struct tagSAFEARRAYBOUND
{
ULONG cElements;
LONG lLbound;
} SAFEARRAYBOUND;
typedef struct tagSAFEARRAYBOUND * LPSAFEARRAYBOUND;
typedef struct tagSAFEARRAY
{
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY;
typedef SAFEARRAY * LPSAFEARRAY;
typedef struct tagDEC
{
USHORT wReserved;
union
{
struct
{
BYTE scale;
BYTE sign;
} DUMMYSTRUCTNAME;
USHORT signscale;
} DUMMYUNIONNAME1;
ULONG Hi32;
union
{
struct
{
ULONG Lo32;
ULONG Mid32;
} DUMMYSTRUCTNAME;
ULONGLONG Lo64;
} DUMMYUNIONNAME2;
} DECIMAL;
/*#define __tagVARIANT
#define __VARIANT_NAME_1
#define __VARIANT_NAME_2
#define __VARIANT_NAME_3
#define __tagBRECORD
#define __VARIANT_NAME_4*/
typedef /* [wire_marshal] */ struct tagVARIANT VARIANT;
struct tagVARIANT
{
union
{
struct __tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union
{
// non ptr stuff
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
DOUBLE dblVal;
VARIANT_BOOL boolVal;
// _VARIANT_BOOL bool;
SCODE scode;
CY cyVal;
DATE date;
BSTR bstrVal;
// ptr stuff
/*IUnknown*/ void *punkVal;
/*IDispatch*/ void *pdispVal;
SAFEARRAY * parray;
BYTE * pbVal;
SHORT * piVal;
LONG * plVal;
LONGLONG * pllVal;
FLOAT * pfltVal;
DOUBLE * pdblVal;
VARIANT_BOOL * pboolVal;
_VARIANT_BOOL * pbool;
SCODE * pscode;
CY * pcyVal;
DATE * pdate;
BSTR * pbstrVal;
/*IUnknown*/ void ** ppunkVal;
/*IDispatch*/ void ** ppdispVal;
SAFEARRAY ** pparray;
VARIANT * pvarVal;
PVOID byref;
CHAR cVal;
USHORT uiVal;
ULONG ulVal;
ULONGLONG ullVal;
INT intVal;
UINT uintVal;
DECIMAL * pdecVal;
CHAR * pcVal;
USHORT * puiVal;
ULONG * pulVal;
ULONGLONG * pullVal;
INT * pintVal;
UINT * puintVal;
struct __tagBRECORD
{
PVOID pvRecord;
/*IRecordInfo*/ void * pRecInfo;
} VARIANT_NAME_4;
} VARIANT_NAME_3;
} VARIANT_NAME_2;
DECIMAL decVal;
} VARIANT_NAME_1;
};
typedef VARIANT * LPVARIANT;
typedef VARIANT VARIANTARG;
typedef VARIANT * LPVARIANTARG;
#endif
I stick to what's done on the windows side for everything except for BSTR
for which I set :
typedef char WCHAR;
typedef WCHAR OLECHAR;
typedef OLECHAR * BSTR;
(I also cut out pure COM stuff, namely portions of code concerning IUnknown
and IDispatch
.)
Having defined this "light" VARIANT
structure, I want to play the same game as for arrays of double
's and for string
's, that is, exchange VARIANT
's between VBA and a C++ dynamic library. So I designed this C++ code :
#include "/path/to/VARIANT.h"
#include <ctype.h>
VARTYPE getvt(const VARIANT & var_in)
{
return var_in.VARIANT_NAME_1.VARIANT_NAME_2.vt;
}
extern "C"
{
void updatevar(VARIANT * var_in_out, bool converttoupper)
{
VARTYPE vt = getvt(*var_in_out);
switch (vt)
{
case 3:
{
long l = (*var_in_out).VARIANT_NAME_1.VARIANT_NAME_2.VARIANT_NAME_3.lVal;
l *= 2L;
(*var_in_out).VARIANT_NAME_1.VARIANT_NAME_2.VARIANT_NAME_3.lVal = l;
}
break;
case 8024:
{
}
break;
case 8:
{
BSTR wc = (*var_in_out).VARIANT_NAME_1.VARIANT_NAME_2.VARIANT_NAME_3.bstrVal ;
int i = 0;
do
{
char c = wc[i];
wc[i]= converttoupper ? static_cast<char>(toupper(c)) : static_cast<char>(tolower(c));
++i;
} while(wc[i]!=0);
(*var_in_out).VARIANT_NAME_1.VARIANT_NAME_2.VARIANT_NAME_3.bstrVal = &wc[0];
}
break;
default:
{
return;
}
}
}
}
put in thevarianttest.cpp
, compiled as follows :
g++ -m32 -Wall -g -c ./thevarianttest.cpp
g++ -m32 -dynamiclib ./thevarianttest.o -o ./thevarianttest.dylib
and used in the VBA side (in excel 2011 for mac os x) as follows :
Declare Sub updatevar Lib "/path/to/the/dylib/thevarianttest.dylib" (ByRef x As Variant, ByVal istoupper As Boolean)
Public Sub doit()
Dim x As Variant
Dim l As Long
l = -666
x = l
Call updatevar(x, True)
MsgBox (x)
Dim s as String
s = "Ludwig von Mises"
x = s
Call updatevar(x, False)
MsgBox (x)
s = "Ludwig von Mises"
x = s
Call updatevar(x, True)
MsgBox (x) 'FAILURE...
End Sub
Executing this VBA code now successively