空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その3(文字列の受け渡し)

その1その2と書いたものの
1年以上放置して、いまさらの第3段。

今回は、文字列の受け渡しです。

初めに

初回に書いた設定に要修正箇所があります。
「プロジェクトの設定」の
3.「構成プロパティ」-「全般」-「プロジェクトの既定値」-「文字セット」を
"Unicode 文字セットを使用する"に変更して下さい。

使用する文字範囲が、Shift-JIS の範囲で限定されるような場合には、
マルチ バイト文字セットを使用する
でも良いのですが、Unicode 文字や、JIS 第三水準、第四水準の文字が含まれる可能性が排除できない場合には、
Unicode 文字セットを使用する
としておいた方が、後でトラブルになりにくいと思います。

文字列を受け渡しするためのデータ型

VBAでは、文字列を扱うデータ型は、String です。
これまで、ネットで見つかるコードを見ると、DLL側は、

  • char*
  • const char*
  • wchar_t*
  • BSTR
  • BSTR*

といった型での受け渡しをしているものが大半を締めていたようです。


しかし、今回の記事では、これらの型を使いません。
使うのは、VARIANT です。

理由は、以下の通りです。

VBAString は、
ByVal 渡しの場合はバイト文字列 BSTR 構造体へのポインターとして渡されます。
ByRef 渡しの場合はポインターへのポインターとして渡されます。


文字列を格納している VBAVariant は、
ByVal 渡しの場合は Unicode ワイド文字文字列 BSTR 構造体へのポインターとして渡されます。
ByRef 渡しの場合はポインターへのポインターとして渡されます。

https://docs.microsoft.com/ja-jp/office/client-developer/excel/how-to-access-dlls-in-excel
VARIANT型

VARIANT型は、構造体(VBAでは、Typeステートメントを使って定義するユーザー定義の型と思ってもらえば、とりあえずはOK。厳密に言えば違うんですけど。)で、以下の様になっています。

typedef struct tagVARIANT {
  union {
    struct {
      VARTYPE vt;
      WORD    wReserved1;
      WORD    wReserved2;
      WORD    wReserved3;
      union {
        LONGLONG     llVal;
        LONG         lVal;
        BYTE         bVal;
        SHORT        iVal;
        FLOAT        fltVal;
        DOUBLE       dblVal;
        VARIANT_BOOL boolVal;
        SCODE        scode;
        VARIANT_BOOL __OBSOLETE__VARIANT_BOOL;
        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;
        SCODE        *pscode;
        CY           *pcyVal;
        VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL;
        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 {
          PVOID       pvRecord;
          IRecordInfo *pRecInfo;
        } __VARIANT_NAME_4;
      } __VARIANT_NAME_3;
    } __VARIANT_NAME_2;
    DECIMAL decVal;
  } __VARIANT_NAME_1;
} VARIANT;

結構たくさんの要素があるのですが、この中で、今回大切なのは、以下の3つです。

データ型 変数 備考
VARTYPE vt VARIANTの変数に格納されているデータの型等の情報
BSTR bstrVal 文字列データ
BSTR* pbstrVal 文字列データを指すポインタ

気が付いた方もいるかもしれませんが、
VARIANTの中に格納される文字列の型は、BSTR型です。

vt
vtに格納される値は、VARENUMの中のいずれかが使用されます。
ただし、一部は単独で使用されず、他の値とORを取るフラグとして使用されるものもあります。

VARIANTの変数に、非配列の文字列が格納されている場合、vt は、下表のいずれかの値となります。

定義 備考
VT_BSTR0x0008文字列
VT_BSTR | VT_BYREF0x4008文字列(参照)
数値、オブジェクト等文字列以外のデータが格納されている場合や初期化されていないような場合には、上記とは異なる値が設定されています。

bstrVal
vtに、VT_BSTRが設定されている時、bstrValには、文字列が格納されています。
pbstrVal
vtに、VT_BSTR | VT_BYREF が設定されている時、pbstrValには、文字列へのポインタが格納されています。


