LABORATORY - Ruby 拡張モジュール

メインメニューDIARYINFORMATIONLABORATORYLINK


ソフトウェアプログラミングデータベース


このところ、まったく更新できていません。すみません、すみません。書きかけたからには、ちゃんとやります。ごめんなさい。
INDEX
  1. 拡張モジュール作成に必要なもの
  2. エントリポイントとクラス定義
  3. オブジェクトの生成
  4. メソッドの定義
  5. C 言語から Ruby オブジェクトを見る
  6. C から Ruby のメソッドを呼ぶ

1. 拡張モジュール作成に必要なもの

 まずは、モジュール名 が必要になります。どんな拡張モジュールを作るのか決めたら、それに合った名前を考えます。どれだけ長くても構いませんが、後で require することを考えると、短い方がいいですよ?(笑)

 次に、C のソースコード が必要になります。が、この時点ではファイル名だけで十分でしょう。中身はこれから書くのですからね。ソースファイルひとつで済むのならば、モジュール名と同じに、複数使うのならば、同じ名前にならないように命名するのが良いそうです。まあ、あくまで目安ということで。bstream というモジュール名でソースファイルが一つだけならば、bstream.c というソースファイルにするわけです。

 ここまで揃ったら、extconf.rb を作りましょう。これはモジュールを作成するための Makefile を生成するスクリプトです。つまり、拡張モジュール作成のステップは「extconf.rb → Makefile → make」という流れになります。これにより、どんな環境でも適切な Makefile を作ることができるようになり、拡張モジュールのプログラマが環境に合わせた Makefile を作らなくてすむようになります。automake みたいなものですね。
 extconf.rb の中身は次のようになります。

#!/usr/local/bin/ruby
require 'mkmf'

create_makefile( 'bstream' )

これは拡張モジュール bstream 用の extconf.rb です。他のモジュールにするときは、create_makefile() への引数 (太字の部分) を書き換えます。また、extconf.rb に実行ビットを立てておくのも忘れないように。ruby へ直接引き渡すのならば良いのですけれど。

 あ、そうそう。これらの前提として、「拡張モジュールを作成する必要性」があるものとしていますからね。ちゃんと動機を持って作るようにしてください…。簡単に作成できますが、Ruby で作るのに比べれば、やはり面倒で時間がかかりますから。ライブラリを Ruby で使えるようにしたい、というのならば仕方ないですけれども。
 まず、他の人が同じようなものを作っていないか、調べてみましょう。作られていない場合は自分がやるしかありませんが、作られている場合は、自分が作業する必要が無くなるかもしれませんし、作らなければならない場合でもインターフェイスなどが自分で作るときの参考になるかもしれませんしね。

 それと、重要なことを忘れていました。拡張モジュールを作成するには、「Ruby のソースコード」「コンパイラ」が必要になります。mswin など、バイナリのみで配布されたものを使っている場合は気をつけましょう。Windows には標準で C コンパイラが付属されていないので、Visual C++ や Cygwin が必要になってきます。
 ソースから make して、make install したならば、/usr/local/include などに必要なファイルがコピーされていますし、create_makefile() で作られた Makefile にそれらへのパスが埋め込まれていますので、とくに意識する必要はありません。

 では、準備ができたら、次はいよいよコーディングです。

2. エントリポイントとクラス定義

 拡張モジュールのエントリポイント (require などで読み込まれたときに実行されるコード) は、main() ではなく Init_拡張モジュール名() になります。ここでいう拡張モジュール名というのは、extconf.rb の create_makefile に渡された文字列のことです。上の例で言えば、次の関数が拡張モジュールのエントリポイントになります。

void Init_bstream ( void )
{
    ...
}

このエントリポイントでは、通常、拡張モジュールが提供するクラスの定義を行います。Ruby で class などとして宣言するのと同じです。例を挙げてみましょう。

class Sample
  def foo ()
  end
end

Ruby で書かれたのと同じクラスを宣言しようとすれば、

#include "ruby.h"

VALUE sample_foo ( VALUE self )
{
  return Qnil;
}

void Init_bstream ( void )
{
  VALUE c;

  c = rb_define_class( "Sample", rb_cObject );
  rb_define_method( c, "foo", sample_foo, 0 );
}

