◆ 目的
Cで書いたコードを動的(あるいは静的)にリンクさせ、Rubyを高速に動作させる
◆ Cで置き換えるRubyのコード
下記のRubyコードをCで置き換える
コード自体に深い意味はない、ただのサンプル用である
class Counter
def initialize
@count = 0
end
attr_reader :count
def increment
@count += 1
end
end
◆ 利用例
test.rb
# Cで書き直し、ビルドした共有ライブラリを取り込む
require 'counter'
# あとは普通のRubyコードど同じ要領で利用できる
cnt = Counter.new
puts cnt.count
cnt.increment
puts cnt.count
$ ruby test.rb
0
1
◆ Cによるクラス定義
Ruby拡張ライブラリ(共有ライブラリ.so)を作成するためのCコードを記述していく
counter.c
#include <ruby.h>
// 関数の宣言(定義は後述)
static VALUE counter_alloc(VALUE klass);
static VALUE counter_initialize(VALUE self);
static VALUE counter_increment(VALUE self);
static VALUE counter_count(VALUE self);
/* Ruby拡張ライブラリ(共有ライブラリ.so)がrequireされた時、
Init_XXX()関数がまず呼び出される */
void
Init_counter(void)
{
// VALUEは各種オブジェクト構造体へのポインタにキャストして使われる
// 今回はCレベルで定義されたクラスを表すRData構造体になる
VALUE Counter;
// クラスの定義
Counter = rb_define_class("Counter", rb_cObject);
/* rb_cObjectはクラスオブジェクト(Object)のVALUEを格納しているグローバル変数である
今回作成するクラスオブジェクトのスーパークラスがObjectクラスであり、
つまりここではrb_cObjectになる
Rubyコードで書けば、"class Counter < rb_cObject" である */
// フィールドのクラスへの関連付け
// Counterクラスへcountフィールドを追加する
// 実際に割り当てるフィールドは、counter_alloc関数内で定義する
rb_define_alloc_func(Counter, counter_alloc);
// メソッドのクラスへの関連付け
// Counterクラスへinitializeメソッド(実体はcounter_initialize関数)を追加する
// 引数の数は0
// プライベートなメソッドなので、本APIを使っている
rb_define_private_method(Counter, "initialize", counter_initialize, 0);
// Counterクラスへcountメソッド(実体はcounter_count関数)を追加する
// 引数の数は0
// Rubyコード上のattr_readerを使ったアクセサメソッドである
rb_define_method(Counter, "count", counter_count, 0);
// Counterクラスへincrementメソッド(実体はcounter_increment関数)を追加する
// 引数の数は0
rb_define_method(Counter, "increment", counter_increment, 0);
}
// フィールドの定義
struct counter {
int count;
};
// フィールドをクラスへセット
static VALUE
counter_alloc(VALUE klass)
{
// フィールド用にメモリを割り当てる
struct counter *ptr = ALLOC(struct counter);
/* ALLOC()はmalloc()に失敗するとGCを行ってからやりなおし、
それでも失敗したら例外を投げる
従って返り値をチェックする必要がないので便利である */
// klassへフィールドをセットする
return Data_Wrap_Struct(klass, 0, -1, ptr);
// Data_Wrap_Struct() のプロトタイプについては下記"◆ GCについて補足"欄を参照のこと
}
// initializeメソッドをクラスへセット
static VALUE
counter_initialize(VALUE self)
{
struct counter *ptr;
// self("struct RData*"であるVALUE)から"struct counter*"を取り出す
Data_Get_Struct(self, struct counter, ptr);
ptr->count = 0;
// initialize の返り値には意味がないので、適当にnilを返しておく
return Qnil;
}
// counterメソッドをクラスへセット
static VALUE
counter_count(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
return INT2FIX(ptr->count);
/* Rubyではオブジェクトの実体を構造体で表現し、扱うときは常にポインタ経由で扱う
構造体はクラスごとに違う型を使うが、ポインタはどのクラスの構造体でも常にVALUE型である*/
}
// counter_incrementメソッドをクラスへセット
static VALUE
counter_increment(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
ptr->count++;
return self;
}
◆ GCについて補足
Data_Wrap_Struct() のプロトタイプは下記である
VALUE Data_Wrap_Struct(VALUE klass,
void (*dmark)(void*),
void (*dfree)(void*),
void *data);
klass: このオブジェクトのクラス
dmark: struct RData の dmark メンバに格納する関数ポインタ
dfree: struct RData の dfree メンバに格納する関数ポインタ
data : ユーザの用意した構造体へのポインタ
struct counter{} 内でcounterの型をint型にしていた
Rubyの整数を使うためにVALUE型に変えれば、intの範囲はRuby任せにできる
ここで注意点
Rubyでは小さな整数はFixnumクラスであり、
大きくなるとBignumクラスになるのは知っているかと思う
Bignum には構造体があるので、GCマークして保護しなくてはいけない
(変更前)
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
return Data_Wrap_Struct(klass, 0, -1, ptr);
}
(変更後)
static void
counter_mark(struct counter *ptr)
{
rb_gc_mark(ptr->count);
}
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
return Data_Wrap_Struct(klass, counter_mark, -1, ptr);
}
◆ 共有ライブラリの作成
まずはMakefileを作成する
extconf.rb
# Makefile を作成するライブラリが必要である
require 'mkmf'
# 'counter'の部分はInit_xxxx のxxxxを使う
create_makefile 'counter'
実行するとMakeファイルができている
$ ruby extconf.rb
makeして共有ライブラリを作る
$ make
これで、counter.soができあがる
◆ 実行
先の利用例で記した方法で実行可能である