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

# 概要
VBA から利用する C++ DLLの作成に特化した自分用Tipsです。

# 型
## 引数：VBA → C++ DLL
<table>
    <tr>
        <th>種類</th>
        <th>サイズ</th>
        <th>VBA</th>
        <th>引渡し方法</th>
        <th>DLL</th>
     </tr>
     <tr>
        <td rowspan=2>論理値</td>
        <td rowspan=2>4</td>
        <td rowspan=2><code>Boolean</code></td>
        <td><code>ByVal</code></td>
        <td><code>int</code><br><code>BOOL</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>int*</code> or <code>int&</code><br>
        <code>BOOL*</code> or <code>BOOL&</code></td>
     </tr>
     <tr>
        <td rowspan=2>バイト</td>
        <td rowspan=2>1</td>
        <td rowspan=2><code>Byte</code></td>
        <td><code>ByVal</code></td>
        <td><code>unsigned char</code><br><code>BYTE</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>unsigned char*</code> or <code>unsigned char&</code><br>
        <code>BYTE*</code> or <code>BYTE&</code></td>
     </tr>
     <tr>
        <td rowspan=6>整数</td>
        <td rowspan=2>2</td>
        <td rowspan=2><code>Integer</code></code></td>
        <td><code>ByVal</code></td>
        <td><code>short</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>short*</code> or <code>short&</code></td>
     </tr>
     <tr>
        <td rowspan=2>4</td>
        <td rowspan=2><code>Long</code></td>
        <td><code>ByVal</code></td>
        <td><code>int</code><br><code>LONG</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>int*</code> or <code>int&</code><br>
        <code>LONG*</code> or <code>LONG&</code></td>
     </tr>
     <tr>
        <td rowspan=2>8</td>
        <td rowspan=2><code>LongLong</td>
        <td><code>ByVal</code></td>
        <td><code>long long</code><br>
        <code>LONGLONG</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>long long*</code> or <code>long long&</code><br>
        <code>LONGLONG*</code> or <code>LONGLONG&</code></td>
     </tr>
     <tr>
        <td rowspan=4>浮動小数点</td>
        <td rowspan=2>4</td>
        <td rowspan=2><code>Single</code></td>
        <td><code>ByVal</code></td>
        <td><code>float</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>float*</code> or <code>float&</code></td>
     </tr>
     <tr>
        <td rowspan=2>8</td>
        <td rowspan=2><code>Double</code></td>
        <td><code>ByVal</code></td>
        <td><code>double</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>double*</code> or <code>double&</code></td>
     </tr>
     <tr>
        <td rowspan=2>日付</td>
        <td rowspan=2>8</td>
        <td rowspan=2><code>Date</code></td>
        <td><code>ByVal</code></td>
        <td><code>double</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>double*</code> or <code>double&</code></td>
     </tr>
     <tr>
        <td rowspan=2>文字列</td>
        <td rowspan=2>8+可変</td>
        <td rowspan=2><code>String</code></td>
        <td><code>ByVal</code></td>
        <td><code>BSTR</code></td>
     </tr>
     <tr>
        <td>ByRef</td>
        <td><code>BSTR*</code> or <code>BSTR&</code></td>
     </tr>
     <tr>
        <td rowspan=2>バリアント</td>
        <td rowspan=2>8</td>
        <td rowspan=2><code>Variant</code></td>
        <td><code>ByVal</code></td>
        <td><code>VARIANT</code></td>
     </tr>
     <tr>
        <td><code>ByRef</code></td>
        <td><code>VARIANT*</code> or <code>VARIANT&</code></td>
     </tr>
     <tr>
        <td rowspan=2>配列</td>
        <td rowspan=2>可変</td>
        <td rowspan=2><code>Long()</code>, <code>String()</code>, etc.</td>
        <td><code>ByRef</code>のみ</td>
        <td><code>SAFEARRAY*</code> or <code>SAFEARRAY&</code></td>
     </tr>
 </table>

 `BSTR`、`VARINAT`、`LPSAFEARRAY`は [oaid.h ヘッダー](https://learn.microsoft.com/ja-jp/windows/win32/api/oaidl/, 'Oaidl.h ヘッダー - Win32 apps | Microsoft Learn') で定義されている他、大文字で表記されている型の利用には、`#include <comutil.h>` や `#include <comdef.h>` などのインクルードが必要です。
 参照渡し `ByRef` についてはポインタ`*`でも参照`&`でも受けられますが、値を取得するため間接参照演算子を付けなくてよいので参照`&`の方が取り回しやすいと思います。
 VBA の論理型 `Boolean` について、C++ で `BOOLEAN` は１バイトの `BYTE` と定義されていますので、`BOOL` と混同しないよう注意が必要です。


 ## 戻り値：C++ DLL → VBA
 
<table>
    <tr>
        <th>種類</th>
        <th>サイズ</th>
        <th>DLL</th>
        <th>VBA</th>
     </tr>
     <tr>
        <td>論理</td>
        <td>4</td>
        <td><code>int</code></td>
        <td><code>Boolean</code></td>
     </tr>
     <tr>
        <td>バイト</td>
        <td>1</td>
        <td><code>unsigned char</code></td>
        <td><code>Byte</code></td>
     </tr>
     <tr>
        <td rowspan=3>整数</td>
        <td>2</td>
        <td><code>short</code></td>
        <td><code>Integer</code></td>
     </tr>
     <tr>
        <td>4</td>
        <td><code>int</code></td>
        <td><code>Long</code></td>
     </tr>
     <tr>
        <td>8</td>
        <td><code>long long</code></td>
        <td><code>LongLong</code></td>
     </tr>
     <tr>
        <td rowspan=2>浮動小数点</td>
        <td>4</td>
        <td><code>float</code></td>
        <td><code>Single</code></td>
     </tr>
     <tr>
        <td>8</td>
        <td><code>double</code></td>
        <td><code>Double</code></td>
     </tr>
     <tr>
        <td>文字列</td>
        <td>可変</td>
        <td><code>BSTR</code></td>
        <td><code>String</code></td>
     </tr>
     <tr>
        <td>バリアント</td>
        <td>8</td>
        <td><code>VARIANT</code></td>
        <td><code>Variant</code></td>
     </tr>
     <tr>
        <td>配列</td>
        <td>可変</td>
        <td><code>LPSAFEARRAY</code></td>
        <td><code>Variant()</code>, <code>Long()</code>, <code>String()</code>, etc.</td>
     </tr>
 </table>

* C++のデータ型

https://learn.microsoft.com/ja-jp/cpp/cpp/data-type-ranges?view=msvc-170

* VBAのデータ型

https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/data-type-summary

 # 文字列
VBAの文字列型`String`は、C++では`BSTR`のデータ型となります。`BSTR`はマルチバイト文字またはワイド文字の配列を示すポインタであり、また配列の手前4バイトにはNULL末端を除く配列のサイズ（バイト数）が配置されます。VBAはUnicode（ワイド文字）で文字列を扱いますが、DLLに`String`を渡す際にはシステムロケールのマルチバイト文字列（ANSI）にして渡します。一方で`Varinat`や`String()`の文字列はワイド文字列で渡します。

https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr

 https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/9c5a5ce4-ff5b-45ce-b915-ada381b34ac1

## ラッパー

https://learn.microsoft.com/ja-jp/cpp/cpp/bstr-t-class?view=msvc-170

https://learn.microsoft.com/ja-jp/cpp/atl/reference/ccombstr-class?view=msvc-170
 
 ## 文字列の変換

 ### マルチバイト文字列→ワイド文字列
 ```cpp:マルチバイト文字列→ワイド文字列：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;
}
 ```
 
https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/mbstowcs-s-mbstowcs-s-l?view=msvc-170

 ```cpp:マルチバイト文字列→ワイド文字列：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;
}
 ```
 
 https://learn.microsoft.com/ja-jp/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
 
 ### ワイド文字列→マルチバイト文字列
 ```cpp:ワイド文字列→マルチバイト文字列：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());
}
 ```
 
 https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/wcstombs-s-wcstombs-s-l?view=msvc-170
 
```cpp:ワイド文字列→マルチバイト文字列：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());
}
```

https://learn.microsoft.com/ja-jp/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte

# バリアント

バリアント型は型情報と値の両方を保持するデータ型です。

https://learn.microsoft.com/ja-jp/office/vba/language/concepts/getting-started/understanding-variants

## VARINAT 構造体
```cpp: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;
    } ;
```

https://learn.microsoft.com/ja-jp/windows/win32/api/oaidl/ns-oaidl-variant

## バリアント型変数の型

| 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 |

## バリアントの作成

### バリアント文字列の作成
```cpp
#include <comutil.h>

VARIANT WINAPI CreateVariant()
{
	VARIANT v;
	VariantInit(&v);
	v.vt = VT_BSTR;
	v.bstrVal = SysAllocString(L"Hello World.");
	return v;
}
```

https://learn.microsoft.com/ja-jp/windows/win32/api/oleauto/nf-oleauto-variantinit



# 配列
VBAの配列は、どの型でもSAFEARRAY構造体でやり取りします。

https://learn.microsoft.com/ja-jp/windows/win32/api/oaidl/ns-oaidl-safearray

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


## 配列要素の型
SAFEARRAYはフラグ`fFeatures`に`FADF_HAVEVARTYPE`が指定されている場合、-4バイト位置に要素の型情報が[VARENUM 列挙](https://learn.microsoft.com/ja-jp/windows/win32/api/wtypes/ne-wtypes-varenum, "VARENUM (wtypes.h) - Win32 apps | Microsoft Learn")で記録されます。VBAの配列はすべて`FADF_HAVEVARTYPE`が立ちます。SAFEARRAYに設定されたVARNUMの値は`SafeArrayGetVartype`で取得できます。

https://stackoverflow.com/questions/18784470/where-is-safearray-var-type-stored

| 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 |

## 配列の作成

### 線形配列（一次元配列）の作成例

```cpp
#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;
}

```

### 多次元配列の作成例

```cpp
#include <comutil.h>

SAFEARRAY *WINAPI CreateVectorSafeArray()
{
	// ２行３列の文字列配列を作成、以下の宣言と同じ
	// 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;
}
```