複素数型を引数にとるFORTRANの関数を拡張ライブラリでラップする方法を説明します。
前回まででINTEGERのような単純型、単純型の配列を利用した関数をラップする方法について説明してきました。 が、FORTRANにはCから見ると先の単純型とは違った処理が要求される型があります。複素数型と文字列型です。 今回は文字列と比較的するとまだ扱いやすい複素数型について説明することにします。
%cat test4.f DOUBLE COMPLEX FUNCTION ADD(A,B) DOUBLE COMPLEX A,B ADD=A+B RETURN END
これが今回のテスト用の関数です。内容は説明するまでもないでしょう。
ちょっと足し算ばっかりで飽きがきてるかなあ?という気はしてるんですがね....
(でも複雑なものを書く知識はないんですよねえ.....引き算ぐらいは可能ですが(^_^))
なんて雑談はこれくらいにして.....Cから呼び出してみます。
%cat test4-c.c #includeint main(void) { struct complex { double r; double i; }; struct complex a,b,c; a.r = 1.0; a.i = 0.0; b.r = 0.0; b.i = 1.0; add_(&c, &a, &b); printf("%f\n%f\n", c.r, c.i); return(0); } % %f77 -c test4.f %gcc -o test4 test4-c.c test4.o %./test4 1.000000 1.000000
今までとは大きく変わってますねえ。なんか構造体の定義もしてるし....
とずいぶんと勝手が違うことは分かっていただけたでしょうか?
Cには複素数を扱う型が存在しないためわざわざ構造体で定義しなおす必要があります。
またFORTRANでは複素数型を返り値とする関数として定義していますがCからみると返り値を第一引数で受け取るように見えます。
この関数をラップする場合、この2点を考慮に入れる必要があります。
また、Cに複素数型がないようにRubyでも標準では複素数クラスというものはありません。
Rubyで複素数をどのように扱うかを決める必要があります。
今回はとりあえず深く考えず、Floatの配列として扱うことにしました。[0]を実数、[1]を虚数とみなします。
注意すべきことはこのくらいでしょう。いつものようにtest4.iを作ることになりますが、ちょっと長いので分けて解説します。
%cat test4.i %module test4 %{ struct d_complex { double r; double i; }; %}
構造体d_complexを定義している部分を見てください。%{ %}で囲まれています。 この部分は生成されるコードtest4_wrap.cにそのままコピーされます。
%typemap(ruby,in) struct d_complex *DCOMPLEX_IN { Check_Type($source, T_ARRAY); $target = (struct d_complex*)malloc(sizeof(struct d_complex)); $target->r = NUM2DBL(RARRAY($source)->ptr[0]); $target->i = NUM2DBL(RARRAY($source)->ptr[1]); }
次の%typemap(ruby,in)ですが、これは前回の例に似ていますから問題ないでしょう。 $targetはd_complexのポインタを示すのでd_complexのサイズ分メモリを確保して 配列からFloatのデータを取り出し、Cのdoubleに変換した後、rとiにそれぞれ代入しています。 Check_TypeはRubyから受け取る引数がArrayであることを保証するためのものです。 これを怠っているとユーザーが配列以外のものを渡してきた場合、コアダンプしてしまいます。
%typemap(ruby,ignore) struct d_complex *DCOMPLEX_OUT { $target = (struct d_complex*)malloc(sizeof(struct d_complex)); } %typemap(ruby,argout) struct d_complex *DCOMPLEX_OUT { $target = rb_ary_new(); rb_ary_push($target, rb_float_new($source->r)); rb_ary_push($target, rb_float_new($source->i)); }
ここで新しい概念としてtypemapの"ignore"と"argout"が登場してきます。
まず"ignore"についてですが、これは適用された引数をラッパー関数から見えないようにするという働きをします。
今回のようにラッパー関数から何かを渡すことがないという場合に利用します。
例では"ignore"で適用される引数を無視するよう指定していますが、同時にadd_の第1引数に渡すことになる$targetのメモリ確保もしています。
これはstruct d_complexのポインタですが、add_においてはその領域は呼び出し側で確保しないといけないものとなっているからです。
あと、"argout"についてですが、これは引数を返り値として扱う場合に利用します。
$sourceが呼び出される関数の返り値となる引数、$targetがRuby側の返り値を示します。
ですから例においては$targetに配列を割り当て、その要素に$sourceから得ることのできる実数と虚数のFloatを入れています。
%typemap(ruby,freearg) struct d_complex *DCOMPLEX_IN { free($source); } %typemap(ruby,freearg) struct d_complex *DCOMPLEX_OUT { free($source); }
前回同様add_に与えるために確保したd_commplex *の領域を解放します。
%apply struct d_complex *DCOMPLEX_IN { struct d_complex *in1, struct d_complex *in2 } %apply struct d_complex *DCOMPLEX_OUT { struct d_complex *out } void add_(struct d_complex *out, struct d_complex *in1, struct d_complex *in2);
最後に適用する引数を決定します。 DCOMPLEX_OUTを返り値を得るための引数用に、DCOMPLEX_INを本来の引数用に割り当てたので 前者を第1引数に、第2、第3引数を後者に適用しています。ここははもちろん
void add_(struct d_complex *DCOMPLEX_OUT, struct d_complex *DCOMPLEX_IN, struct d_complex *DCOMPLEX_IN);
のように書くことも可能です。以下完成版。
%module test4 %{ struct d_complex { double r; double i; }; %} %typemap(ruby,in) struct d_complex *DCOMPLEX_IN { Check_Type($source, T_ARRAY); $target = (struct d_complex*)malloc(sizeof(struct d_complex)); $target->r = NUM2DBL(RARRAY($source)->ptr[0]); $target->i = NUM2DBL(RARRAY($source)->ptr[1]); } %typemap(ruby,ignore) struct d_complex *DCOMPLEX_OUT { $target = (struct d_complex*)malloc(sizeof(struct d_complex)); } %typemap(ruby,argout) struct d_complex *DCOMPLEX_OUT { $target = rb_ary_new(); rb_ary_push($target, rb_float_new($source->r)); rb_ary_push($target, rb_float_new($source->i)); } %typemap(ruby,freearg) struct d_complex *DCOMPLEX_IN { free($source); } %typemap(ruby,freearg) struct d_complex *DCOMPLEX_OUT { free($source); } %apply struct d_complex *DCOMPLEX_IN { struct d_complex *in1, struct d_complex *in2 } %apply struct d_complex *DCOMPLEX_OUT { struct d_complex *out } void add_(struct d_complex *out, struct d_complex *in1, struct d_complex *in2);
最後にいつものように拡張ライブラリをつくります。テスト結果だけ以下に示します。
%ruby -e "require 'test4.so'; p Test4.add_([1.0,1.0], [1.0,1.0])" [2.0, 2.0]