Под mac os x у меня есть офис 2011 и его excel и VBA, и у меня есть g++ от gcc-5.3.0.
Я много играл в передачу массивов (числовых встроенных типов) из VBA в dylib (расширение dll для mac os x), обновлял их и отправлял обратно в VBA, см., например:
передача функции c/c++ dylib, принимающей указатель на VBA на Mac
Теперь я хотел бы сделать то же самое со строками, и сначала только с одной строкой, а не с массивом. Я хочу получить строку из VBA, изменить ее на С++ и отправить обратно, обновленную, в VBA.
Код на стороне C++
:
#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);
}
}
в файле thedylib.dylib
, скомпилированном следующим образом (офис 2011 для mac os x 32 бита):
g++ -m32 -Wall -g -c ./thedylib.cpp
g++ -m32 -dynamiclib ./thedylib.o -o ./thedylib.dylib
тогда как на стороне VBA (в excel 2011 для mac os x) у меня есть этот код:
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
и при его выполнении появляется "ЛЮДВИГ ФОН МИЗЕС", как и ожидалось.
Примечание 1. Обратите внимание на ByVal
перед строкой в подписи подписчика. Установка вместо этого ByRef
привела бы к сбою во время выполнения. Еще более странно: представьте, что я добавляю tolowerfunc
в свою dylib (тот же код, что и для toupperfunc
, но опираясь на функцию tolower
C++ из ctype.h
). Это также работает, как и ожидалось, но на этот раз размещение ByRef
перед строкой в подписи вместо ByVal
больше не вызывает сбой во время выполнения. Итак, есть ли что-то другое между функциями С++ toupper
и tolower
? Если так, то ? Что объясняет такое поведение?
Примечание 2. Как следствие того факта, что то, что я описал выше, работает, теперь мы знаем, что Excel 2011 VBA на mac os x не обменивается строками с dylib, используя тот же формат в памяти, который COM BSTR использует. Вместо этого он использует строки char*
с завершающим нулем.
Принимая во внимание замечание 2 и поскольку моей долгосрочной целью было «использование VARIANT под os x», см., например:
Передача ВАРИАНТА из mac OS X Excel 2011 VBA на С++,
Наконец, я сымитировал структуру VARIANT
в файле VARIANT.h
, как вы можете видеть в следующем коде:
#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
Я придерживаюсь того, что сделано на стороне Windows для всего, кроме BSTR
, для которого я установил:
typedef char WCHAR;
typedef WCHAR OLECHAR;
typedef OLECHAR * BSTR;
(Я также вырезал чистый материал COM, а именно части кода, касающиеся IUnknown
и IDispatch
.)
Определив эту «легкую» структуру VARIANT
, я хочу сыграть в ту же игру, что и для массивов double
и string
, то есть обменяться VARIANT
между VBA и динамической библиотекой C++. Итак, я разработал этот код C++:
#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;
}
}
}
}
вставьте thevarianttest.cpp
, скомпилированный следующим образом:
g++ -m32 -Wall -g -c ./thevarianttest.cpp
g++ -m32 -dynamiclib ./thevarianttest.o -o ./thevarianttest.dylib
и используется на стороне VBA (в excel 2011 для mac os x) следующим образом:
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
Выполнение этого кода VBA теперь последовательно выскакивает: -1332 Людвиг фон Мизес Людвиг фон Мизес
первые два в порядке, но для последнего ожидалось "ЛЮДВИГ ФОН МИЗЕС"... И последний, в конечном счете, опирается на функцию toupper
C++, ту же функцию из предыдущего примечания 1, которая не позволяла мне ByRef
использовать в VBA в первом примере...
Так что же происходит? Почему в первом примере все работало, а во втором нет?
Примечание 3. Обратите внимание на ByRef
перед VARIANT
подписью подписи. Вместо этого вставка ByVal
вызывает сбой во время выполнения... (Это противоположно тому, что происходило со строками и без вариантов в первом коде VBA.)