Ruby Programing for FORTRAN 〜SWIGを使う(7)〜

内容

今までの総まとめとしてDCLをラップしてみます。

.

これまでの解説でFORTRANの関数をラップする上で必要となるテクニックはすべて説明しました。 チュートリアルの一応の締めとしてDCLのラッピングに挑戦したいと思います。

とりあえず今まで出てきたラッピングの手法を整理します。 まずは配列、及び文字列から。

%typemap(ruby,in) int *ARRAY {
  int i, len;

  Check_Type($source, T_ARRAY);
  len = RARRAY($source)->len;
  $target = (int*)malloc(sizeof(int)*len);
  for (i = 0; i < len; i++)
    $target[i] = NUM2INT(rb_Integer(RARRAY($source)->ptr[i]));
}

%typemap(ruby,in) float *ARRAY {
  int i, len;

  Check_Type($source, T_ARRAY);
  len = RARRAY($source)->len;
  $target = (float*)malloc(sizeof(float)*len);
  for (i = 0; i < len; i++)
    $target[i] = (float)RFLOAT(rb_Float(RARRAY($source)->ptr[i]))->value;
}

%typemap(ruby,in) double *ARRAY {
  int i, len;

  Check_Type($source, T_ARRAY);
  len = RARRAY($source)->len;
  $target = (double*)malloc(sizeof(double)*len);
  for (i = 0; i < len; i++)
    $target[i] = (double)RFLOAT(rb_Float(RARRAY($source)->ptr[i]))->value;
}

%typemap(ruby,ignore) int *LENGTH(int tmp) {
  tmp = 0;
  $target = &tmp;
}

%typemap(ruby,in) char *ARRAY {
  int len;

  Check_Type($source, T_STRING);
  len = RSTRING($source)->len;
  $target = (char*)malloc(len + 1);
  strncpy($target, RSTRING(rb_String($sourc)e)->ptr, len);
  $target[len] = '\0';
}

%typemap(ruby,ignore) int LENGTH {
  $target = 0;
}

%typemap(ruby,freearg) int *ARRAY,
                       char *ARRAY,
                       float *ARRAY,
                       double *ARRAY
{
  free($source);
}
   

intと同様にfloat doubleについても同様な定義を作っていきます。 使い方は配列のポインタ引数に*ARRAYを指定し、長さを与える引数部分は*LENGTHを指定します。 ただし、charの配列である文字列については長さに関してはLENGTHを使用します。

次は複素数型について。

%{

struct complex
{
  float r;
  float i;
};

struct d_complex
{
  double r;
  double i;
};

%}

%typemap(ruby,in) struct d_complex *COMPLEX_IN,
                  struct d_complex *DCOMPLEX_IN {
  Check_Type($source, T_ARRAY);
  $target = ($type)malloc(sizeof($basetype));
  $target->r = NUM2DBL(RARRAY($source)->ptr[0]);
  $target->i = NUM2DBL(RARRAY($source)->ptr[1]);
}

%typemap(ruby,ignore) struct d_complex *COMPLEX_OUT,
                      struct d_complex *DCOMPLEX_OUT {
  $target = ($type)malloc(sizeof($basetype));
}

%typemap(ruby,argout) struct d_complex *COMPLEX_OUT,
                      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 *COMPLEX_IN,
                       struct d_complex *DCOMPLEX_IN,
                       struct d_complex *COMPLEX_OUT,
                       struct d_complex *DCOMPLEX_OUT {
  free($source);
}
   

倍精度に加えて、倍精度も用意します。 ところで、上の例でも使っていましたが、この%typemapでは対応させる型を,で列挙することができます。 ただ、列挙することでmallocで確保すべきメモリサイズの指定等をsizeof(d_complex)のようにきめ打ちすることができなくなります。 そんなとき使うのが$typeと$basetypeです。 $typeは%typemapを適用する型そのものを$basetypeは$typeの元となる型、例えば$typeがポインタであるならば*を取り除いたものを示すことになります。 使い方は前回ほぼ同じです。単精度ならCOMPLEX何某を、倍精度ならDCOMPLEX何某を使ってください。

最後にDCLに特化したものかもしれませんが、1 or 0で指定するフラグについて。

%typemap(ruby,in) int *FLAG(int tmp) {
  if ($source == Qnil || $source == Qfalse)
    tmp = 0;
  else if ($source == Qtrue)
    tmp = 1;

  $target = &tmp;
}
   

nilまたはfalseを与えられた場合は0、それ以外の場合は1を渡すようにしています。 本来intのポインタとして取り扱うものですから、int *INPUTをint *FLAGにするだけで使用できます。