となります。

 まず、rb_define_class() で Ruby のクラスを宣言します。ここでは、Sample という名前のクラスを Ruby の Object クラスから派生させて作成する、としています。気がついていないかもしれませんが、Ruby でクラスを宣言すると、明示的に指定されていない限り、Object クラスから派生しているということになります。ちなみに、rb_define_class ではこの親クラスを省略することはできません。拡張モジュールを書くときは、Ruby がやってくれているようなことを自分で明示的に書かなければならないのです。
(C 言語が引数の省略を許さない、という事情もあると思いますけれど…)

 さて。返値を c という VALUE 型の変数に格納していますが、この VALUE という型は Ruby で使われている変数を示すものと思ってください。Ruby では、クラスそのものも Class というクラスのオブジェクトになっています。ですから、宣言された「クラスオブジェクト」が返されてくるのです。ここら辺、ちょっとややこしいかもしれませんね。ちなみに、何の断りもなく使っている rb_cObject は、ruby.h で宣言されているグローバル変数で、Object のクラスオブジェクトを示すようになっています。

 次の rb_define_method() で、クラス c …すなわち、Sample クラスにメソッド foo を宣言しています。実は、Ruby の内部では、各メソッドには C の関数が対応づけられています。Ruby のメソッドを呼んでいるつもりでも、実際には C の関数を呼んでいるわけです。拡張モジュールでもこの仕組みを使いますので、sample_foo() 関数へのポインタをメソッド宣言時に引き渡すようにしています。これで、Sample オブジェクトのメソッド foo が呼び出されたとき、sample_foo() 関数が呼び出されるようになります。
 最後の 0 は、引数の数を示しています。この foo メソッドは 0 個の引数をとる、という意味になります。上限は 15 だったか 16 だったかなのですが、普通、そんなに引数をとることはありませんよね? そんなに使いたい場合は、Ruby の配列で引数を渡すようにすれば実現できます。
 詳しい話はもっと後で行います。とりあえず今は「ふーん」と聞いておいてください。

 それでは、次はオブジェクトの生成です。

3. オブジェクトの生成

 Ruby では、ふつう、オブジェクトを new メソッドによって生成します。しかし、この返されてきているオブジェクトがいったい何なのか、不思議に思ったことはないでしょうか? Ruby でしばらくプログラミングしていればわかると思いますが、new メソッドを自分で定義した、というようなことは無いはずです。C++ などの コンストラクタに相当するメソッドは initialize メソッド ですので、わたしたちが定義したクラスの new メソッドは、Ruby のすべてのクラスの親、Object クラスの new メソッドを使っているということになります。 
 はて? それでは、new メソッドはいったい何をしているというのでしょうか? コンストラクタでもない、自分で挙動を定義するわけでもない。それなのに、万事がうまくいくのです。不思議に思ったことはないでしょうか。
 この謎を解く鍵は、Ruby のクラスと C++ や Java のクラスを見比べて、その違いを考えることで見えてきます。

 C++ や Java においては、クラスを宣言するときはメンバ変数も宣言しなければなりませんでした。たとえば、次のようにですね。

class Foo
{
  int a, b, c;

  int bar ( int x ) {
    a = x;
    return b;
  }
};

 しかし、Ruby では次のようになります。

class Foo
  def bar ( x )
    @a = x
    return @b
  end
end

何か気がつきましたか? そうです、メンバ変数の宣言が存在していない のです。C++ や Java では、クラスとクラスの違いに「名前が違う」、「メソッドが違う」、そして「メンバ変数 (=データ構造) が違う」と 三つ のものがあります。しかし、Ruby でのクラスとクラスの違いは「名前が違う」と、「メソッドが違う」の 二つ しかないのです。データ構造はすべて共通 (インスタンス変数が自動的に作られるデータ構造) なのです。
 ということで、Ruby においては、全てのオブジェクトに共通したデータ構造を生成する働きを持つ、Object クラスのクラスメソッド new を書き換える必要はない、ということになります。

 さて。ここまでは、Ruby のみでプログラミングしているときの話です。拡張モジュールは C 言語でプログラミングされますので、当たり前ですが、拡張モジュールで扱うデータ構造は C 言語で使われるもの、ということになります。つまり、C 言語から Ruby オブジェクトを見てあげる必要があるわけです。
