[ Top |Wiki |掲示版 ] powered by


L-penguin

Ruby用拡張ライブラリをCで作る

Rubyは、C(あるいはC++)で書いたコードを動的(あるいは静的)にリンクして利用する事ができる。うまく使えば、Rubyで書くより高速な動作が期待できる。 たとえば、GUIライブラリである「Ruby/GTK」は、描画部分にCで書かれたライブラリを利用する事で、実用的な画面描画速度を確保している。 その、Ruby用拡張ライブラリをCで書く方法である。

拡張ライブラリ作成の流れ

Rubyはオブジェクト指向言語である。オブジェクト指向言語におけるプログラムとは、基本的にクラス定義である(と、少なくとも私は思っている)。そこで、ここではクラス定義に話を絞る。

  1. メソッドの実体となる関数を定義する
  2. Init関数を作る
  3. rb_define_classで、新しいクラスを作る
  4. rb_define_methodで、クラスに1で作成したメソッドを追加する
  5. Makefileを作る
  6. makeする
  7. 使う

その前に、#include <ruby.h>を忘れてはいけない。拡張ライブラリ作成に必要となる関数、マクロ、データ型などは、全てこれをincludeすることで利用可能になる。

#include <ruby.h>

1, メソッドの実体となる関数を用意する

引数と戻り値

実際にメソッドが呼び出された時に実行されるのが、この関数である。Ruby内部で使われるため、引数と戻り値には指定がある。まず戻り値は、VALUE型でなければならない。VALUE型は、rubyのオブジェクトを格納する型である。また、引数も基本的にこのVALUE型で受け取る。

例えば、

d = hoge.gunyo(a,b,c)

というメソッドが実行されたとき、gunyoメソッドの実体となるgunyo関数は、 3つのVALUE型の値a,b,cを受け取り、実行後には、gunyo関数のVALUE型の戻り値が、dに格納される。

例1

先程のgunyoメソッド。

VALUE gunyo(VALUE self, VALUE va, VALUE vb, VALUE vc){
  VALUE r;

  /* なんかの処理 */

  return r;
}

第一引数は、selfとなっている。メソッドの実行主体(つまり自分自身)が、ここに渡される。 値の変換

rubyから引数を受け取っても、VALUE型なのでそのまま処理を行う事はできない。たとえ

hoge.gunyo(1,2,3)

という呼び出しであっても、「VALUE型の数値」はint型の数値とは異なる。そこで、VALUE型の値を、対応するデータ型に変換するマクロが用意されている。

FIX2INT(value)
VALUE型の整数値をintに変換する。
NUM2INT(value)
FIX2INTと同じだが、こちらは与えられたvalueが整数値であるかどうか チェックを行い、違えば例外を発生する。FIX2INTは何もチェックしない。
NUM2DBL(value)
浮動小数点数を、double型に変換する。これもチェックを行う。
STR2CSTR(value)
文字列を、char*型に変換する。これもチェックを行う。

逆にrubyに値を返す時には、VALUE型に変換しなければならない。これもマクロや関数が用意されている。

INT2FIX(i)
intをVALUE型整数値に変換する。
INT2NUM(i)
INT2FIXと同じだが、31bitにおさまらない場合にBignum(多倍長整数)に 変換してくれる。
rb_float_new(f)
double、floatをVALUE型の浮動小数点数に変換する。
rb_str_new2(s)
char*型の文字列を、VALUE型の文字列に変換する。

例2

より具体的なgunyoメソッド。

VALUE gunyo(VALUE self, VALUE va, VALUE vb, VALUE vc){
  VALUE r;
  int a,b,c,d;

  a = NUM2INT(va); b = NUM2INT(vb); c = NUM2INT(vc);
  d = a + b + c;  
  r = INT2FIX(d);

  return r;
}

型チェック

NUM2INT(value)などのマクロは、valueが本当に整数値かどうかチェックしてくれる。しかしこれらを使わない場合、自分で型チェックを行わなければならない。そのためのマクロと関数が用意されている。

TYPE(value)

valueの種類を返すマクロ。返り値は、ruby.hで定義される以下の定数 による。

T_NIL     nil
T_OBJECT  通常のオブジェクト
T_CLASS   クラス
T_MODULE  モジュール
T_FLOAT   浮動小数点数
T_STRING  文字列
T_REGEXP  正規表現
T_ARRAY   配列
T_FIXNUM  Fixnum(31bit長整数)
T_HASH    連想配列
T_STRUCT  (Rubyの)構造体
T_BIGNUM  多倍長整数
T_FILE    入出力
T_TRUE    真
T_FALSE   偽
T_DATA    データ
T_SYMBOL  シンボル