なお、文字列を返すものについてはあまり汎用性がない上に、そもそもFORTRANで文字列を返す関数を作ることはあまりないと思うので説明は省きます。

以上のものをまとめたものをextra.iとして保存します。

さて、実際のラップ作業に入るとしましょう。 が、DCLかなり大きいライブラリなので全部ここで例を挙げようなものならばとてつもなく長くなってしまいます。 そこで説明ではA href="http://dennou-k.kugi.kyoto-u.ac.jp/arch/ruby/Ruby/dcl/" accesskey="rd">地球流体電脳倶楽部で作られているRuby/DCLのDemoのhop.rbを動かすことを目標とします。 以下がhop.rbの動作に必要な関数をそろえたインターフェースです。

%cat dcl.i
%module dcl
%include typemaps.i
%include extra.i

void gropn_(int *INPUT);
void grcls_();
void grfrm_();
void sgpwsn_();
void ussttl_(char *ARRAY, char *ARRAY, char *ARRAY, char *ARRAY, int LENGTH, int LENGTH, int LENGTH, int LENGTH);
void usgrph_(int *LENGTH, float *ARRAY, float *ARRAY);
void sgpwsn_();
   

特に意図したわけではないのですが、配列、文字列両方とも存在している好例ですね。

とりあえず、dcl_wrap.cを生成します。が、そのままでは上手く動かないことは明らかですね。 文字列を扱うussttl_、配列を扱うusgraph_があるからです。 少々長いですが、以下にdcl_wrap.cのussttl_、usgraph_を呼び出すラッパ関数を示します。

static VALUE
_wrap_ussttl_(int argc, VALUE *argv, VALUE self) {
    VALUE varg0 ;
    VALUE varg1 ;
    VALUE varg2 ;
    VALUE varg3 ;
    VALUE varg4 ;
    VALUE varg5 ;
    VALUE varg6 ;
    VALUE varg7 ;
    char *arg0 ;
    char *arg1 ;
    char *arg2 ;
    char *arg3 ;
    int arg4 ;
    int arg5 ;
    int arg6 ;
    int arg7 ;

    {
        arg4 = 0;
    }
    {
        arg5 = 0;
    }
    {
        arg6 = 0;
    }
    {
        arg7 = 0;
    }
    rb_scan_args(argc, argv, "40", &varg0, &varg1, &varg2, &varg3);
    {
        int len;

        Check_Type(varg0, T_STRING);
        len = RSTRING(varg0)->len;
        arg0 = (char*)malloc(len + 1);
        strncpy(arg0, RSTRING(rb_String($sourc)e)->ptr, len);
        arg0[len] = '\0';
    }
    {
        int len;

        Check_Type(varg1, T_STRING);
        len = RSTRING(varg1)->len;
        arg1 = (char*)malloc(len + 1);
        strncpy(arg1, RSTRING(rb_String($sourc)e)->ptr, len);
        arg1[len] = '\0';
    }
    {
        int len;

        Check_Type(varg2, T_STRING);
        len = RSTRING(varg2)->len;
        arg2 = (char*)malloc(len + 1);
        strncpy(arg2, RSTRING(rb_String($sourc)e)->ptr, len);
        arg2[len] = '\0';
    }
    {
        int len;

        Check_Type(varg3, T_STRING);
        len = RSTRING(varg3)->len;
        arg3 = (char*)malloc(len + 1);
        strncpy(arg3, RSTRING(rb_String($sourc)e)->ptr, len);
        arg3[len] = '\0';
    }
    ussttl_(arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7);
    {
        free(arg0);
    }
    {
        free(arg1);
    }
    {
        free(arg2);
    }
    {
        free(arg3);
    }
    return Qnil;
}

static VALUE
_wrap_usgrph_(int argc, VALUE *argv, VALUE self) {
    VALUE varg0 ;
    VALUE varg1 ;
    VALUE varg2 ;
    int *arg0 ;
    float *arg1 ;
    float *arg2 ;
    int tmp ;

    {
        tmp = 0;
        arg0 = &tmp;
    }
    rb_scan_args(argc, argv, "20", &varg1, &varg2);
    {
        int i, len;

        Check_Type(varg1, T_ARRAY);
        len = RARRAY(varg1)->len;
        arg1 = (float*)malloc(sizeof(float)*len);
        for (i = 0; i < len; i++)
        arg1[i] = (float)RFLOAT(rb_Float(RARRAY(varg1)->ptr[i]))->value;
    }
    {
        int i, len;

        Check_Type(varg2, T_ARRAY);
        len = RARRAY(varg2)->len;
        arg2 = (float*)malloc(sizeof(float)*len);
        for (i = 0; i < len; i++)
        arg2[i] = (float)RFLOAT(rb_Float(RARRAY(varg2)->ptr[i]))->value;
    }
    usgrph_(arg0,arg1,arg2);
    {
        free(arg1);
    }
    {
        free(arg2);
    }
    return Qnil;
}
   

