概要
VBA から利用する C++ DLLの作成に特化した自分用Tipsです。
型
引数:VBA → C++ DLL
種類 | サイズ | VBA | 引渡し方法 | DLL |
---|---|---|---|---|
論理値 | 4 | Boolean |
ByVal |
int BOOL
|
ByRef |
int* or int& BOOL* or BOOL&
|
|||
バイト | 1 | Byte |
ByVal |
unsigned char BYTE
|
ByRef |
unsigned char* or unsigned char& BYTE* or BYTE&
|
|||
整数 | 2 | Integer |
ByVal |
short |
ByRef |
short* or short&
|
|||
4 | Long |
ByVal |
int LONG
|
|
ByRef |
int* or int& LONG* or LONG&
|
|||
8 | LongLong |
ByVal |
long long LONGLONG
|
|
ByRef |
long long* or long long& LONGLONG* or LONGLONG&
|
|||
浮動小数点 | 4 | Single |
ByVal |
float |
ByRef |
float* or float&
|
|||
8 | Double |
ByVal |
double |
|
ByRef |
double* or double&
|
|||
日付 | 8 | Date |
ByVal |
double |
ByRef |
double* or double&
|
|||
文字列 | 8+可変 | String |
ByVal |
BSTR |
ByRef |
BSTR* or BSTR&
|
|||
バリアント | 8 | Variant |
ByVal |
VARIANT |
ByRef |
VARIANT* or VARIANT&
|
|||
配列 | 可変 |
Long() , String() , etc. |
ByRef のみ |
SAFEARRAY* or SAFEARRAY&
|
BSTR
、VARINAT
、LPSAFEARRAY
は oaid.h ヘッダー で定義されている他、大文字で表記されている型の利用には、#include <comutil.h>
や #include <comdef.h>
などのインクルードが必要です。
参照渡し ByRef
についてはポインタ*
でも参照&
でも受けられますが、値を取得するため間接参照演算子を付けなくてよいので参照&
の方が取り回しやすいと思います。
VBA の論理型 Boolean
について、C++ で BOOLEAN
は1バイトの BYTE
と定義されていますので、BOOL
と混同しないよう注意が必要です。
戻り値:C++ DLL → VBA
種類 | サイズ | DLL | VBA |
---|---|---|---|
論理 | 4 | int |
Boolean |
バイト | 1 | unsigned char |
Byte |
整数 | 2 | short |
Integer |
4 | int |
Long |
|
8 | long long |
LongLong |
|
浮動小数点 | 4 | float |
Single |
8 | double |
Double |
|
文字列 | 可変 | BSTR |
String |
バリアント | 8 | VARIANT |
Variant |
配列 | 可変 | LPSAFEARRAY |
Variant() , Long() , String() , etc. |
- C++のデータ型
- VBAのデータ型
文字列
VBAの文字列型String
は、C++ではBSTR
のデータ型となります。BSTR
はマルチバイト文字またはワイド文字の配列を示すポインタであり、また配列の手前4バイトにはNULL末端を除く配列のサイズ(バイト数)が配置されます。VBAはUnicode(ワイド文字)で文字列を扱いますが、DLLにString
を渡す際にはシステムロケールのマルチバイト文字列(ANSI)にして渡します。一方でVarinat
やString()
の文字列はワイド文字列で渡します。
ラッパー
文字列の変換
マルチバイト文字列→ワイド文字列
#include <comutil.h>
#include <string>
#include <locale.h>
VARIANT WINAPI MultibyteToWideChar1(BSTR s)
{
setlocale(LC_CTYPE, "ja-JP"); // 日本語ロケールを設定
size_t len;
mbstowcs_s(&len, nullptr, 0, (char *)s, _TRUNCATE);
std::wstring ws(len, L'\0'); // NULL文字分を含む
mbstowcs_s(&len, &ws[0], len, (char *)s, _TRUNCATE);
ws.resize(len - 1); // NULL文字分を減らす
VARIANT v;
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = SysAllocString(ws.c_str());
return v;
}
#include <comutil.h>
#include <string>
VARIANT WINAPI MultibyteToWideChar2(BSTR s)
{
size_t len = MultiByteToWideChar(CP_ACP, 0, (char *)s, -1, nullptr, 0) - 1; // NULL文字分を減らす
std::wstring ws = std::wstring(len, L'\0');
MultiByteToWideChar(CP_ACP, 0, (char *)s, -1, &ws[0], len);
ws += ws;
VARIANT v;
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = SysAllocString(ws.c_str());
return v;
}
ワイド文字列→マルチバイト文字列
#include <comutil.h>
#include <string>
#include <locale.h>
BSTR WINAPI WideCharToMultibyte1(VARIANT v)
{
setlocale(LC_CTYPE, "ja-JP"); // 日本語ロケールを設定
size_t len;
wcstombs_s(&len, nullptr, 0, v.bstrVal, _TRUNCATE);
std::string mbs(len, '\0');
wcstombs_s(&len, &mbs[0], len, v.bstrVal, _TRUNCATE);
mbs.resize(len - 1); // NULL文字分を減らす
return SysAllocStringByteLen(mbs.c_str(), mbs.length());
}
#include <comutil.h>
#include <string>
BSTR WINAPI WideCharToMultibyte2(VARIANT v)
{
size_t len = WideCharToMultiByte(CP_ACP, 0, v.bstrVal, -1, nullptr, 0, nullptr, nullptr) - 1; // NULL文字分を減らす
std::string mbs(len, '\0');
WideCharToMultiByte(CP_ACP, 0, v.bstrVal, -1, &mbs[0], len, nullptr, nullptr);
return SysAllocStringByteLen(mbs.c_str(), mbs.length());
}
バリアント
バリアント型は型情報と値の両方を保持するデータ型です。
VARINAT 構造体
struct tagVARIANT
{
union
{
struct __tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union
{
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
DOUBLE dblVal;
VARIANT_BOOL boolVal;
VARIANT_BOOL __OBSOLETE__VARIANT_BOOL;
SCODE scode;
CY cyVal;
DATE date;
BSTR bstrVal;
IUnknown *punkVal;
IDispatch *pdispVal;
SAFEARRAY *parray;
BYTE *pbVal;
SHORT *piVal;
LONG *plVal;
LONGLONG *pllVal;
FLOAT *pfltVal;
DOUBLE *pdblVal;
VARIANT_BOOL *pboolVal;
VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL;
SCODE *pscode;
CY *pcyVal;
DATE *pdate;
BSTR *pbstrVal;
IUnknown **ppunkVal;
IDispatch **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 *pRecInfo;
} __VARIANT_NAME_4;
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
} ;
バリアント型変数の型
VBA型 | VARENUM 列挙 |
---|---|
Variant/Empty | VT_EMPTY |
Variant/Null | VT_NULL |
Variant/Byte | VT_UI1 |
Variant/Integer | VT_I2 |
Variant/Long | VT_I4 |
Variant/LongLong | VT_I8 |
Variant/LongPtr | VT_I8 |
Variant/Single | VT_R4 |
Variant/Double | VT_R8 |
Variant/Currency | VT_CY |
Variant/Date | VT_DATE |
Variant/String | VT_BSTR |
Variant/Boolean | VT_BOOL |
Variant/Object | VT_DISPATCH |
配列 | VT_ARRAY |
Variant/Varinat(0 To 1) | VT_VARIANT + VT_ARRAY |
バリアントの作成
バリアント文字列の作成
#include <comutil.h>
VARIANT WINAPI CreateVariant()
{
VARIANT v;
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = SysAllocString(L"Hello World.");
return v;
}
配列
VBAの配列は、どの型でもSAFEARRAY構造体でやり取りします。
SAFEARRAY 構造体
typedef struct tagSAFEARRAY
{
USHORT cDims; // 次元数
USHORT fFeatures; // フラグ
ULONG cbElements; // 要素当たりのバイト数
ULONG cLocks; // ロックされた回数
PVOID pvData; // データ
SAFEARRAYBOUND rgsabound[ 1 ]; // 次元ごとの開始インデックスと要素数
} SAFEARRAY;
配列要素の型
SAFEARRAYはフラグfFeatures
にFADF_HAVEVARTYPE
が指定されている場合、-4バイト位置に要素の型情報がVARENUM 列挙で記録されます。VBAの配列はすべてFADF_HAVEVARTYPE
が立ちます。SAFEARRAYに設定されたVARNUMの値はSafeArrayGetVartype
で取得できます。
VBA型 | VARENUM 列挙 | 追加フラグ |
---|---|---|
Byte | VT_UI1 | |
Integer | VT_I2 | |
Long | VT_I4 | |
LongLong | VT_I8 | |
LongPtr | VT_I8 | |
Single | VT_R4 | |
Double | VT_R8 | |
Currency | VT_CY | |
Date | VT_DATE | |
String | VT_BSTR | FADF_BSTR |
Boolean | VT_BOOL | |
Object | VT_DISPATCH | |
Variant | VT_VARIANT | FADF_VARIANT |
配列の作成
線形配列(一次元配列)の作成例
#include <comutil.h>
SAFEARRAY *WINAPI CreateVectorSafeArray()
{
// VT_I4はVBAでのLong型相当、以下の宣言と同じ
// Dim sa() As Long: Redim sa(1 To 3)
SAFEARRAY *psa = SafeArrayCreateVector(VT_I4, 1, 3);
if (!psa)
return nullptr; // 失敗
// 配列に値を設定
static_cast<int *>(psa->pvData)[0] = 123; // sa(1) = 123
static_cast<int *>(psa->pvData)[1] = 456; // sa(2) = 456
static_cast<int *>(psa->pvData)[2] = 789; // sa(3) = 789
return psa;
}
多次元配列の作成例
#include <comutil.h>
SAFEARRAY *WINAPI CreateVectorSafeArray()
{
// 2行3列の文字列配列を作成、以下の宣言と同じ
// Dim sa As Sting: Redim sa(1 To 2, 1 To 3)
SAFEARRAYBOUND rgsabound[2];
rgsabound[0].lLbound = 1;
rgsabound[0].cElements = 2;
rgsabound[1].lLbound = 1;
rgsabound[1].cElements = 3;
SAFEARRAY *psa = SafeArrayCreate(VT_BSTR, 2, rgsabound);
if (!psa)
return nullptr; // 失敗
// 配列に値を設定、SAFEARRAYの文字列はワイド文字列
HRESULT _;
long index_lt[2] = { 1, 1 }; // (1, 1)
_ = SafeArrayPutElement(psa, index_lt, SysAllocString(L"左上"));
long index_ct[2] = { 1, 2 }; // (1, 2)
_ = SafeArrayPutElement(psa, index_ct, SysAllocString(L"中上"));
long index_rt[2] = { 1, 3 }; // (1, 3)
_ = SafeArrayPutElement(psa, index_rt, SysAllocString(L"右上"));
long index_lb[2] = { 2, 1 }; // (2, 1)
_ = SafeArrayPutElement(psa, index_lb, SysAllocString(L"左下"));
long index_cb[2] = { 2, 2 }; // (2, 2)
_ = SafeArrayPutElement(psa, index_cb, SysAllocString(L"中下"));
long index_rb[2] = { 2, 3 }; // (2, 3)
_ = SafeArrayPutElement(psa, index_rb, SysAllocString(L"右下"));
return psa;
}