C言語で、ruby用拡張モジュールを作成する方法

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);
}

動作の仕組みを説明すると、まず

という流れで、 上記ソースコードの 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の仕組みをきちんと理解する必要はありそうです。