まず、_wrap_ussttl_についてですが、これは前にこのチュートリアルで示したものと同じ処置をします。 すなわち、rb_scan_argsを先頭に持っていき、長さをあらわすargにそれぞれ対応する文字列の長さを入れていきます。 実際の修正は以下のとおり。

    rb_scan_args(argc, argv, "40", &varg0, &varg1, &varg2, &varg3);
    {
        //arg4 = 0;
        arg4 = RSTRING(varg0)->len;
    }
    {
        //arg5 = 0;
        arg5 = RSTRING(varg1)->len;
    }
    {
        //arg6 = 0;
        arg6 = RSTRING(varg2)->len;
    }
    {
        //arg7 = 0;
        arg7 = RSTRING(varg3)->len;
    }
    //rb_scan_args(argc, argv, "40", &varg0, &varg1, &varg2, &varg3);
   

まあこれはあまり頭を悩ますことはないでしょう。

_wrap_usgraph、これは少々工夫が要ります。 配列は確かに2つあるのですが長さを指定する引数は一つだけです。ちょっと悩みました。 なお、本家(?)のラッピングではこういった配列の長さはRuby側でも渡すことにしていました。 でも省くことが不可能ではないような気がします。以下のように書けば。

    rb_scan_args(argc, argv, "20", &varg1, &varg2);
    {
        if (RARRAY(varg1)->len < RARRAY(varg2)->len)
          tmp = RARRAY(varg1)->len;
        else
          tmp = RARRAY(varg2)->len;
        //tmp = 0;
        arg0 = &tmp;
    }
    //rb_scan_args(argc, argv, "20", &varg1, &varg2);
   

2つの配列のうち、短い方を引数として渡すようにすればいいのではないでしょうか? ただ、元のFORTRANの関数がそもそも何をやっているか理解していないので実は問題ある手法かもしれませんが:)

以上のように配列の長さを渡すという作業は文字列と違って関数が自主的に定義するため、 その関数の構成如何によって若干代わってきます。 その都度考えるという割と頭を使い、単純作業ではいかない部分といえるでしょう。

さて、あとは拡張ライブラリを生成するだけです。 がその前にもういくつか手直しが要ります。

まず、いつもどおりに生成したMakefileの修正をしなければなりません。 DCLライブラリ自体をリンクするように修正するのは当然ですが、 それ以外にもDCLが使用しているXやさまざまなFORTRANのランタイムのライブラリをリンクする必要があります。

#DLDFLAGS = -Wl,-soname,$(.TARGET) -L/usr/local/lib
DLDFLAGS = -Wl,-soname,$(.TARGET) -L/usr/local/lib -L/usr/X11R6/lib

#LIBS = -L. -l$(RUBY_INSTALL_NAME) -lc
LIBS = -L. -l$(RUBY_INSTALL_NAME) -lc -lX11 -lg2c -ldcl
   

あと、dcl_wrap.cのInit_dclで各モジュール関数の名前から_を取り除いておくと色々と便利でしょう。

後はMakeして動作確認するだけです。が、hop.rbは当然そのままでは動きません。以下のように修正してください。

#$:.push "../work"  #  These tricks are needed for
#$:.push "work"     #          pre-install test only.

require "dcl.so"
include Dcl

include Math

NMAX = 400
dt = 2*PI / (NMAX-1)
x, y = [], []

for n in 0..NMAX
  t = dt*n
  x[n] = 1e2*sin(4*t)
  y[n] = 1e-3*cos(5*t)+6
end

iws = (ARGV[0] || (puts ' WORKSTATION ID (I)  ? ;'; sgpwsn; gets)).to_i
gropn iws
grfrm

ussttl('X-TITLE', 'x-unit', 'Y-TITLE', 'y-unit')
usgrph(x, y)

grcls
   

上手く動いたでしょうか?動作しなかった場合、dcl_warp.cで加えた修正に問題があったのかもしれません。 rb_scan_argsを前に持ってくるのを忘れるというのは単純なミスですが、意外と忘れがちなのでしっかりと確認しましょう。 (本当は最初から先頭に持ってこれるとよいのですが)

さて、目標となったhop.rbは動かすことができました。 この後、残りの関数を....とやっていくわけですが重複する部分が多いため 上のものに残りの部分を付け加えたdcl.iとそれから生成したコードを修正したdcl_wrap.cをおいておきます。 DCLライブラリについてはどのように使えばいいのかまるで分かっていないので是非テストをよろしくお願いします。

.

前に戻る [目次] 次に進む