(C で [0, "ruby", 1.6] と書いても、Integer と String、Float の Array になりませんよね。)
 ということで、C 言語から Ruby オブジェクトを見てみますと…、クラス定義のところで現れましたが、VALUE 型の変数として見えるのです。詳しい話は省略しますが、この VALUE 型の変数はポインタとしても見ることができ、そのようにポインタとして見た場合、Ruby オブジェクトのデータ構造を表す struct RObject * などとして見ることができます。struct RObject という構造体は、Ruby のヘッダファイル ruby.h で定義されています。
 その struct RObject を抜粋してきますと…、

struct RBasic {
    unsigned long flags;
    VALUE klass;
};

struct RObject {
    struct RBasic basic;
    struct st_table *iv_tbl;
};

となっています。これが、Ruby オブジェクトの C 言語におけるデータ構造です。
 しかし、拡張モジュールで作成するクラスもこのデータ構造 (struct RObject など) を使わなければならない、というわけではありません。サードパーティ製のライブラリで使われるデータがこの構造になっているというわけはありませんし、このままでは一般性に欠けすぎていますからね。実は、Ruby の拡張モジュールを作ると、自分で定義した好きな構造体を Ruby オブジェクトに割り当てることができます。
 …ここまでの話で、ピンと来ましたか?
 つまり、Ruby のオブジェクトと異なるデータ構造 (struct RObject 以外) を使うということは、Ruby の Object クラスが備えたクラスメソッド new を使うことができない、ということなのです。…となれば、することは一つ。自分で、new メソッドを作ってしまうのです!

 えらく長い「引き」でした。
 いいかげん、実際のコードを見てみましょうか。

#include "ruby.h"

struct Sample {
  int n;
};

static void sample_free ( struct Sample * ptr )
{
  ruby_xfree( ptr );
}

static VALUE sample_new ( VALUE klass )
{
  struct Sample * ptr;
  VALUE obj;

  obj = Data_Make_Struct( klass, struct Sample,
                          NULL, sample_free, ptr );
  ptr->n = 100;

  return obj;
}

static VALUE sample_foo ( VALUE self )
{
  struct Sample * ptr;

  Data_Get_Struct( self, struct Sample, ptr );

  return INT2FIX( ptr->n );
}

void Init_sample ( void )
{
  VALUE c;

  c = rb_define_class( "Sample", rb_cObject );
  rb_define_singleton_method( c, "new", sample_new, 0 );
  rb_define_method( c, "foo", sample_foo, 0 );
}

Sample というクラスを作り、new メソッドを定義しました。
 まず、この Sample というクラスが使用する C 言語でのデータ構造、struct Sample を定義します。先程説明したとおり、これは自分の好きなように定義することができます。
 ちょっと関数を飛び越えて、sample_new 関数を見てください。ここではまず、struct Sample * 型の変数 ptr と、Ruby のオブジェクト型 VALUE の変数 obj を宣言していますね。そして注目すべきは次の行です。Data_Make_Struct マクロで、C 言語のデータ構造から Ruby オブジェクトを生成しています。…これだけなんです。これだけで、Ruby のオブジェクトが生成されます。
 このマクロは、第二引数 (ここでは struct Sample) で与えられた型のメモリを確保し、それを第五引数で与えられた同じ型の変数 (ここでは ptr) に格納します。また、第一引数 (ここでは Ruby オブジェクトの klass) に与えられたクラスの Ruby オブジェクトを生成し、それに第三引数 (ここでは与えていません) をガベージコレクタ用のハンドラ、第四引数 (ここでは sample_free()) を解放用のハンドラとして割り当てます。

Ruby オブジェクトを生成する
Data_Make_Struct(CLASS,TYPE,GC,FREE,PTR)
CLASS - Ruby のクラスオブジェクト
TYPE - オブジェクトに対応する C のデータ型
GC - ガベージコレクタで使われるマーク用関数
FREE - データ領域解放時に使う関数
PTR - TYPE * 型のポインタ

 なお、Ruby のメソッドに割り当てられた C の関数には、第一引数に自分自身を示す Ruby オブジェクト (self のこと) が渡されます。sample_new は singleton メソッド (クラスメソッド) として定義したので、クラスメソッドにおける self、つまり自分自身 (クラスオブジェクト) が渡されます。もちろん、Data_Make_Struct の第一引数に渡すオブジェクトがクラスオブジェクト以外であってはなりません。現在のところチェックは行われていないようですが、どんな挙動を示すかわかったものではありませんからね。おそらく、コアダンプすることになるでしょう。
 メソッドの定義 (メソッド foo) などに関しては、省略します。

 これで、次のようなコードを書くことができます。

