VARIANT 型を受け取る際の注意

SPECIAL

COM コンポーネントを作ったときのことです。

VBScirpt と Visual Basic とで VARIANT の渡され方に若干の違いがあったのでまとめてみました。


VARIANT 型

VARIANT 型のデータ型とは、整数や少数、文字列など、用途に応じて多彩な値を代入できるように作られた構造体です。

それゆえ若干プログラムを組むのに工夫が必要になりますけど、状況に応じて値の使い方を切り替えたり、COM コンポーネントでは省略可能な引数として使用できたりと、何かと便利な面をもっています。

 

ただ、初めて COM コンポーネントでまともに VARIANT 型を多用していてはまりました。

Visual Basic から渡される VARIANT の引数と、VBScript から渡される VARIANT の引数とでは、その渡され方に若干の違いがありました。そのため、Visual Basic で動作確認をしていた時はよかったのに、いざ VBScript から使用しようとしたら思い通りに動かない…。

そんなんで、今回は Visual Basic と VBScript との違いに関してまとめて見ました。

 

COM での受け取られ方

Visual C++ で VARIANT 型と LPVARIANT 型の2つの引数を取るメソッドを作成して、そのメソッドが実際にどのような値として引数を受け取るのかを調べてみました。なお、どちらの引数とも、[in, optional] となっています。

VBScript

渡した引数 VARIANT LPVARIANT
省略時 VT_ERROR VT_ERROR
未定義変数 VT_BYREF | VT_VARIANT VT_EMPTY
数値型変数 (変数に数値を代入) VT_BYREF | VT_VARIANT VT_I2
数値型定数 (引数に直接代入) VT_I2 VT_I2
文字型変数 (変数に文字列を代入) VT_BYREF | VT_VARIANT VT_BSTR
文字列定数 (引数に直接代入) VT_BSTR VT_BSTR
日付変数 (変数に日付データを代入) VT_BYREF | VT_VARIANT VT_DATE
日付定数 (引数に直接代入) VT_DATE VT_DATE
Null 変数 (変数に Null を代入) VT_BYREF | VT_VARIANT VT_NULL
Null 定数 (引数に直接代入) VT_NULL VT_NULL

 

VBScript の場合、変数に入れられた値は、どんな内容であれ VARIANT 型の引数へは 「VARIANT 型への参照」 として渡されるようです。LPVARIANT 型に対しては、格納されている値そのものを示してくれています。

また、定数に関しては、VARIANT、LPVARIANT を問わず、どちらとも格納されている値そのものを示してくれるようです。

Visual Basic

渡した引数 VARIANT LPVARIANT
省略時 VT_ERROR VT_ERROR
未定義変数 型宣言するだけでも、その情報を示した
数値型変数 As Integer VT_I2 VT_BYREF | VT_I2
数値型定数 VT_I2 VT_I2
文字型変数 As String VT_BSTR VT_BYREF | VT_BSTR
文字列定数 VT_BSTR VT_BSTR
日付変数 As Date VT_DATE VT_BYREF | VT_DATE
日付定数 VT_DATE VT_DATE
Null 変数 - -
Null 定数 VT_NULL VT_NULL

 

Visual Basic の場合、VARIANT 型へは、代入された値そのものが代入され、また LPVARIANT にも代入された値の参照が代入されるので直感的のように思えます。

ただし、定数に関しては LPVARIANT への代入であっても VT_BYREF がつかない形で代入されてきます。

 

相互のずれを中和する

VBScript と Visual Basic ではこれだけ受け取り方が違うため、単純に文字列をもらうから VT_BSTR を調べればいいという話ではなくなってきます。なので、使用する前に意図している型に変換する必要があります。

以下では、VariantChangeType 関数を用いて強制的に変換する方法と、VT_BYREF を取り除いた形にする方法の2つを書いてみました。

 

VariantChangeType

Visual C++ には、VariantChangeType という関数が用意されていますので、これをつかえば VARIANT 型の値を変更することが出来ます。

// dstVariant: 変換後の値を格納する, srcVariant: 返還前の値

VARIANT dstVariant;

VariantInit(&dstVariant);

 

VariantChangeType(&dstVariant, &srcVariant, 0, VT_BSTR);

 

:

:

 

VariantClear(&dstVariant);

 

大雑把に書くと上のような感じです。

上記の場合、srcVariant に渡された VARIANT 変数を BSTR 型に変換しています。こうすることで変換後の dstVariant では、迷うことなく VT_BSTR の値、つまり dstVariant.bstrVal で文字列を取得することが出来ます。

なお、変換先 dstVariant の初期化は忘れずに行いましょう。そうしないと、変換に失敗する場合があるようです。

 

VT_BYREF を取り去る

VariantChangeType 関数を使うと、VT_I4 さえ VT_BSTR に変換してしまうということもしてくれます。

なので便利なのですが、場合によっては VT_I4 の場合はこれ、VT_BSTR の場合はこれ、といった処理をしたい場合があると思います。その場合はやっぱり、自力で判定しなくてはなくなってきてしまいます。

 

けれど、VARIANT 型の引数を使用するたびにそんなことをやっていると大変なので、変換関数を作るといいと思います。

やりかたとしては、VT_BYREF がくっついているかをしらべて、ついていなければそのまま。ついていたら、VT_BYREF がついていない型へ VariantChangeType で変換します。

ただし、VT_BYREF | VT_VARIANT だった場合、単純に VT_BYREF を取り除いて VT_VARIANT というようには出来ないので、この場合は特別に、 pvarVal の値を取り出します。

 

この辺りを踏まえて作った関数が次のようになります。

 

HRESULT VariantChangeTypeByVal(LPVARIANT dstVal, LPVARIANT srcVal)

{

HRESULT hr;

 

// 変換もとの VARIANT が VT_BYREF を含むか調べる。

if (srcVal->vt & VT_BYREF)

{

// VT から VT_BYREF を取り除く。

unsigned short vt = srcVal->vt & !VT_BYREF;

 

switch (vt)

{

case VT_VARIANT:

// VARIANT 型への変換になってしまう場合は、そのままコピー。

hr = VariantCopy(dstVal, srcVal->pvarVal);

break;

 

default:

// VARIANT 型以外への変換ならば、VariantChangeType() を使用。

hr = VariantChangeType(dstVal, srcVal, 0, vt);

break;

}

}

else

{

// VT_BYREF を含んでいなければ、そのままコピー。

hr = VariantCopy(dstVal, srcVal);

}

 

return hr;

}