以下の2つの違いについては、後述します。

  • VT_BSTR
  • VT_BSTR | VT_BYREF


docs.microsoft.com
docs.microsoft.com
docs.microsoft.com

関数

DLL側でのVARIANT内部形式の判断

VARIANTとBSTRの変換を行う際に、気をつけなければいけないのが、vt の値です。
文字列が格納されている場合の vt の値は、以下の2つがある事を書きました。
これらは、DLLの関数に渡すVBAの変数の型に依存します。

vtVBAの変数の型呼出例備考
VT_BSTR
[ 0x0008 ]
内部処理形式が Stringの Variant の場合Dim v As Variant
v = ""
Call DllFunc(v)
vがemptyの状態で渡すと、VT_EMPTYとなる
VT_BSTR | VT_BYREF
[ 0x4008 ]
String の場合Dim s As String
Call DllFunc(s)
 
VT_BYREF フラグの状態によって以下の処理方法を変える必要があるため、vtの値の確認は重要です。

  1. VBAからDLLへパラメータとして文字列を受け取る場合
  2. DLLからVBAへパラメータに文字列を設定して返す場合
VARIANTの文字列の変換

VARIANT→BSTR

std::wstring	convVstr2Wstr(const VARIANT v)
{
	std::wstring ws;

	if (v.vt == VT_BSTR)
	{
		ws = std::wstring(v.bstrVal, SysStringLen(v.bstrVal));
	}
	else if (v.vt == (VT_BSTR | VT_BYREF))
	{
		ws = std::wstring(*v.pbstrVal, SysStringLen(*v.pbstrVal));
	}

	return ws;
}

wstring→VARIANT
VARIANT変数に文字列を設定する場合、以下の手順で行います。

  1. vt に VT_BSTR または、VT_BSTR | VT_BYREF をセットする。
  2. bstrVal または、pbstrVal に文字列をセットする

VARIANT変数 を vString とする。
vString.vt == VT_BSTR の場合

	std::wstring ws(L"設定する文字列");

	vString.vt = VT_BSTR ;
	vString.bstrVal = SysAllocString(ws.c_str());

	//以下の方法では、正しく文字列が返らない
	//BSTR bs = SysAllocString(ws.c_str());
	//vString.bstrVal = bs;
	//SysFreeString(bs);

vt == VT_BSTR | VT_BYREF の場合

	std::wstring ws(L"設定する文字列");

	vString.vt = VT_BSTR | VT_BYREF;

	BSTR bs = SysAllocString(ws.c_str());
	SysReAllocString(vString.pbstrVal, bs);
	SysFreeString(bs);
VBAからDLLへ文字列を渡す

VBAからDLLへ文字を渡すだけ(一方通行)の場合、

呼出元/先引数指定備考
VBAByVal vString As Variant 
DLLVARIANT vString 

DLLからVBAへ文字列を返す(パラメータ)

呼出元/先引数指定備考
VBAByRef vString As Variant 
DLLVARIANT* pvString*を忘れないこと

DLLからVBAへ文字列を返す(復帰値)

呼出元/先宣言備考
VBADeclare Function FuncName Lib LIB_PATH () As Variant 
DLL__declspec(dllexport) VARIANT WINAPI FuncName 

コード

DLL

AccessibleFromVBA.h
AccessibleFromVBA.cpp
AccessibleFromVBA.def
stdafx.h

VBA

実行サンプル

SetString実行時
f:id:Z1000S:20191010204255j:plain

GetStringByParam、GetStringByRetVal実行結果
f:id:Z1000S:20191009163339j:plain

最後に

文字列はVARIANTで受け渡しですよ!!!



次は配列当たりでしょうか?
では、また1年半後にwww


z1000s.hatenablog.com
z1000s.hatenablog.com






プリコンパイル済みヘッダ使わなきゃよかった・・・