require "sample"

sample = Sample.new
p sample.foo

 さあ、拡張モジュールを作る方法は憶えていますか? extconf.rb を書いて、ruby で実行するのです…が、ここでは違う方法を紹介します。ワンライナーで書いてしまう方法です (やっていることは変わらないのですけれど)。なお、ビルドするディレクトリには C のソースコード以外置かない ようにしましょう。create_makefile メソッドは自動的に全ての .c ファイルを取り込んでしまいます。

$ ls
sample.c

$ ruby -e 'require "mkmf"; create_makefile( "sample" )'
creating Makefile

$ make
gcc ...

$ ruby -e 'require "sample"; sample = Sample.new; p sample.foo'

この実行結果は載せないことにします。
 ぜひ自分で試して、感動してみてください。Ruby と C 言語が完全に協調して動いているのですよ? かなり驚異的なことではないでしょうか。
 え!? Prolog とかで体験した? え、あ、う…そ、そうなんですか…?

 いかがだったでしょうか。次は、より詳しくメソッドについて見ていきます。

4. メソッドの定義

 オブジェクトを作ったならば、そのオブジェクトがどのように振る舞うか、という事を定義しなければなりません。この「オブジェクトの振る舞い」は メソッド によって決定します。
 メソッドには二種類あり、「クラスメソッド (特異メソッド)」と通常の「メソッド」があります。クラスメソッドはクラスオブジェクトそのものに関連づけられたメソッドで、オブジェクトに依存せず実行することができます。逆に、どのオブジェクトにも依存しないような事しかできません。一方、通常のメソッドは処理対象となるオブジェクト (レシーバ といいます) のコンテキストで処理されます。
 例外として、トップレベルで宣言されたメソッドは、どこからでも呼び出すことのできるメソッド (ほとんどクラスメソッドと同じ) になります。この場合にコンテキストは存在しません。
(ちなみに、このメソッドは Kernel モジュールの private メソッドになります。)

 さて、このメソッドですが、それぞれを定義するには次の関数を使用します。

クラスメソッド (特異メソッド) を定義する
rb_define_singleton_method(OBJ, NAME, FUNC, ARG)
VALUE OBJ - メソッドが定義されるオブジェクト
const char *NAME - メソッド名 (文字列)
VALUE (*FUNC)() - メソッドの本体
int ARG - メソッドがとる引数の個数

メソッドを定義する *1
rb_define_method(CLASS, NAME, FUNC, ARG)
VALUE CLASS - メソッドが定義されるクラス
const char *NAME - メソッド名 (文字列)
VALUE (*FUNC)() - メソッドの本体
int ARG - メソッドがとる引数の個数

 引数の名前を見ればわかりますが、rb_define_singleton_method 関数はクラス以外にも適用することができます。その場合、指定されたオブジェクトに固有のメソッド、ということになります。

 それでは、Ruby におけるいろいろなメソッドを拡張モジュールでどのように書けばいいのか、例となるソースコードを出しながら説明していきます。

class Sample
  def foo ()
  end
end

VALUE sample_foo ( VALUE self )
{
  return Qnil;
}

...

v = rb_define_class( "Sample", rb_cObject );
rb_define_method( v, "foo", sample_foo, 0 );

 まずは引数が無い場合です。
 引数を取らないので、rb_define_method 関数の ARG には 0 を指定します。また、対応する関数 sample_foo は、self オブジェクトのみを引数として受け取るようにします。これで OK です。

class Sample
  def foo ( a )
  end
end

VALUE sample_foo ( VALUE self, VALUE a )
{
  return Qnil;
}

...

rb_define_method( v, "foo", sample_foo, 1 );

 今度は引数が一個の場合です。v は最初の例と同じく Sample クラスを表すと考えてください。
 引数を取るので、rb_define_method 関数の ARG には 1 を指定します。二個以上の場合は、2 や 3 を指定するようにします。そして、対応する関数 sample_foo には、self の後に引数として受け取るオブジェクトを並べて書いていきます。
 個数の対応を間違えないようにしましょう。

class Sample
  def foo ( a, b=nil )
  end
