C言語で、rubyの拡張モジュールを作成する方法について調べてみました。
とりあえず例として
- Sample という拡張モジュールを作成し
- Sample::sample() というメソッドをCで実装
してみました。
このSampleモジュールを使うと、以下のrubyのコードは
require "sample" puts Sample::sample("test")
コンソールに "test" と表示するようになります
拡張モジュールのソースコード
Cで次のようなコードを書きます。
#include <ruby.h> static VALUE rb_sample_sample(VALUE obj, VALUE arg) { Check_Type(arg, T_STRING); return rb_str_new2(RSTRING_PTR(arg)); } void Init_sample() { VALUE mSample = rb_define_module("Sample"); rb_define_module_function(mSample, "sample", rb_sample_sample, 1); }
動作の仕組みを説明すると、まず
- ruby 側で require "sample" とすると、rubyのインタプリタは sample.so を探し出し、動的にリンクする
- sample.so を読み込んだrubyのインタプリタは、 sample.so中にある Init_sample() を実行
という流れで、 上記ソースコードの Init_sample() が実行されます
次に、Init_sampleでは
- "Sample" というモジュールを生成。
- rb_define_module_function() で モジュールに、メソッド"sample"を追加
します
ソースを見るとわかるように、ruby は内部的には、モジュールに、"sample"という文字列と、対応するCの関数のポインタの組を登録することで、メソッドを登録しています。また rb_define_module_function の最後の引数 "1" は、 "sample"メソッドの引数の数です。
"sample"メソッドの引数は1つですが、C言語側のrb_sample_sample()の引数は、二つになります。
static VALUE
rb_sample_sample(VALUE obj, VALUE arg)
ここで、obj はSampleモジュール自身、 arg が "sample"メソッドへの引数、となります。
コンパイル方法
コンパイルに必要となる Makefile は、 ruby 側で自動生成できます
まず、以下のような ruby のコードを用意します。ファイル名は慣例的に extconf.rb とするらしいです
require "mkmf" $CFLAGS += "" $LOCAL_LIBS += " " create_makefile("sample")
これで
$ ruby extconf.rb
を実行すると Makefile が生成されるので、あとは
$ make
するだけです。うまくコンパイルできれば sample.so が生成されます
実行方法
以下のようなコードを test-sample.rb として用意しておけば
require "sample" puts Sample::sample("test")
$ ruby ./test-sample.rb
で、sample.so が勝手に読み込まれて、コンソールに "test"と出力されます。とりあえず、拡張モジュール完成です!
メモリ管理の仕組みについて
ところで、上記拡張モジュールが確保したメモリは、いつ解放されるのでしょうか?普段C/C++を使っている人間としては、メモリリークが気になります。そこでとりあえず、チェックツールである valgrind で解放しわすれたメモリがないか確認してみました
$ valgrind ruby ./test-sample.rb
いろいろ情報が出力されますが、ログの末尾をみると
==12578== LEAK SUMMARY: ==12578== definitely lost: 0 bytes in 0 blocks. ==12578== possibly lost: 0 bytes in 0 blocks. ==12578== still reachable: 339,867 bytes in 7,419 blocks. ==12578== suppressed: 0 bytes in 0 blocks. ==12578== Rerun with --leak-check=full to see details of leaked memory.
ということで、とりあえずメモリリークは無いようです。
GCがちゃんと走っているようですが、実用的な拡張モジュールを作るのであれば、VALUEの中身、GCの仕組みをきちんと理解する必要はありそうです。