0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VBA で使う C++ DLL をコーディングする際のあれこれ

Posted at

概要

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&

BSTRVARINATLPSAFEARRAYoaid.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)にして渡します。一方でVarinatString()の文字列はワイド文字列で渡します。

ラッパー

文字列の変換

マルチバイト文字列→ワイド文字列

マルチバイト文字列→ワイド文字列:mbstowcs_s利用
#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;
}

マルチバイト文字列→ワイド文字列:MultiByteToWideChar利用
#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;
}

ワイド文字列→マルチバイト文字列

ワイド文字列→マルチバイト文字列:wcstombs_s利用
#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());
}

ワイド文字列→マルチバイト文字列:WideCharToMultiByte利用
#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 構造体

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 構造体

SAFEATTAY 構造体
typedef struct tagSAFEARRAY
    {
    USHORT cDims;					// 次元数
    USHORT fFeatures;				// フラグ
    ULONG cbElements;				// 要素当たりのバイト数
    ULONG cLocks;					// ロックされた回数
    PVOID pvData;					// データ
    SAFEARRAYBOUND rgsabound[ 1 ];	// 次元ごとの開始インデックスと要素数
    } 	SAFEARRAY;

配列要素の型

SAFEARRAYはフラグfFeaturesFADF_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;
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?