end

VALUE sample_foo ( int argc, VALUE * argv, VALUE self )
{
  return Qnil;
}

...

rb_define_method( v, "foo", sample_foo, -1 );

 次はデフォルト引数があるような場合です。Ruby では引数の個数を定めるのと同時にデフォルト引数の値を指定することができますけれども、C 言語ではそのようなことができません。そこで、引数の個数として -1 を指定します。引数の個数として -1 が指定されていると、Ruby に渡された引数は C の配列として関数へ渡されるようになります。
 面倒ですが、sample_foo 関数では argc をチェックして何個の引数が渡されたかを調べ、個数が足りないようであればデフォルトの値を設定するようにします。
 なお、この作業を補助するため、次のような関数が提供されています。

渡された引数を解析する
rb_scan_args(ARGC, ARGV, FORMAT, ...)
int ARGC - 引数の個数 (argc)
VALUE *ARGV - 引数 (argv)
const char *FORMAT - フォーマット文字列
正規表現で書けば /\d(\d(\*)?)?/ となる
一文字目は必須な引数の数
二文字目は任意な引数の数
三文字目の * は余る引数を許すかどうか
(VALUE *) ... - 格納先のアドレス

使い方は、おいおい紹介していくことにしましょう。

class Sample
  def foo ( *arg )
  end
end

VALUE sample_foo ( VALUE self, VALUE arg )
{
  return Qnil;
}

...

rb_define_method( v, "foo", sample_foo, -2 );

 最後は、Ruby の配列として引数を受け取るような場合です。このようなときは、引数の個数として -2 を指定すれば Ruby の配列 (Array オブジェクト) として arg にその引数の配列オブジェクトが引き渡されます。
 どのようにこのオブジェクトを操作するかは、次にでも説明します。今はちょっと待ってくださいね。
 また、ここではすべて rb_define_method 関数で説明しましたが、rb_define_singleton_method 関数でもまったく同じです。ただ、sample_foo 関数など、メソッドの実体となる関数には self ではなく、レシーバオブジェクトが引き渡されるようになります。たとえば、クラスメソッドならばそのクラスオブジェクトとなります。
 前後していますが、前回の sample_new 関数ではそのクラスオブジェクトが Ruby オブジェクトの生成時に使われています。見直してみてください。

 というわけで、メソッドの宣言について説明しました。
 今のままでは Ruby オブジェクトを操作できないために何もできません。そこで、次回は C 言語から Ruby オブジェクトをどのように操作するかを説明します。


*1 rb_define_method 関数で CLASS に Qnil が指定された場合は、Object クラスへ定義されるようになっています。ここで、CLASS に Class や Module 以外の Ruby オブジェクトを指定した場合は、その Ruby オブジェクトの生成に使われたクラスへメソッドが定義されるようです。…が、そのようなわかりにくいコーディングはしない方が良いでしょう。また、将来、この仕様は変更されるかもしれません。

5. C 言語から Ruby オブジェクトを見る

 拡張モジュールは C 言語で書かれています。しかし、拡張モジュールは Ruby から呼び出されます。そのため、Ruby とちゃんとコミュニケーションを取るような拡張モジュールを書こうとした場合、C 言語から Ruby の変数へどのようにアクセスするのか、その方法を知っておかなければなりません。
 さて、これまでに何度か出てきたとおり、C 言語から Ruby の変数を見ると、VALUE 型の変数として見ることができます。では、この VALUE という型は、実際には何を示しているのでしょうか? 何かの構造体なのでしょうか、それとも、単なる数値なのでしょうか?
 それを明らかにするため、Ruby のソースコードを見てみますと…。

typedef unsigned long VALUE;

となっています。
 …あれ? と思った方もいるでしょう。どうして、数値だけであんなに複雑なオブジェクトを操作することができるのか? と。
 これは、世の中に驚異的でトンデモな圧縮技術があるとか、そういうわけではありません。実は、この VALUE は unsigned long として宣言されていますが、ほとんどの場合、ポインタにキャストして 利用されるのです。
 しかし、あらゆるものをポインタとして扱ってしまうと、効率が落ちてしまいます。文字列などはどうしようもありませんが、整数演算など、足し算をするたびに整数オブジェクトが生成されていたのでは使い物にならないでしょう。
 そこで、Ruby ではとても巧妙な方法でこの問題を解決しています。先程 VALUE はポインタとして使うと書きましたが、一部では、その本来の意味である整数として扱われているのです。
 …ここまでで、再び、あれ? と思った方がいるでしょう。まず第一に、ポインタと整数は幅が一致しないかもしれない、ということ。そして第二に、ポインタと整数の区別はどこで行っているのか? ということ。
 第一の「ポインタと整数の幅が一致しないかも?」というのは、次のような方法で解決しています。

