2008-01-09 15:51:08 +0900 (276d); rev 19
これは草稿です。
いずれ正式なところに移す予定。
本稿は以下のような読者を対象にしています。
2 に関して。 「ポインタって何?」レベルの人は不可です。 構造体とポインタをちゃんと使ってプログラミングのできる人を対象とします。
3 に関して。 以下のプログラムの結果がどうなるか、 考えるだけでわかるようにしておいてください。
class C
p self # 何が表示される?
def C.m
puts '1'
p self
end
def m
puts '2'
p self
end
end
C.m # 何が表示される?
C.new.m # 何が表示される?
このマニュアルは大きく分けると二つに分かれます。
前者はさらに五つに分かれます。
拡張ライブラリの作成方法を知るには、まず Ruby オブジェクトが C 言語でいかに表現されているかを身に付けなければなりません。 そして次にモジュールとメソッドの定義を覚え、 続いてインスタンスの作成方法を覚えていきましょう。
Ruby オブジェクトの実体は構造体 struct RXXXX で、VALUE 型が その構造体を指すポインタです。具体的には以下の構造体があります。
struct RObject Objectと、Ruby レベルのクラス全部とその下位クラス struct RClass Module, Class とその下位クラス struct RFloat Float とその下位クラス struct RString String とその下位クラス struct RArray Array とその下位クラス struct RRegexp Regexp とその下位クラス struct RHash Hash とその下位クラス struct RFile IO とその下位クラス struct RStruct Struct とその下位クラス struct RBignum Bignum とその下位クラス struct RData 上記以外で、C レベルで定義したクラス全部とその下位クラス
VALUE と構造体の関係を図にすると次のようになっています。
+- VALUE -+ +---- struct RString --+
| -----------------> | VALUE klass; |
+---------+ | unsigned long flags; |
| char *ptr; |
| long len; |
| union aux { .... } |
+----------------------+
ごく僅かな例外として、Fixnum・Symbol などは構造体を持たず、 VALUE (ポインタ) に直接埋め込まれています。 具体的に列挙すると以下のクラスのインスタンスは構造体を持ちません。
VALUE 型の変数 obj があるとき、RARRAY(obj) や RSTRING(obj) で VALUE を struct RXXXX * にキャストできます。
例 VALUE obj; RSTRING(obj)->ptr; /* 文字列の実体のchar*にアクセス */
ただし RXXXX() は静的なキャストであり、VALUE が本当にその構造体 を指しているかチェックしません。例えば RSTRING(obj) としたとき、 obj が本当に struct RString* であるかどうかは確認していません。 ですから、obj が struct RString を指していることはプログラマが 実行時にチェックする必要があります。これが「構造体型のチェック」です。 構造体型をチェックするにはマクロ ?Check_Type を使います。
使用例 Check_Type(ary, T_ARRAY)
T_ARRAY は、構造体 struct RArray に対応するフラグです。 Ruby オブジェクトを生成するとき、 ruby は struct RXXXX 構造体にこの T_XXXX フラグを記録します。 ですから、構造体からこのフラグを取り出してみればその構造体が 「本当に」(実行時に) どういう型であるかがわかります。 ?Check_Type はそれを見て型をチェックしてくれるわけです。
なお RSTRING() の逆、 つまり構造体へのポインタから VALUE を得る方法は、 「メソッドの実体関数を定義する II」で話します。
さきほど、Fixnum などのインスタンスは例外的に構造体を持た ないと言いました。構造体を持たないのでポインタは取れません。 代わりに、VALUE に埋め込んである整数値を直接 C の型に変換 する関数・マクロがあります。その例をいくつか挙げます。
FIX2xxx() は ?Check_Type() 相当を行いませんので、 すでに型チェックをした値にしか使えません。 NUM2xxx() は構造体型チェックもしてくれるので、 普通はこちらを使うべきです。
以下のマクロで Symbol である VALUE と ID (unsigned long) を相互変換できます。
ID というのはインタプリタで名前を扱うときに使うものです。 例えばメソッド名や変数名は C の文字列ではなく ID を使って 管理されています。
true・false・nil の三つはインスタンスが一つしかないので 以下のマクロで直接表現されます。変換する必要はありません。
| Ruby レベル | C レベル |
|---|---|
| true | Qtrue |
| false | Qfalse |
| nil | Qnil |
Ruby でプログラムを書いている限り GC (Garbage Collection, ガベージコレクション) についてはほとんど意識しなくて済みますが、 拡張ライブラリを書くのならば GC の理解は「必須」です。 「インタプリタがうまくやってくれるだろう」と考えるのは間違いです。 Ruby はメモリ管理をかなり助けてはくれますが、 メモリ管理を忘れさせてくれるわけではありません。
まず Ruby のオブジェクトがどうやって管理されているかを 簡単に説明します。
Ruby オブジェクトは、つまりその実体の構造体は、ヒープに置いてあります。 ようするに malloc 割り当てです。 つまり、いつかは誰かが free しないとメモリリークするということです。
普通の C プログラミングだと malloc も free も自分でタイミングを 決めますが、Ruby オブジェクトの場合そうはいきません。 malloc のタイミングはこちらの自由にできますが、 free するタイミングは Ruby が決定します。我々にできるのは、 free するタイミングを Ruby が間違いなく決定できるように、 情報を提供することだけです。
次に Ruby の GC の仕組みを簡単に説明します。Ruby の GC は マーク&スイープという方式です。マーク&スイープガーベージ コレクタは適当なタイミングで起動し、これから使う可能性のある オブジェクト全てを「マーク」します。このフェイズでマークされ なかったオブジェクトは続く「スイープ」フェイズで全て解放され ます。
つまり、これから使う可能性のあるオブジェクトすべてを、GC が 発生するたびにマークするのが「GC から保護する」ということです。 この責任は拡張ライブラリのプログラマ全員が負わなければなりません。
さて、どうすればオブジェクトをマークすることができるでしょうか。 まず、「マーク」はオブジェクト単位であることを確認してください。 つまり、オブジェクト構造体に付けるものであることを確認してください。 しかし、実際にマークを行うときは VALUE が対象です。
構造体は解放されない
--mark---> VALUE ------> [struct Rxxxx]
構造体は解放されない
--mark---> VALUE ------>
--mark---> VALUE ------> [struct Rxxxx]
--mark---> VALUE ------>
構造体は解放されない
VALUE ------>
--mark---> VALUE ------> [struct Rxxxx]
VALUE ------>
解放される!
VALUE ------> [struct Rxxxx]
解放される!
VALUE ------>
VALUE ------> [struct Rxxxx]
VALUE ------>
※ 対象が構造体であることからわかるように、Fixnum や Symbol は GC の対象になりません。なぜならばその実体は C の整数なので、 そもそもメモリ管理をする必要がないからです。
プロセス内に存在するいずれかの VALUE からオブジェクト構造体を 指せる場合、その構造体はマークしなければなりません。逆に言うと、 必要な構造体を指す VALUE が一つでもあれば、そしてその VALUE を マークの対象にすれば、よいということです。
では、VALUE 型の変数はどうすればマークの対象になるでしょうか。 これには明示的な方法と暗黙的な方法があります。以下の三つに 分けて議論しましょう。
CPU のレジスタ、および C のローカル変数 (マシンスタック上) に VALUE があるときは、暗黙のうちにマークの対象になります。
唯一の例外として、コンパイラの最適化でローカル変数が消えてしまう場合は、 実行時にマシンスタック上に VALUE が置かれなくなるわけですから、 マークされなくなります。 これに関しては「VALUE を最適化から守る」で詳述します。
C のグローバル変数に VALUE を入れる場合は 明示的にマークの対象にする必要があります。 rb_gc_register_address() を使って、 プロセスにただ一度だけアドレスを登録してください。
/* 例 */
static VALUE obj;
void
Init_xxx() /* Init_xxxx については後述 */
{
rb_gc_register_address(&obj)
これで obj に代入されている VALUE と、そこからリンクされている オブジェクトすべてが GC のたびに自動的にマークされます。
ヒープ上に置いた VALUE は GC のたびに明示的にマークする必要があります。 具体的には rb_gc_mark() を使ってマークします。以下にプロトタイプを示します。
void rb_gc_mark(VALUE obj);
rb_gc_mark() は、引数の obj と、 その内部に保持されているオブジェクトすべてを再帰的にマークする関数です。
GC のタイミングを知る方法は「ポインタからVALUEを作る」で書きます。
さて、ここまでは主に Ruby のデータについて話してきました。 今度はコードの話です。
Ruby 拡張ライブラリ (である共有ライブラリ、つまり *.so) が require でロードされると、 Ruby はその共有ライブラリ内に定義されている関数 Init_xxxx() (xxxx はライブラリの require 名) を呼び出します。 例えば require 'somelibrary.so' としたら Init_somelibrary() を呼びます。 この Init_ 関数は Ruby スクリプトで言うところの「トップレベル」に相当します。
Ruby レベルのライブラリ (*.rb) では、 スクリプトのトップレベルでクラスやモジュールを定義しています。 これは C レベルでも同様で、この Init_xxxx() の中でクラスやモジュールを 定義します。ではどのように定義したらよいのでしょうか。 モジュールのほうが簡単なので、まずモジュールから解説していきます。
Ruby の module 文は実はロードのさいに「実行」されており、 細かく見ると以下の三つを行っています。
C での表現はこれに素直に対応します。 1 と 2 は
rb_define_module("SomeModule");
で行います。つまり
module SomeModule end
には
void
Init_somelibrary(void)
{
VALUE SomeModule;
SomeModule = rb_define_module("SomeModule");
}
が相当します。さきほど言ったとおり Init_somelibrary() は Ruby インタプリタが呼び出すので、extern にする必要があります。
3 は Init_xxxx() の残りが相当します。 たとえばメソッド ?SomeModule#a の定義には
rb_define_method(SomeModule, "a", somemod_a, 0);
が対応します。 somemod_a() がこのメソッドの実体です。 「0」はメソッドの引数の数です。
また、特異メソッド ?SomeModule.b の定義には
rb_define_singleton_method(SomeModule, "b", somemod_s_b, 0);
が対応します。 なお、Ruby の拡張ライブラリでは特異メソッドの実体関数に 「rb_xxxx_s_」というプリフィクスをつけるのが一般的なので、 ここでもそれに従っています。
ここまでで Init_xxxx() は以下のようになりました。
void
Init_somelibrary(void)
{
VALUE SomeModule;
SomeModule = rb_define_module("SomeModule");
rb_define_singleton_method(SomeModule, "b", somemod_s_b, 0);
rb_define_method(SomeModule, "a", somemod_a, 0);
}
これは Ruby レベルでは以下に相当します。
module SomeModule
def SomeModule.b
# somemod_s_b() を実行
end
def a
# somemod_a() を実行
end
end
ところで、Init_somelibrary() では暗黙のうちに Ruby オブジェクトを作成しています。 モジュール ?SomeModule です。 それならばこのオブジェクトを GC から保護しなければいけないのではないでしょうか。
ですが、一見したところ ?SomeModule オブジェクトは GC から保護されて いるようには見えません。Init_somelibrary() の実行中はローカル変数 ?SomeModule が存在するのでマーク対象になりますが (GC.1)、関数が終了 したらローカル変数領域自体が消滅し、マークされなくなってしまうからです。 しかしモジュールオブジェクトは Ruby の定数経由で参照できるので、 保護しなくてはいけないのではないでしょうか。
結論から言うと、この点は心配ありません。rb_define_module() は モジュールオブジェクトを Ruby の定数に代入します。そして定数に 代入されているオブジェクトは Ruby が責任を持ってマークしてくれます。 他には以下のような VALUE も自動マークの対象になります。
我々が責任を持たなくてはいけないのは、我々が直接使う VALUE だけ です。Ruby インタプリタが勝手に保存して使う VALUE を保護するのは Ruby インタプリタの責任です。
続いて、メソッドの実体の関数 (以下、実体関数と略記) の書きかたを 話します。以下が典型的な実体関数のプロトタイプです。
static VALUE somemod_a(VALUE self);
第一引数に self を受け取り、VALUE を返します。Ruby には値を 返さないメソッドはないので返り値は常に VALUE です。論理的に void のときはその場の判断で self とか nil を返すとよいでしょう。 それから、基本的に実体関数は static にすべきです。
さて、?SomeModule#a を String オブジェクト「"OK"」を 返すように定義しましょう。つまり
def a "OK" end
を定義します。 この Ruby プログラムには以下のコードが対応します。
static VALUE
somemod_a(VALUE self)
{
return rb_str_new2("OK");
}
rb_str_new2() が C の文字列から Ruby の String オブジェクトを作る関数です。
ところで、以前 「Ruby オブジェクトは必ず GC から保護しなければならない」 という規則を述べました。somemod_a() ではどのようにオブジェクトが保護されているでしょうか。
まず引数の self はどうでしょうか。 関数の引数はレジスタかマシンスタックに置いて渡されるので、 保護されています。
rb_str_new2() で作成する文字列オブジェクトはどうでしょうか。 rb_str_new2() がオブジェクトを作成してから 値が返ってくるまでの間は、やはりレジスタかマシンスタックにあるはずです。 それを受け取って返り値にする途中も、 やはりレジスタかスタック上に VALUE が存在するはずです。 従ってこのメソッドでは Ruby オブジェクトは完全に保護されています。
あたりまえと言えばあたりまえですが、 関数間の引数受け渡しと返り値に関しては GC を気にしなくてよいと言えます。
クラス定義自体 (クラスオブジェクトの作成) は簡単で、 rb_define_module() を rb_define_class() に変えるだけです。 Ruby での
class SomeClass < Object
は、C では
rb_define_class("SomeClass", rb_cObject);
が対応します。rb_cObject はクラスオブジェクト Object の VALUE を格納しているグローバル変数です。
このように、クラス自体を定義するのは簡単です。 問題は独自インスタンスの作成、つまり new の定義方法です。 それを以下で説明します。
(注:以下の話は Ruby 1.8 以降が対象です)
我々が普段使っている ?AnyClass.new は Class#new であり、 その C での実体は rb_class_new_instance() です。 この関数はまずクラスオブジェクトに対してインスタンスの作成 (よーするにメモリ割り当て) を要求します。 続いてその「割り当てられただけのオブジェクト」 に対してメソッド initialize を呼び、オブジェクトを初期化します。 従って、我々がクラスを定義するときにはこの二つの操作、つまり
に対応する関数を用意する必要があります。
では以下でサンプルプログラムを書きながらこの二つを説明して いきましょう。サンプルにするのは整数のカウンターを表現する Counter クラスです。Ruby で書くと次のようになります。
class Counter
def initialize
@count = 0
end
attr_reader :count
def increment
@count += 1
end
end
まず、独自クラスのインスタンスの領域は malloc() で割り当てます。 Ruby 関係のソースコードでは ALLOC() や ALLOC_N() という マクロが使われていますが、これは「ちょっと便利な malloc()」であり、 メモリ領域が最終的に malloc() で割り当てられることに違いはありません。
ちなみに ALLOC() のどこが便利かと言うと、 NULL を返すことがないというところです。 ALLOC() は malloc() に失敗するとまず GC を行ってからやりなおし、 それでも失敗したら ?NoMemoryError を投げます。 従って返り値をチェックする必要がありません。
では構造体を割り当てます。まず構造体を定義し、
struct counter {
int count;
};
それを関数内で割り当てます。
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
ALLOC() の引数は、割り当てたい型です。 サイズではありません。
さて、counter_alloc の返り値は VALUE なので、 このポインタを VALUE に変換しなければいけません。 直接対応する struct RXxxxx があるクラスなら キャストして返すだけでよいのですが、 ユーザ定義の構造体の場合にはもう一工夫必要です。 以下の図のように、構造体をカスケードしなくてはいけないのです。
+-VALUE-+ +---- struct RData ----+
| -------> | VALUE klass; |
+-------+ | unsigned long flags; |
| void (*dmark)(void*) |
| void (*dfree)(void*) | +-struct counter-+
| char *data ------------> | int count; |
+----------------------+ +----------------+
このような仕組みになっているのは、やはり GC のためです。 struct counter には VALUE のメンバはありませんが、VALUE が 必要になったとしてみてください。struct counter は malloc 割り当て (ヒープ割り当て) なので、 メンバの VALUE を明示的にマーク対象にする必要があります。 しかしこの場合、
わけです。 ですから、
ことによって問題を解決します。struct RData の dmark メンバは そのための関数 (マーク関数) を保存しておくスペースです。
また struct RData にはもう一つ、dfree というメンバがあります。 これは構造体 (struct counter) を解放するのに使う関数です。 I/O ストリームなど、後始末が必要なときに使います。 通常はここには -1 を渡しておけば十分です。 Ruby が free() を呼び出してくれます。
さて、具体的にどうやって struct RData を用意するのでしょうか。 これには、そのための専用マクロ、?Data_Wrap_Struct を使います。
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
return Data_Wrap_Struct(klass, 0, -1, ptr);
}
?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 : ユーザの用意した構造体へのポインタ
これでインスタンスを割り当てる関数は完成です。あとはこれを クラスに関連付ければ終わりです。関連付けるには、 Init_xxxx() の中で rb_define_alloc_func() を呼びます。
void
Init_counter(void)
{
VALUE Counter;
Counter = rb_define_class("Counter", rb_cObject);
rb_define_alloc_func(Counter, counter_alloc);
/* ……以下、メソッドを定義する…… */
}
initialize は通常のメソッドなので、一般のメソッド定義と同じように 定義するだけです。ただし initialize は普通は private なので、 rb_define_method() の変種の rb_define_private_method() を使います。 API の使いかたは rb_define_method() と全く同じです。
void
Init_counter(void)
{
VALUE Counter;
Counter = rb_define_class("Counter", rb_cObject);
rb_define_alloc_func(Counter, counter_alloc);
rb_define_private_method(Counter, "initialize", counter_initialize, 0);
/* ……以下、メソッドをさらに定義する…… */
}
では次に counter_initialize() の書きかたを説明します。 とりあえず外枠は簡単ですね。
static VALUE
counter_initialize(VALUE self)
{
/* なにかする */
}
問題はこの関数の中身です。この関数を書くには、 self (struct RData* であるはずの VALUE) から struct counter* を取り出さなければいけませんね。 つまり ?Data_Wrap_Struct() の逆です。 これには ?Data_Get_Struct() というマクロを使います。
static VALUE
counter_initialize(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
これで変数 ptr に struct counter* が取り出せます。 あとは適切に構造体を初期化すれば OK です。
static VALUE
counter_initialize(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
ptr->count = 0;
return Qnil;
}
initialize の返り値には意味がないので、 適当に nil を返しておくことにします。
以上で Counter.new が正常に働きます。
あとは Counter#increment と Counter#count を定義すれば とりあえず終わりです。最後なので Counter の定義を全て載せます。
/*
counter.c
*/
#include "ruby.h"
struct counter {
int count;
};
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
return Data_Wrap_Struct(klass, 0, -1, ptr);
}
static VALUE
counter_initialize(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
ptr->count = 0;
return Qnil;
}
static VALUE
counter_count(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
return INT2FIX(ptr->count);
}
static VALUE
counter_increment(VALUE self)
{
struct counter *ptr;
Data_Get_Struct(self, struct counter, ptr);
ptr->count++;
return self;
}
void
Init_counter(void)
{
VALUE Counter;
Counter = rb_define_class("Counter", rb_cObject);
rb_define_alloc_func(Counter, counter_alloc);
rb_define_private_method(Counter, "initialize", counter_initialize, 0);
rb_define_method(Counter, "increment", counter_increment, 0);
rb_define_method(Counter, "count", counter_count, 0);
}
言い忘れていましたが、VALUE などは全て ruby.h で定義されているので、 拡張ライブラリでは必ず ruby.h を #include してください。
counter.c をコンパイルするには、単に cc を呼ぶだけでは不十分です。 ruby.h は標準的なインクルードパスには置いていないので #include が エラーになりますし、*.so を作る場合はその他にいろいろフラグが必要です。 しかしそれをいちいちやるのは面倒なので、拡張ライブラリを 簡単にコンパイルするための手順が用意されています。
まず extconf.rb というファイルを作り、こう書きます。
require 'mkmf' create_makefile 'counter'
「counter」の部分は Init_xxxx の xxxx を使います (それはつまり、require 'xxxx' とするときの xxxx でもあります)。
そして ruby extconf.rb を実行します。これで Makefile ができるので、 あとは make するだけです。
% ruby extconf creating Makefile % make gcc -pipe -fPIC -g -Wall -I. -I/usr/lib/ruby/1.8/i686-linux -I/usr/lib/ruby/1.8/i686-linux -I. -c counter.c gcc -pipe -shared -rdynamic -L"/usr/lib" -o counter.so counter.o -lruby-1.8.0 -ldl -lcrypt -lm -lc % ls Makefile counter.c counter.o counter.so extconf.rb
counter.so ができました。Ruby から使ってみましょう。
% cat test.rb
require 'counter'
cnt = Counter.new
p cnt.count
cnt.increment
p cnt.count
cnt.increment
p cnt.count
100.times { cnt.increment }
p cnt.count
% ruby test.rb
0
1
2
102
うまくいっているようです。
さて、なかなかうまくいっているように見える Counter クラスですが、 一つ問題を見付けました。カウンタが int なので、int の範囲を越えると オーバーフローしてしまうのです。そこでカウンタに Ruby の整数を 使うことにしました。つまり struct counter を次のように変えようと いうことです。
struct counter {
VALUE counter;
};
さて、こうなると突然新しい問題が発生します。 オブジェクト構造体に VALUE があるため、 こいつの GC 対策が必要になるからです。 通常のメソッド (increment とか) は置いておいて、 counter_alloc() について話しましょう。 そこが一番 GC に関わっているからです。
※ Ruby の整数は Fixnum だろ、Fixnum はポインタじゃないから マークしなくていいんじゃないのか……と思うかもしれません。 しかし整数が大きくなると勝手に Bignum になります。 そしてBignum には構造体があるので、 やはり明示的に保護しなくてはいけないのです。
まず、元の counter_alloc() はこうでした。
static VALUE
counter_alloc(VALUE klass)
{
struct counter *ptr = ALLOC(struct counter);
return Data_Wrap_Struct(klass, 0, -1, ptr);
}
struct counter のメンバが VALUE になると、これがこう変わります。
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);
}
?Data_Wrap_Struct の第二引数に counter_mark が渡っています。 これが struct counter をマークするための関数です。そして counter_mark では count メンバを rb_gc_mark しています。 これで count メンバ (の指す Ruby オブジェクト) がちゃんと マークされるようになりました。
以上で基本は終わりです。 拡張ライブラリの基本とは以下の三つです。
あとは重要ライブラリの API を見ていけばよいでしょう。 特に重要なのは以下のカテゴリです。
それぞれ簡単に見ておきましょう。
Ruby の文字列 (struct RString) は、 char の配列 (str->ptr) とその長さ (str->len) を保持しています。 この ptr と len には直接アクセスが可能です。
ただし許されるのは参照だけです。 メンバを書き換えるのは不可です。 読んだ値を保存して別の関数で使ったりしてもいけません。 毎回 struct RString から読み直してください。
また前述した通り、RXXXX() を使う前には 構造体型をチェックしなければいけません。 そのためには ?Check_Type() を使うのでした。 例えば文字列であるかどうかチェックしたければ以下のようになるでしょう。
VALUE str = ....; /* str が struct RString* でなかったら TypeError */ Check_Type(str, T_STRING);
ですが文字列の場合は、 メソッド to_str による暗黙の変換を許したほうがよいことが多々あります。 ですから通常は文字列を期待する引数は次のように マクロ ?StringValue() を使って文字列にします。
static VALUE
someclass_somemethod(VALUE self, VALUE str)
{
StringValue(str);
f(RSTRING(str)->ptr);
?StringValue(str) は、str が文字列なら何もせず、 それ以外のときは to_str で変換を試み、 to_str も定義されていなければ例外を発生します。 なお、GC の都合により、 ?StringValue() は変換した値を変数に代入して返します。
また、ポインタ (str->ptr) だけがあればよい場合は ?StringValuePtr() が使えます。 このマクロを使うと先程のコードは次のように書けます。
static VALUE
someclass_somemethod(VALUE self, VALUE str)
{
f(StringValuePtr(str));
ところで、RSTRING(str)->ptr は NULL になる可能性があります。 NULL でないことを保証したい場合は、 マクロ StringValueCStr() を使ってください。 ただし、同時に文字列中に NUL ('\0') を入れられなくなります。
文字列関係の重要 API を以下に示します。 返り値は全て VALUE なので省略します。
詳しくは refe -e で調べてください。 → ReFe
配列は VALUE の配列 (ary->ptr) とその長さ (ptr->len) を保持しています。 こちらも構造体メンバへの直接アクセスが可能です。
以下に重要 API を示します。 返り値は全て VALUE なので省略します。
詳しくは refe -e で調べてください。 → ReFe
可変長引数は、Ruby レベルで言うオプション引数 (arg = default) だとか 「残り全部引数」(*arg) を C で実装するための仕組みです。 ここでは
class SomeClass
def a(fst, opt = nil, *rest)
....
end
end
という a を実装してみます。
まず、引数の数を可変にするには rb_define_method の第四引数 (メソッドの引数の数) を -1 にします。
rb_define_method(SomeClass, "method_a", someclass_method_a, -1);
そして実体関数のプロトタイプを次のように変えます。
static VALUE someclass_a(int argc, VALUE *argv, VALUE self)
argc が実際に渡ってきた引数の数、argv が引数の配列です。
可変長引数を使うと Ruby は引数の数のチェックをしなくなるので、 こちらで明示的にチェックする必要があります。地道に手でやっても 構いませんが、rb_scan_args を使うと少し楽をできます。
fmt のフォーマットは以下の通りです。
これらの指定文字はそれぞれ省略可能ですが、 必ずこの順番で現れなければいけません。
rb_scan_args を使ってさきほどのコードを実装する例を以下に示します。
static VALUE
someclass_a(int argc, VALUE *argv, VALUE self)
{
VALUE fst, opt, rest;
rb_scan_args(argc, argv, "11*", &fst, &opt, &rest);
任意のメソッドを呼び出す API がいくつかあります。 もっとも基本的なのが rb_funcall() です。
メソッドへの引数は第四引数以降にあたえ、 その数を nargs に指定します。 メソッドの引数はすべて VALUE でなければいけません。
VALUE result;
result = rb_funcall(rb_str_new2("test"), rb_intern("chop"), 0);
rb_p(result); /* 「"tes"」と表示されるはず */
他に、引数の与えかたが違う rb_funcall2()、rb_funcall3()、rb_funcall4() もあります。
例外を発生するには rb_raise() を使います。
rb_raise(rb_eArgError, "wrong number of arguments (%d for 3)", argc);
また、例外を rescue するには rb_rescue() を、 ensure 節を定義するには rb_ensure() を、それぞれ使います。
イテレータを作るためのメソッドをいくつか紹介します。
ちなみに引数なしの yield に対応するのは rb_yield(Qnil) です。
C レベルでイテレータを呼び出すこともできます。 ReFe で rb_iterate() を調べてください。
デバッグ用関数も紹介しておきます。
ここまでは「とにかく動作するように」という視点で話しました。 この節では「安全に動作するように」という視点で話します。
Ruby レベルでは意識しなくてもよいことでも拡張ライブラリでは 強く意識する必要がある、という点はいくつもあります。例えば GC はよい例です。そしてセキュリティチェックもその一つです。
Ruby レベルでプログラムを書く限り、$SAFE だけ設定しておけば あとは Ruby がセキュリティチェックをやってくれます。しかし 拡張ライブラリでは意識的に配慮しないとセキュリティチェックの 枠組自体が壊れます。
まず Ruby のセキュリティチェックの仕組みを簡単におさらいします。
Ruby のセキュリティチェックには二種類あります。
セーフレベル 1 では危険な値がチェックされます。 セーフレベル 4 では危険な動作もチェックされます。
危険な値とは「汚染マーク」がついているオブジェクトのことです。 汚染マークは一度付いたら明示的に除去しないと外れません。 そして、そのオブジェクトを元に生成したオブジェクトもやはり 汚染されます。この汚染マークが付いたオブジェクトを使って 特定の操作 (I/O など) をするのを禁止するのが 「危険な値のチェック」です。
一方「危険な動作」とは、I/O やプロセス操作のことです。 さきほどとの違いは、汚染されていないオブジェクトを使っても、 動作だけで禁止されるということです。
拡張ライブラリでこの枠組に従うには、主に以下の三つを行う 必要があります。
汚染マークの伝播には以下の API が使えます。
GC の話をしたとき、コンパイラの最適化でローカル変数が 消えてしまう場合がある、ということを言いました。 具体的には以下のようなコードでこの問題が起こる可能性があります。
VALUE str = rb_str_new("testtesttest");
some_function(RSTRING(str)->ptr);
/* 以降のコードでは str を使っていない */
some_function() は、途中で GC が発生する可能性のある関数です。
この場合、some_function() を呼び出す時点で str は必要なくなりますから、 コンパイラが str を消してしまうことがあります。 そうすると some_function() の途中で GC が起こり、 str (であった Ruby オブジェクト) が回収され、 str->ptr が free されます。 some_function() の続きでさらに str->ptr を使うと SEGV するという理屈です。
解決するには、VALUE の宣言に volatile を付けます。
volatile VALUE str = rb_str_new("testtesttest");
some_function(RSTRING(str)->ptr);
これならば some_function() を呼んでいる間も str は確実にマシンスタック上に残ります。
また特に String の場合は、 ?StringValue()・?StringValuePtr()・StringValueCStr() を使うと GC safe になります。
Object には dup が定義されているので、 それを継承した ?SomeClass にもやはり dup が呼び出せます。 しかしデフォルトの定義では内部構造体まではコピーされないので (できないので)、 不適切な定義のままになっています。
dup を正しく動かすには、?SomeClass#initialize_copy(orig) を定義します。 initialize_copy は orig を self にコピーするメソッドです。 例えば Counter クラスに定義するなら次のようにします。
static int
counter_p(VALUE obj)
{
Check_Type(obj, T_DATA);
return (RDATA(obj)->dmark == counter_mark);
}
static VALUE
counter_init_copy(VALUE self, VALUE orig)
{
struct counter *ptr, *org;
Data_Get_Struct(self, struct counter, ptr);
if (!counter_p(orig)) {
rb_raise(rb_eTypeError, "Counter#initialize_copy called with wrong object");
}
Data_Get_Struct(orig, struct counter, org);
ptr->count = org->count;
return Qnil;
}
まず counter_p() で orig が Counter オブジェクトかどうか確認しておきます。 ここではマーク関数が Counter のものかどうかで判定しました。 これは拡張ライブラリでの常套手段です。
self と orig それぞれの内部構造体が取れればあとは簡単です。 今回は単純に count をコピーすることにしました。
全ての Ruby オブジェクトは freeze できますが、 C レベルではその保護機構がほとんど働きません。 つまり明示的にフラグをチェックして、 freeze されているときは内容を変更しないようにする必要があります。 コンテナ系のクラス (mutable object) を実装するときは できるだけ freeze に対応すべきです。
freeze に関しては以下の API が使えます。
C レベルで定義したオブジェクトはデフォルトではマーシャルできません。 マーシャルできる (できると便利な) オブジェクトならば できるだけマーシャルできるようにすべきでしょう。
定義する方法は Ruby レベルと同じです。 つまり、Ruby 1.8 以降ならば ?SomeClass#marshal_load と ?SomeClass#marshal_dump を定義します。
簡単な拡張ライブラリなら extconf.rb だけで済みますが、 ソースコードが複数ある場合などは depend というファイルが必要になります。
depend は、ソースコードの依存関係を記述するファイルです。 例えば counter.so の場合なら次のように書いておけば十分です。
counter.o: counter.c
もちろん、もっと複雑な依存関係がある場合はそれを記述します。 また拡張子 .o は環境によって .obj になったりもしますが、 そのあたりは mkmf.rb が吸収してくれるので常に .o で大丈夫です。
このファイルは、extconf.rb (mkmf.rb) が Makefile を生成するときに単純に末尾に連結されます。 ですから実は依存関係以外にいろいろ書いてしまうことも できるのですが、お勧めできません。 なぜなら世の中にはいろいろな make があるからです。 例えば nmake (VC++ の make) とか Borland make とか。
まず、一次情報は常にソースコードです。 ソースコードに同梱されている README.EXT.ja も有益です。 さらに詳しく ruby 内部について知りたいときは RubyHackingGuide も役に立つでしょう。
RubyDocumentationProject には拡張ライブラリ API の リファレンスマニュアルがあります。また ReFe はこの リファレンスをコマンドラインから検索することができ、 データベースも最初から付いています。
ruby-dev ML を地道に読むとかなり濃い情報が得られます。
Related Pages: RubyPages OptimizingRubyProgram
system revision 1.162