(README.EXT.jaより抜粋)
Check_Type(VALUE value, int type)
valueの種類がtypeでなければ例外を発生する。typeは、上に挙げた定 数で指定する。

2, Init関数を作る

作成した拡張ライブラリを読み込んだ際に、実際にruby側から呼び出されるのはこのInit関数である。なので、具体的な定義は全てここで行う。

具体的にはInit_*****という名前で、*****の部分には作成中のライブラリの名前を指定する(ファイル名と同一である必要はない)。後で述べる方法でmakeすると、*****.soという名前のライブラリになる。

例3

Unyoライブラリ用のInit関数。

void Init_Unyo(void){
  /* 処理 */
}

3, rb_define_classで、新しいクラスを作る

Init関数の中でまず行わなければならないのが、これから定義するクラスを生成することである。rb_define_class関数がこれを行う。

rb_define_class(classname, super)
superを継承し、名前がclassnameであるようなクラスを生成する。

継承するスーパークラスは、普通はObjectクラスで問題無いだろう。その場合、 superにはrb_cObjectを指定する。

例4

Unyoライブラリに、Hogeクラスを作る。

void Init_Unyo(void){
  VALUE rb_cHoge;

  rb_cHoge = rb_define_class("Hoge", rb_cObject);

  /* メソッド定義 */
}

生成されたクラスは、VALUE型の値として返される。これを変数rb_cHogeに格納しておき、以降のメソッド定義等で使う。

4, rb_define_methodで、クラスに1で作成したメソッドを追加する

3で生成したクラスに対してメソッドを定義するには、rb_define_method関数を使う。

rb_define_method(klass, methodname, func, args)
klassに格納されたクラスに、methodnameで指定される名前のメソッド を定義する。

funcには、1で定義したメソッド本体への関数ポインタを渡す。要するに関数名の後ろに()を書かなければ良い(はず)。

argsは、引数の数である。selfはカウント外なので注意。例えば

hoge.gunyo(a,b,c)

なら、argsには3を指定する。

argsが負の値の場合は、引数が配列で渡される。

argsが-1の場合
VALUE型の引数が格納されたCの配列(つまりVALUE *argv)が渡される。
argsが-2の場合
VALUE型の引数が格納されたrubyの配列(つまりVALUE argv)が渡される。

Cの配列で渡された場合は、argv[3]などとすれば3番目の要素にアクセスできるが、rubyの配列の場合はデータ変換が必要である。

例5

UnyoライブラリのHogeクラスに、先程のgunyoメソッドを定義する。

void Init_Unyo(void){
  VALUE rb_cHoge;

  rb_cHoge = rb_define_class("Hoge", rb_cObject);

  rb_define_method(rb_cHoge, "gunyo", gunyo, 3);
}

ここまででできたソース

#include <ruby.h>

VALUE gunyo(VALUE self, VALUE va, VALUE vb, VALUE vc){
  VALUE r;
  int a,b,c,d;

  a = NUM2INT(va); b = NUM2INT(vb); c = NUM2INT(vc);
  d = a + b + c;  
  r = INT2FIX(d);

  return r;
}

void Init_Unyo(void){
  VALUE rb_cHoge;

  rb_cHoge = rb_define_class("Hoge", rb_cObject);

  rb_define_method(rb_cHoge, "gunyo", gunyo, 3);
}

5, Makefileを作る

ソースができた所でコンパイルするのだが、普通にコンパイルしても拡張ライブラリにはならない。rubyには、拡張ライブラリとしてのコンパイルを行う Makefileを、生成するためのrubyプログラムが用意されているので、これを使う。

次の内容で、extconf.rbというrubyスクリプトを書く(ファイル名は違ってもよい)。

require "mkmf"
create_makefile("Unyo")

create_makefileが、requireされたmkmf.rbで定義されている「Makefileを生成する関数」である。引数の"Unyo"には、Init_*****の、*****の部分を指定する。この関数が呼び出されると、Init_*****によって定義されるライブラリを作成するためのMakefileが作られる。

6, makeする

$ make

7, 使う

エラーがなければ、ディレクトリに Unyo.so というファイルができたはずである。これができあがった拡張ライブラリである。

requireして使う。

ソース

require "Unyo.so"

hoge = Hoge.new
d = hoge.gunyo(1,2,3)
print d,"\n"

実行結果

$ ruby hoge.rb
6
$ 

Since 2002, L-penguin.