#if SIZEOF_LONG != SIZEOF_VOIDP
---->> ruby requires sizeof(void*) == sizeof(long) to be compiled. <<----
#endif

そうです、void * 型と long 型の幅が異なる場合は、コンパイルができない のです。つまり、Ruby が動く環境では、常に long と void * は等価 (ビット列として) であると限定されます。これで最初の問題は解決しました。(騙されたと思うかもしれませんが…)
 第二の「ポインタと整数はどうやって区別するのか?」というのは、次のコードを見れば理解することができるでしょう。

#define FIXNUM_FLAG 0x01
#define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG))

この INT2FIX というマクロは、C 言語の int 型変数を Ruby 整数 (Fixnum オブジェクト) に変換するためのものです。見ると、1 ビット左へシフトし、最下位ビットを立てています。
 わかりましたでしょうか? つまり、Ruby が VALUE 型の変数を整数か、それともオブジェクトへのポインタであるかを判断するのは、VALUE 型変数の 最下位ビットが 1 であるかどうか、で行っているのです。それで判別できるのか? と思うかもしれませんが、通常、奇数単位でメモリブロックが確保されるようなことはありません。偶数は常に最下位ビットが 0 ですから、このメモリ確保時の特性を使い、整数とオブジェクトの識別を行っているのです。
 これで二番目の疑問も解決したことでしょう。(キワモノな設計ですが…)

 次に、Fixnum オブジェクト以外の Ruby オブジェクトが、C 言語からいったいどのように見えるのか、説明していきましょう。
 まず、これら Fixnum 以外の Ruby オブジェクトに対しては、VALUE がポインタになっていると書きました。では、いったいどこを指し示しているのでしょうか? ポインタ、というのですから、どこかを指し示す (ポイント) していなければなりません。
 実は、この VALUE が示す先は、オブジェクトによって型が変わります。たとえば、String オブジェクトならば RArray 構造体を、Regexp オブジェクトならば RRegexp 構造体を、Array オブジェクトならば RArray 構造体を、という具合です。
 普通、拡張モジュールを作るときにアクセスする機会があるのは RString 構造体と RArray 構造体くらいです。ですので、ここではこれら二つの構造体についてのみ、説明することにします。他のオブジェクトは、それぞれ対応した (適切な) 構造体を使っているんだな、と思っていてください。

struct RString {
    struct RBasic basic;
    long len;   /* 文字列長 */
    char *ptr;  /* 文字列バッファ */
    VALUE orig; /* 参照している文字列オブジェクト */
};

struct RArray {
    struct RBasic basic;
    long len;   /* 配列の要素数 */
    long capa;  /* 配列のバッファ長 */
    VALUE *ptr; /* バッファ */
};

どちらにも共通して現れる RBasic 構造体は、オブジェクト情報を管理するためのヘッダーだと考えてください。また、Ruby のソースコードにはコメントは付いていません。ここでは、理解を助けるために若干書き換えています。それぞれのメンバの意味はコメントを見ればすぐにわかるでしょう。
 これらの構造体に VALUE 型の変数を変換するとき、次のようなマクロが用意されています。

RSTRING(V)
VALUE 型の変数 V を struct RString * に変換する。チェック無し。
たとえば、String のインスタンスであるとわかっている変数 obj が示している文字列の長さを得たい場合は、
RSTRING(obj)->len
とする。
RARRAY(V)
VALUE 型の変数 V を struct RArray * に変換する。チェック無し。
詳細は上の RSTRING と同じ。

