Rubyの拡張ライブラリを作ってみよう!
はじめに
Ruby Advent Calendar jp: 2011 : ATNDの17日目の記事です。昨日は@yoppiblogさんのSeleniumの自動テストをCI環境(Jenkins)で快適に実施するでした。
Rubyを使ってて
遅い…ここだけ超遅い…
とか
あのライブラリが使いたい!でもRuby用のライブラリじゃないし…
みたいなこと、ありますよね?
そんなとき、Rubyの拡張ライブラリで解決できるかもしれません。
Rubyの拡張ライブラリは、普通のライブラリと異なりC(とかC++とかその他の言語とか)で作成します。そのため、Rubyで直接書くよりも高速に処理できたり、Cのインタフェースが用意されているライブラリをRubyから呼びだしたりすることができます。すばらしい!
そんなわけで、拡張ライブラリの作り方をざっくり説明したいと思います。
用意するもの
最小限のRuby拡張ライブラリ
Rubyの拡張ライブラリを作成してから使うまでの流れは、
みたいな感じです。
1. Cでコードを書く
foo というライブラリを作ることにしましょう。
まず適当にディレクトリを作って
$ mkdir foo $ cd foo
こんな感じのコードを書きます。
/* foo.c */ #include <stdio.h> /* require 'foo' とやると、Init_foo() が呼ばれる */ void Init_foo() { puts("Init_fooですよー"); }
拡張ライブラリでは、
require 'ライブラリ名'
をやると
Init_(ライブラリ名)
が呼び出されます。普通はここでクラスやメソッドの定義などをやります。
2. Makefileを生成するためにextconf.rbを書く
Makefileを生成するためのスクリプトをRubyで書きます。下記のようなファイルをextconf.rbという名前で作成しましょう。
require 'mkmf' create_makefile('foo')
mkmfをrequireして create_makefile(ライブラリ名) とするだけで、とりあえずMakefileを生成できます。
3. Makefileを生成して make && make install する
extconf.rb を普通に実行すればMakefileが生成されるので、普通に make && make install すればOKです。
$ ruby extconf.rb creating Makefile $ make compiling foo.c linking shared-object foo.so $ make install /usr/bin/install -c -m 0755 foo.so /path/to/ruby-1.9.3-p0/lib/ruby/site_ruby/1.9.1/i686-linux installing default foo libraries
4. 使う
普通にrequireするだけ。他のライブラリと全く同じです。
$ irb ruby-1.9.3-p0 :001 > require 'foo' Init_fooですよー => true ruby-1.9.3-p0 :002 >
クラスとメソッドを定義する
クラス、メソッドを定義するには、それぞれrb_define_class()、rb_define_method()を使います。
/* name: クラス名 super: 親クラス 戻り値: 定義したクラス */ VALUE rb_define_class(const char *name, VALUE super);
/* klass: メソッドを定義するクラス name: メソッド名 func: メソッド実行時に呼び出されるCの関数 argc: メソッドの引数の数 戻り値: なし */ void rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc);
VALUE型は、Rubyのオブジェクトを指すポインタです。引数や戻り値でRubyのオブジェクトをやりとりする際は、このVALUE型を使います。
また、privateメソッドを定義するrb_define_private_methodもあります。initializeメソッドを定義するときにはこっちを使いましょう。
/* klass: メソッドを定義するクラス name: メソッド名 func: メソッド実行時に呼び出されるCの関数 argc: メソッドの引数の数 戻り値: なし */ void rb_define_private_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc);
では、さっきのfoo.cをこんな感じに変えて、FooクラスとFoo#addメソッドを定義してみます。
/* foo.c */ #include <stdio.h> #include <ruby.h> VALUE cFoo; /* Fooクラス */ /* self はメソッドの呼び出し元オブジェクト */ VALUE bar_func(VALUE self) { puts("Foo#barですよー"); return self; } void Init_foo() { /* Fooクラスを定義 */ cFoo = rb_define_class("Foo", rb_cObject); /* Fooクラスに引数0個のbarメソッドを定義 barメソッドが呼ばれると、bar_funcが実行される */ rb_define_method(cFoo, "bar", RUBY_METHOD_FUNC(bar_func), 0); }
こんな感じで実行します。ちゃんと bar_func が呼ばれてますね。
$ ruby extconf.rb && make && make install $ irb ruby-1.9.3-p0 :001 > require 'foo' => true ruby-1.9.3-p0 :002 > foo = Foo.new => #<Foo:0x88f0d60> ruby-1.9.3-p0 :003 > foo.bar Foo#barですよー => #<Foo:0x88f0d60> ruby-1.9.3-p0 :004 >
Rubyの値とCの値を変換する
実際の拡張ライブラリでは、Rubyの値とCの値との変換が必要になります。
よく使うのはこのへんです。
関数名 | 用途 |
---|---|
int NUM2INT(VALUE x) | Rubyの数値をCのint型に変換 |
double NUM2DBL(VALUE x) | Rubyの数値をCのdouble型に変換 |
char* StringValuePtr(VALUE str) | Rubyの文字列をCのchar*に変換 |
VALUE INT2NUM(int x) | Cのint型をRubyの数値に変換 |
VALUE DBL2NUM(double x) | Cのdouble型をRubyの数値に変換 |
VALUE rb_str_new2(const char* ptr) | Cのchar*型をRubyの文字列に変換 |
こんな感じで使います。
#include <stdio.h> #include <ruby.h> VALUE cFoo; VALUE bar_func(VALUE self, VALUE i_val, VALUE d_val, VALUE s_val) { /* Rubyの値をCの値に変換して */ int i = NUM2INT(i_val); double d = NUM2DBL(d_val); char* s = StringValuePtr(s_val); /* なんか処理して */ double x = i + d; printf("[%s : %d + %lf = %lf]\n", s, i, d, x); /* Cの値をRubyの値に変換して返す */ return DBL2NUM(x); } void Init_foo() { cFoo = rb_define_class("Foo", rb_cObject); /* Foo#barは引数を3つ取るように変更 */ rb_define_method(cFoo, "bar", RUBY_METHOD_FUNC(bar_func), 3); }
Foo#barに引数を3つ与えて実行してみましょう。
$ ruby extconf.rb && make && make install $ irb ruby-1.9.3-p0 :001 > require 'foo' => true ruby-1.9.3-p0 :002 > foo = Foo.new => #<Foo:0x9441ea8> ruby-1.9.3-p0 :003 > foo.bar(1, 2.2, 'foobar') [foobar : 1 + 2.200000 = 3.200000] => 3.2
どうやらちゃんと動いてます。やったね!
さらに調べるには
- LoveRubyNet Wiki: RubyExtensionProgrammingGuide
- 今回触れなかったRuby内部のデータ構造や、データをGCから守る方法など、重要なポイントが書かれています。必読です。
- Ruby 1.9.3 リファレンスマニュアル > 関数一覧
- こんなのやるにはどうすればいいの?という場合はこちら。説明が無いものもありますが、その場合はソースコード見て下さい。
- README.EXT.ja
- Rubyのソースコード
- やっぱり一時情報は大事です。
まとめ
Rubyの拡張ライブラリの作り方をざっくり紹介しました。これでボトルネックもCで書かれたライブラリも怖くないはず!
もうすこし書きたいところではありますが、時間もアレなのでこのへんで。
明日は18日目、@sakairyotaさんです。