COMで配列の受け渡しがどのよう行なわれているか調べたメモ

ATLでCOMサーバ作ってみて、配列の受け渡しが難しかったので調べた。
テスト用に渡されたパラメータの型に関する情報を文字列で返す簡単なCOMサーバを作って試してみた。
テスト用のCOMサーバのソースは後述する。

※なお、Method() と書くべきところを Mthod() となってしまってるのは単なるTypoで、あとで気づいたけど直すのめんどうだったのでそのままにしてる。

VBScriptの場合

テストコード

function format_table(title, result)
  format_table = join(Array("", title, result, ""), "|")
end function

set v = CreateObject("VariantTest.Tester")

WScript.echo format_table("hoge", v.Mthod("hoge"))
WScript.echo format_table("Array(""xxx"")", v.Mthod(Array("hoge", "fuga")))
WScript.echo format_table("Array(123)", v.Mthod(Array(123, 456)))

dim arr2(1)
arr2(0) = "hoge"
arr2(1) = "fuga"
WScript.echo format_table("Dim arr()", v.Mthod(arr2))

dim arr3(1)
arr3(0) = 123
arr3(1) = 456
WScript.echo format_table("Dim arr() - 2", v.Mthod(arr3))

arr4 = Array("hoge", "moge")
WScript.echo format_table("arr = Array()", v.Mthod(arr4))

WScript.echo format_table("(Array())", v.Mthod((Array("hoge", "moge"))))

set v = nothing

結果

"hoge" [BSTR]
Array("xxx") [ARRAY][VARIANT]
Array(123) [ARRAY][VARIANT]
Dim arr() [BYREF][VARIANT]
Dim arr() - 2 [BYREF][VARIANT]
arr = Array() [BYREF][VARIANT]
(Array()) [ARRAY][VARIANT]

上記結果から、VBScriptからCOMサーバへSAFEARRAYを渡すためにはArray()で配列を生成するか、Array()への参照をもつ変数をカッコで括って (arr) のようにして、渡す必要がある。

実験してみて、以外だったのは、 Dim で宣言した配列は、そのままではARRAYとして認識されないこと
また、Array()で生成した配列を変数に格納したとたんそれもARRAYとして認識されず、VARIANTの参照となってしまうこと
その場合の対処として、()で括って参照先の値を評価することでARRAYとして認識させることができること

JScriptの場合

テストコード

function format_table(title, result) {
  return ["", title, result, ""].join("|");
}

v = new ActiveXObject('VariantTest.Tester');

WScript.echo("'hoge'",    v.Mthod('hoge'));
WScript.echo("['xx']",    v.Mthod(["hoge", "fuga"]));
WScript.echo("Array('xxx')",v.Mthod(Array("hoge", "fuga")));

sa = new Array();
sa[0] = "hoge";
sa[1] = "fuga";
WScript.echo("new Array()", v.Mthod(sa));

vbs = new ActiveXObject("ScriptControl");
vbs.language = "VBScript"
WScript.echo("ScriptControl", v.Mthod(vbs.eval('Array("hoge", "moge")')));

dic = new ActiveXObject("Scripting.Dictionary");
dic.add(0, "hoge");
dic.add(1, "fuga");
WScript.echo("Dictionary", v.Mthod(dic.Items()));

結果

'hoge' [BSTR]
['xx'] [DISPATCH]
Array('xxx') [DISPATCH]
new Array() [DISPATCH]
ScriptControl [ARRAY][VARIANT]
Dictionary [ARRAY][VARIANT]

上記の結果から、JScriptからCOMサーバに対してSAFEARRAYを渡すには、Scripting.Dictionary オブジェクトのItems()メソッドからSAFEARRAYを得る方法がある。(コメントでおしえてもらいました)

VB6の場合

テストコード

Sub Main()
    Dim v As New VariantTestLib.Tester
    
    Debug.Print format_table("""hoge""", v.Mthod("hoge"))
    Debug.Print format_table("array()", v.Mthod(Array("abc", "def")))
    Debug.Print format_table("array()", v.Mthod(Array(123, 456)))

    Dim arr2(1) As String
    arr2(0) = "hoge"
    arr2(1) = "fuga"
    Debug.Print format_table("arr2()", v.Mthod(arr2))
    
End Sub

Function format_table(title, result)
    format_table = Join(Array("", title, result, ""), "|")
End Function

結果

"hoge" [BSTR]
array() [ARRAY][VARIANT]
array() [ARRAY][VARIANT]
arr2() [ARRAY][BSTR]

COMがVB用作られただけあって、いちばんわかり易い結果になっている。

テスト用COMサーバのソースコード

参考までに上記のテストで使用したCOMサーバのソースコードを掲載しておく

// Tester.cpp : CTester の実装

#include "stdafx.h"
#include "Tester.h"
#include <iostream>

// CTester


STDMETHODIMP CTester::Mthod(VARIANT param, BSTR* ret)
{
	CComBSTR result;
	
	long vt = param.vt;

	if (vt & VT_ARRAY)	{
		result.Append("[ARRAY]");
		vt ^= VT_ARRAY;
	}
	if (vt & VT_BYREF) {
		result.Append("[BYREF]");
		vt ^= VT_BYREF;
	}

	if (vt == VT_BSTR)		result.Append("[BSTR]");
	else if (vt == VT_VARIANT)	result.Append("[VARIANT]");
	else if (vt == VT_UI1)		result.Append("[UI1]");
	else if (vt == VT_UI2)		result.Append("[UI2]");
	else if (vt == VT_INT)		result.Append("[INT]");
	else if (vt == VT_I4)		result.Append("[LONG]");
	else if (vt == VT_R4)		result.Append("[FLOAT]");
	else if (vt == VT_R8)		result.Append("[DOUBLE]");
	else if (vt == VT_ERROR)		result.Append("[ERROR]");
	else if (vt == VT_UNKNOWN)	result.Append("[UNKNOWN]");
	else if (vt == VT_DISPATCH)	result.Append("[DISPATCH]");
	
	result.CopyTo(ret);

	return S_OK;
}