配列を引数に取る関数を拡張ライブラリでラップする方法を説明します。
前回でめでたくINTEGER型を渡すことができるようになりました。 が、FORTRANには当然他にも色々な変数の型があり、当然関数に種々の引数を渡すことができます。 1つ1つ潰していきましょう。今回は配列です。次のような関数をFORTRANで用意しました。
%cat test3.f INTEGER FUNCTION ADD(V) INTEGER I, TOTAL, V(9) TOTAL=0 DO I=1,9 TOTAL=TOTAL+V(I) END DO ADD=TOTAL RETURN END
INTEGERの配列を渡すとそれをすべて足したものを返す関数ですね。 前回と同じようにCで呼んでみましょうか?
%cat test3-c.c #includeint main(void) { int i, sum[9]; for(i = 0; i < 9; i++) sum[i] = i; printf("%d\n", add_(sum)); return(0); } %gcc -o test3 test3-c.c test3.o %./test3 36
Cから見るとこのadd_という関数は引数に配列の先頭ポインタを取ります。(ポインタのポインタではないことに注意!) さて、本番にいきましょうか。
%cat test3.i %module test3 %include typemaps.i %typemap(ruby,in) int INTARRAY[ANY] { int i, len; len = RARRAY($source)->len; if ($dim0 <= len) $target = (int*)malloc(sizeof(int)*len); else $target = (int*)malloc(sizeof(int)*$dim0); for (i = 0; i < len; i++) $target[i] = NUM2INT(rb_Integer(RARRAY(varg0)->ptr[i])); for (; i < $dim0; i++) $target[i] = 0; } %typemap (ruby, freearg) int INTARRAY[ANY] { free($source); } %apply int INTARRAY[ANY] { int argv[9] } int add_(int argv[9]);
なんか急に複雑になってしまいましたがめげずにすすめます(^ ^)
まず、%applyから。これは%typemapで定義した変換コードを適用する関数の引数を指定します。
今回の例でいうと、int INTARRAY[ANY]という引数に対して2つの%typemapsを定義していますがこれを
add_の引数int argv[9]に適用しますという意味になります。(*1)
(*1)前回同様、%applyを利用しないと方法もあります。その場合はラップする関数の列挙の際、前回同様INTARRAY[9]のようにすることになります。
ただ、INPUTのように標準の%typemapと違って新たに付け加えたものは適用をすると明示した方がよいと考え%applyを使用しています。
さて、一番重要な%typemapに入るとしましょう。見れば分かるとおり2つ存在しています。 これらはそれぞれ別の働きをします。まず、前者を見てみましょう。
%typemap(ruby,in) int INTARRAY[ANY] { int i, len; len = RARRAY($source)->len; if ($dim0 <= len) $target = (int*)malloc(sizeof(int)*len); else $target = (int*)malloc(sizeof(int)*$dim0); for (i = 0; i < len; i++) $target[i] = NUM2INT(rb_Integer(RARRAY(varg0)->ptr[i])); for (; i < $dim0; i++) $target[i] = 0; }
%typemapは2つの引数をとります。1つ目は言うまでもないでしょう。どの言語についてのものであるかを指定します。当然rubyです。
が、2つ目はよく分かりませんね。"in"ってなんでしょうか?
実はこれは引数がadd_を呼び出すラッパー関数にとってどんな役割を持つのかを指定するためのものです。
今回の例ではadd_のint配列の引数はRuby側のArrayを変換したものを突っ込む先であるわけですから"in"が該当することになります。
"in"を利用した場合、$sourceがRubyの配列を指すVALUE変数になり、$targetがadd_へ渡すint配列(正確にはint*)を意味するようになります。
よって$sourceからデータを取り出し、$targetにメモリを確保してデータを入れるコードを書くことができます。
%typemap (ruby, freearg) int INTARRAY[ANY] { free($source); }
のこるは後者の%typemapです。第1引数は改めて説明することもないですね。 第2引数についてですが"freearg"は呼び出す関数(ここでいうとadd_)を呼び出した後それを呼び出すために利用した道具を片付けるコードを挿入するために使われます。 挿入される位置はadd_の後です。今回のようにmallocでメモリを確保した場合、解放しないとまずいことになるので利用することになります。 $sourceがadd_の引数に与えられる変数を、$targetがRubyのラッパ関数の引数を指します。 もっともRubyの場合、GCでRubyの値は回収されますから専ら$sourceを利用することになりと思います。
ふう.....ようやく説明が終わりました。さあ、swigでコードを生成しましょう。
static VALUE _wrap_add_(VALUE self, VALUE varg0) { int *arg0 ; int result ; VALUE vresult = Qnil; { int i, len; len = RARRAY(varg0)->len; if (9 <= len) arg0 = (int*)malloc(sizeof(int)*len); else arg0 = (int*)malloc(sizeof(int)*9); for (i = 0; i < len; i++) arg0[i] = NUM2INT(rb_Integer(RARRAY(varg0)->ptr[i])); for (; i < 9; i++) arg0[i] = 0; } result = (int )add_(arg0); vresult = INT2NUM(result); { free(arg0); } return vresult; }
上は生成したソースtest3_wrap.cのadd_のラッパー部分の抜粋です。 %typemapで記述したコードが反映されていますね。 あとはこれを利用して拡張ライブラリをMakeするだけです。(前回と作業内容はまったく同じなので省略します) Makeできたら実行してみましょう。
%ruby -e "require 'test3.so'; p Test3.add_([0,1,2,3,4,5,6,7,8])" 36