これらを使えば、Ruby 変数の内容を 参照 することができます。ヘタに書き換えてしまった場合、Ruby のコアが混乱してしまう可能性があるので、不用意には書き換えない方がいいでしょう。ただ、ptr が示しているバッファの内容はある程度書き換えてしまっても大丈夫なようです。
 ここでは他の型のオブジェクトは説明しませんが、主に、Ruby で提供される関数 (API) で処理を行います。組み込みメソッドなど、ほとんどのものを C 言語から直接呼び出すことができます。また、Ruby のメソッドを呼び出すこともできるので、操作の面で劣るようなことはありません。
 今回は VALUE の内部構造を解説するだけで終わってしまいましたが、次回からはそれらオブジェクトをどのように操作していくのか、という話をしていきたいと思います。まずは、一般的なメソッド呼び出しから説明していきますね。

6. C から Ruby のメソッドを呼ぶ

 拡張モジュールからでも、Ruby のメソッドを呼び出すことができます。以前に「メソッドの定義」ということで、メソッドをどのように定義するか、ということを説明しましたけれども、今回はその逆になるわけです。以前の記事は「C 言語で記述したメソッドを Ruby からどうやってコールするか?」ということを説明したのですが、今回は「Ruby で記述したメソッドを C 言語からどうやってコールするか?」ということになります。
 まあ、Ruby でコールするのは簡単です。

object.method

のようにすればいいのですからね。
 さて、C 言語で Ruby のメソッドを呼び出すには何が必要でしょうか? まず、レシーバとなるオブジェクトが必要ですよね。グローバル関数などは Object のクラスメソッドとして実装されていますから、どのオブジェクトのメソッドを呼び出すか? ということで、レシーバとしてクラスオブジェクトが必要になります。それと、メソッド名が必要ですよね。名前のわからないメソッドは呼び出せません。あとは、必要であれば引数、といったところでしょうか。
 Ruby のメソッドを呼び出すには、次の関数が用意されています。

Ruby のメソッドを呼び出す
rb_funcall ( VALUE recv, ID method, int argc, ... )
VALUE recv
レシーバオブジェクト
ID method
メソッドID
int argc
引数の個数
(VALUE*) ...
引数

任意のメソッドを呼び出す場合、この関数が使われます。そして、これ以外の関数は使われません。つまり、この関数の使い方さえ憶えておけば、C 言語から Ruby のオブジェクトを完璧に操作することができるわけです。
 recv に引き渡すのは、前回に説明した VALUE 型の Ruby オブジェクトです。
 さて、method にはメソッド ID を引き渡すのですが、初めて聞く言葉ですよね。このメソッド ID というものは「識別子に対応する整数」で、取得には rb_intern() 関数を用います。

文字列から ID を得る
rb_intern ( const char * name )
const char * name
識別子

「どうして ID を指定しなければならないんだ、文字列じゃダメなのか?」と思うかもしれません。しかし、Ruby の内部ではメソッドや変数などはすべて Symbol で管理されています。この Symbol は整数値であるため、メソッドを呼び出すときは文字列から対応する Symbol、すなわちメソッド ID に変換しなければならないのです。数値で表されていた方が、効率などの面で有利ですしね。
 何度も呼び出すようなメソッドがある場合、予め rb_intern() でメソッド ID を求めておくと、効率がよくなります。やはり、対応する識別子を探すのはそれなりに時間がかかりますからね。
 では、次の Ruby コードを C 言語で書きなおしてみましょう。

ary = Array.new()
for i in 0...10 do
  ary.store( i, i*i )
end

これを C 言語で書くと、次のようになります。

VALUE ary;
int i;

ary = rb_ary_new();
for( i = 0; i < 10; ++i )
  rb_funcall( ary, rb_intern("store"), 2, INT2FIX(i), INT2FIX(i*i) );

C 言語で書く意味などないようなコードですが(笑)、まあ、サンプルということで。
 最初の rb_ary_new() という関数は、Array オブジェクトを生成するためのものです。これは Ruby 組み込みの関数として提供されています。今回取りあげた rb_funcall() はその後で使われています。二つの引数を取り、ary オブジェクトの store メソッドを呼び出しています。
 実は、ary が Array のオブジェクトであるとわかっているならば、rb_ary_store() 関数を呼び出すことで同じ事を行うことができます。しかし、ary が Array オブジェクトではなかったりするような場合、このように rb_funcall() を使って store メソッドを呼び出さなければなりません。

 今回は C 言語から Ruby オブジェクトのメソッドを呼び出す方法について説明しました。次回は Ruby に組み込まれたオブジェクト操作について説明していきます。

メインメニューDIARYINFORMATIONLABORATORYLINK