ENTRANCE|INFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)|Developper's Nest(開発掲示板)|こころ宙
C 言語でも、Ruby でも、全てのプログラムは何かしらのデータを扱っています。たとえば数値データであるかもしれませんし、文字列データであるかもしれません。
それらのデータは、C 言語と Ruby の間で、扱い方が異なっています。C 言語であれば、変数には余計な情報が一切付けられません。たとえば
int 型として宣言された変数 a は、メモリ内の数バイトを表すためのいわば「名前」であり、それ以上の情報は持っていません。しかし、Ruby の変数は様々な情報を持っています。ある変数 a があったとき、その変数からは「どのクラスのインスタンスか?」「インスタンス変数は?」などの情報を得ることができます。
では、拡張モジュールから Ruby の変数を操作するにはどうすればよいのでしょうか? まず、Ruby
の世界に存在しているオブジェクトを、C 言語の世界で見てあげなければなりません。
その方法として、Ruby には VALUE という型が用意されています。この型を介することによって、C 言語から Ruby
の変数を認識することができるようになります。逆に、Ruby から C 言語で作られたデータを認識することもできるようになります。
ここで、二つの課題が出てきます。
どちらも、それなりに多くの話題を抱えています。
ここではまず、「Ruby から渡されたオブジェクトを C 言語でどのように操作するか?」というところから話を始めていこうと思います。「C
言語で Ruby オブジェクトをどのように生成するか?」という話に関しては、いろいろと細かい話を抱えているので、後に回します。
ですから、現状では、「Ruby からデータを受け取り、拡張モジュール内で処理を完結させる。」というスタイルのものを作ることができる、ということになります。
Ruby の Fixnum クラスおよび Bignum クラスのインスタンスは、C 言語の int 型や long 型 (数値) に変換することができます。
int rb_num2int (VALUE object) |
|
object |
数値オブジェクト。 |
| 返値 | object に対応する数値。 |
なお、この API は、引数として渡される object が Fixnum クラスや Bignum クラス以外のインスタンスであっても、to_int() メソッドによって数値オブジェクトに変換され、その結果が数値になって返されます。to_int() が定義されていないような場合は、例外 TypeError が発生します。
変換対象のオブジェクトが Fixnum クラスのインスタンスである、と分かっているのであれば、より高速に変換するマクロが用意されています。ただし、一切のチェックが行われないので、確実でない場合は rb_num2int() を使った方が安全でしょう。
FIX2LONG(fixnum) |
|
fixnum |
Fixnum インスタンス。(VALUE) |
| 返値 | fixnum に対応する数値。(long) |
Ruby に付属している拡張モジュールのドキュメントでは FIX2INT() マクロを使うように書かれていますが、現在の実装 (Ruby 1.6.7) を見る限り、FIX2INT() マクロは、性能的に rb_num2int() とほとんど変わりません。ただし、今後の実装では、また変わってくる可能性があります。
(2002/11/7), (2002/12/17)
Ruby の String インスタンスは、C 言語の char* 型 (つまり文字列) に変換することができます。
char* rb_str2cstr(VALUE str, int* len) |
|
str |
String インスタンス。 |
len |
文字列の長さを格納するバッファ。NULL が指定されている場合は、無視されます。 |
| 返値 | 文字列の先頭を示すポインタ。 |
Ruby 1.6.7 の実装では、len に NULL 以外が指定され、Ruby インタプリタが verbose モードで動作している場合、文字列内にヌル文字が含まれているかどうかのチェックが行われるようです。また、str で渡されたオブジェクトが String インスタンスでない場合は、自動的に文字列オブジェクトへ変換されます。変換には to_str() メソッドが使用されます。
文字列長を取得する必要がない場合は、次のマクロを使うことで、より簡単に文字列を取得できます。
STR2CSTR(str) |
|
str |
String インスタンス。(VALUE) |
| 返値 | 文字列の先頭を示すポインタ。(char*) |
また、数値型で見た FIX2LONG のように、一切のチェック無しに、高速に文字列ポインタを取得する方法もありますが、これについては、また後で取りあげることにします。
なお、詳しい話は後に回しますが、上の rb_str2cstr() API で得られた文字列は、変換後にいくつかの操作を行うと、ガベージコレクタによってメモリ領域が開放されてしまう可能性があります。変換で得られたポインタはすぐに使い、長期間、使い回すようなことはやめた方がいいでしょう。
Ruby 1.8 からは、新しい API が追加されました。
StringValuePtr(value) |
|
value |
VALUE 変数。(VALUE) |
| 返値 | 文字列の先頭を示すポインタ。(char*) |
この API は、引数で渡された value が文字列オブジェクトでなければ、文字列オブジェクトを生成し、そのポインタを返すと共に value 自身も書き換えてしまうというものです。そのため、渡される引数 value は、必ず VALUE 型の変数でなければなりません。(内部でアドレスを求めているからです)
Ruby 1.8 以降では、これまでの STR2CSTR などが obsolete として扱われてしまっています。これから拡張モジュールを作成しようとしている方は、知っておいた方がいいでしょう。なお、これまで通り、STR2CSTR などを使用することもできます。
Ruby の Array インスタンスには、任意数の Ruby オブジェクトが格納されています。Ruby API を使用することで、C 言語から、配列要素であるオブジェクトにアクセスすることができます。
VALUE rb_ary_entry(VALUE ary, long offset) |
|
ary |
Array インスタンス。 |
offset |
配列中から要素を取得する位置。 負の値を指定した場合は、配列の末尾から数えられる。 |
| 返値 | 指定された位置の Ruby オブジェクト。 範囲外の位置が指定された場合は nil が返される。 |
この API を使うことで、Ruby と同じような感覚で配列を扱うことができます。
Ruby で「配列の各要素に対して何かを行いたい」という場合は、イテレータを使うのが一般的です。しかし、C
言語にはイテレータのような仕組みが存在しないため、同様のことを行うには、ゼロから配列長まで増加させていくカウンタを使うのが一般的です。
そこで、配列の長さを取得する方法が必要になります。
…しかし、Ruby 1.6.7 の時点では、配列の長さを取得するための API が存在していません。そのため、直接的に配列長を取得する必要があります。
int len = RARRAY(ary)->len;
とりあえずは、上のように書くものなのだ、と思っていてください。詳しくは、また後で述べます。
ary には Array のインスタンスである VALUE を指定します。
(2002/11/18, 2003/03/31)
Hash インスタンスには、あるオブジェクトとオブジェクトの対応関係が納められています。Ruby API を使用することによって、指定されたオブジェクトに対応するオブジェクトを求めることができます。
VALUE rb_hash_aref(VALUE hash, VALUE key) |
|
hash |
Hash インスタンス。 |
key |
キーとなるオブジェクト。 ハッシュ中の、このオブジェクトに対応するオブジェクトが返されます。 |
| 返値 | key に対応するオブジェクト。存在しない場合は nil が返されます。 |
この API を使うことで、拡張モジュール (C 言語) 中でも Ruby と同じ感覚でハッシュを使うことができるようになります。
プログラムを組んでいると、ハッシュの中にどんなキーが格納されているか
(Ruby でいう keys メソッド)、どんな値が格納されているか (Ruby でいう values メソッド)、などということを知りたい場合があるでしょう。しかし、現状 (1.6.7)
では直接的な手段 (API を使った手段) は提供されていません。
この問題を解決するには「Ruby メソッドを拡張モジュールから呼び出す」ということをしなければならないのですが、それはもう少し後に回すことにします。
(2002/12/20)
ENTRANCE|INFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)|Developper's Nest(開発掲示板)|こころ宙