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