Perl,Ruby,PythonでCOMの引数に配列を指定する2014年10月22日 21:25

COMの引数に配列を指定する方法を調べていたところ、日本語の情報があまり多くはないように感じたので、自分自身のメモ代わりにも書いておきます。
私が使っているCOMは、パンローリング株式会社の提供している'Pan Active Market Database'(http://www.panrolling.com/pansoft/amarket/)というものです。
宣伝ではないので、製品の詳しい説明はしません。興味のある方はサイトを覗いてみてください。

例として取り上げるクラス、メソッドは次のとおりです。
この例では、配列に値をセットしてCOMに渡すのではなく、COMの側で配列を初期化して値をセットして戻してきます。
ProgIDは、'ActiveMarket.Names.1'です。
Names(名前)クラス --> 銘柄コードと銘柄名との対応を保持するクラス
AllNamesメソッド --> 種類を指定して、銘柄コードと銘柄名の一覧を取得するメソッド

 形式
Sub AllNames(ByVal Kind As KindFlag, Codes() As String, Names() As String)
Enum KindFlag
AM_KINDFLAG_SPOTS = 1
AM_KINDFLAG_FUTURES = 2
AM_KINDFLAG_ALL = 3
End Enum

 引数
Kind 入力 取得する種類 (AM_KINDFLAG_SPOTS: 現物、AM_KINDFLAG_FUTURES: 先物、AM_KINDFLAG_ALL: すべて)
Codes 出力 銘柄コードの一覧
Names 出力 銘柄名の一覧

 戻り値 ナシ

マニュアルではVB6のコードで説明されていますが、実装がVB6かどうかは分かりません。

Perlの場合
 1 use strict;
 2 use warnings;
 3 use Win32::OLE;
 4 use Win32::OLE::Variant;
 5 use 5.010;
 6 my $nm = Win32::OLE->new('ActiveMarket.Names.1');
 7 my $v1 = Variant(VT_ARRAY|VT_BSTR|VT_BYREF, [1,2], 1);
 8 my $v2 = Variant(VT_ARRAY|VT_BSTR|VT_BYREF, [1,2], 1);
 9 $nm->AllNames(3, $v1, $v2);
10 my @codes = $v1->Dim();
11 my @names = $v2->Dim();
12 my $codes_ref = $codes[0];
13 my ($codes_first_ix, $codes_last_ix) = (${$codes_ref}[0], ${$codes_ref}[1]);
14 my $names_ref = $names[0];
15 my ($names_first_ix, $names_last_ix) = (${$names_ref}[0], ${$names_ref}[1]);
16 say 'codes[', $codes_first_ix, ']=', $v1->Get($codes_first_ix);
17 say 'codes[', $codes_last_ix, ']=', $v1->Get($codes_last_ix);
18 say 'names[', $names_first_ix, ']=', $v2->Get($names_first_ix);
19 say 'names[', $names_last_ix, ']=', $v2->Get($names_last_ix);
 実行結果(Perl v5.20.1 で確認)
codes[1]=501
codes[5323]=9997
names[1]=東京IOM一般大豆
names[5323]=ベルーナ

7,8行目で配列の領域を設定していますが、my $v1 = Variant(VT_ARRAY|VT_BSTR|VT_BYREF, 0); としても問題ないようです。このデータベースは日々更新されるのでデータ件数があらかじめ分からないし、COMの側で初期化するので取り敢えず領域を設定しておく必要はないかもしれません。
10,11行目のDimメソッドで取得する配列には、先頭・最終インデックスが格納されています。実際には、この例では([1,5323],)のリストで、[1,5323]が格納された配列のリファレンスが格納されています。

Rubyの場合
 1 require 'win32ole'
 2 include WIN32OLE::VARIANT
 3 an = WIN32OLE.new('ActiveMarket.Names.1')
 4 codes = WIN32OLE_VARIANT.array([4], VT_ARRAY|VT_BSTR|VT_BYREF)
 5 names = WIN32OLE_VARIANT.array([4], VT_ARRAY|VT_BSTR|VT_BYREF)
 6 an.AllNames(3, codes, names)
 7 codes_ruby = codes.value;
 8 names_ruby = names.value;
 9 codes_first_ix = 1
10 codes_last_ix = codes_ruby.size
11 names_first_ix = 1
12 names_last_ix = names_ruby.size
13 puts "codes[#{codes_first_ix}]=#{codes[codes_first_ix]}"
14 puts "codes[#{codes_last_ix}]=#{codes[codes_last_ix]}"
15 puts "names[#{names_first_ix}]=#{names[names_first_ix]}"
16 puts "names[#{names_last_ix}]=#{names[names_last_ix]}"
17 puts "codes_ruby[#{codes_first_ix - 1}]=#{codes_ruby[codes_first_ix - 1]}"
18 puts "codes_ruby[#{codes_last_ix - 1}]=#{codes_ruby[codes_last_ix - 1]}"
19 puts "names_ruby[#{names_first_ix - 1}]=#{names_ruby[names_first_ix - 1]}"
20 puts "names_ruby[#{names_last_ix - 1}]=#{names_ruby[names_last_ix - 1]}"
 実行結果(Ruby 2.1.3p242 で確認)
codes[1]=501
codes[5323]=9997
names[1]=東京IOM一般大豆
names[5323]=ベルーナ
codes_ruby[0]=501
codes_ruby[5322]=9997
names_ruby[0]=東京IOM一般大豆
names_ruby[5322]=ベルーナ

4,5行目で取り敢えずの領域を指定しておかないと、"in `array': memory allocation error(SafeArrayCreate) (RuntimeError)"になります。
戻ってきたSafeArrayの先頭と最終のインデックスを調べる方法はないようですので、7,8行目でRubyオブジェクトにしてからそのサイズを調べています。
実行結果を見ると、Rubyオブジェクトは先頭が0から始まるRubyの配列に変わっていることが分かります。

Pythonの場合
1 import win32com.client
2 nm = win32com.client.Dispatch("ActiveMarket.Names.1")
3 codes_names = nm.AllNames(3)
4 codes_last_ix = len(codes_names[0]) - 1
5 names_last_ix = len(codes_names[1]) - 1
6 print('codes_names[0][0]=', codes_names[0][0], sep='')
7 print('codes_names[1][0]=', codes_names[1][0], sep='')
8 print('codes_names[0][', codes_last_ix, ']=', codes_names[0][codes_last_ix], sep='')
9 print('codes_names[1][', names_last_ix, ']=', codes_names[1][names_last_ix], sep='')
 実行結果(Python 3.4.2,pywin32-Build218 で確認)
codes_names[0][0]=501
codes_names[1][0]=東京IOM一般大豆
codes_names[0][5322]=9997
codes_names[1][5322]=ベルーナ

PerlのWIN32::OLEとRubyのWin32oleに比べると、Pythonのwin32com.clientは趣が違います。
ドキュメントを読むと、PythonとCOMをシームレスに接続するというコンセプトで設計されているそうですが、私の感覚では確かに単純で無駄がないという感じがします。受け取るためだけの引数なら戻り値にしてしまうという発想は、自由で面白いと思いました。