Ruby.EXT.4

ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙


目次


Ruby と C を繋ぐ ID

 ひとつ前のページ で、VALUE 型を通して Ruby のデータを C 言語で利用する、ということを説明しました。今度は、ID というものを通して Ruby 側で定義されたメソッドを利用する方法について説明します。
 ここで、メソッドについて簡単に復習しておきましょう。毎回、説明の内容が変わっているような気がするかもしれませんけれど、きっとそれは気のせいです(笑)。
 メソッドというのは、オブジェクトへ向けたメッセージのようなものです。メソッドによって、オブジェクトに「末尾の空白を取り除いてくれ」とか「データを全部消してくれ」などといったメッセージが送られ、その指示に従ってオブジェクトが動作するのです。Ruby では、メソッドの呼び出しを次のように記述しました。

object.method(param)

この例は、object という名前のオブジェクトに対して method という名前のメソッドを param というパラメータで適用する、ということを示しています。

 では、拡張モジュールから Ruby のメソッドを呼び出すにはどうすればいいでしょうか?
 それには、Ruby API の rb_funcall() を使用します。

VALUE rb_funcall(VALUE obj, ID id, int argc, ...)
obj メソッドを適用するオブジェクト。
id メソッドの識別子に割り当てられた ID 値。
argc パラメータの個数。
返値 実行結果。

では、文字列オブジェクト "10" に対して to_i() メソッドを適用するときは、次のように書くのでしょうか…?

VALUE strobj = rb_str_new2("10");
rb_funcall(strobj, "to_i", 0);

これは誤りです。メソッドの識別子は ID 型なので、文字列 (char*) を指定することはできません。ちなみに、rb_str_new2() という Ruby API は文字列オブジェクトを生成するためのものです。

 実は、Ruby 内部において、メソッド名などの識別子 (たとえば to_i) はすべて数値のインデクスを使って管理されています。文字列で持っていると、指定されたメソッドをオブジェクトが持っているかどうかを検索するのに時間がかかってしまいますからね。C 言語などを熟知した方は「実行前にメソッドはバインドされているから検索する必要なんて無いんじゃないの?」と思うかもしれませんが、Ruby では名前とクラスが一対一に対応していないので、実行時に、オブジェクトに指定されたメソッドが存在するかどうかを毎回調べなければならないのです。
 まあ、そんなわけで、Ruby のメソッドを呼び出すときは、メソッド名 (識別子) に対応する ID 値を予め調べておかなければなりません。もちろん、そのための API も用意されています。

ID rb_intern(const char * name)
name 識別子を表す文字列。
返値 識別子に対応する ID 値。

この API を使い、上の例は以下のように修正されます。

VALUE strobj = rb_str_new2("10");
rb_funcall(strobj, rb_intern("to_i"), 0);

なお、識別子は、一度 Ruby に登録されれば、以降で変更されることはありません。そのため、頻繁に使う識別子は予めキャッシュしておくのが利口です。
 たとえば、

int i;
for( i = 0; i < 10000; ++i ) {
  rb_funcall(obj, rb_intern("foo"), 0);
}

とするよりも、

int i;
ID id_foo = rb_intern("foo");
for( i = 0; i < 10000; ++i ) {
  rb_funcall(obj, id_foo, 0);
}

とした方が早くなります。

(2002/12/20)

ハッシュを使ってみよう

 以前の「ハッシュにアクセスする」で、ハッシュテーブルの情報を取り出すには「Ruby メソッドを拡張モジュールから呼び出す必要がある」と書きました。
 ここまでに説明したことを使い、早速、試してみることにしましょう。

#include "ruby.h"

VALUE test_foo ( VALUE self, VALUE arg )
{
    VALUE keys;
    
    keys = rb_funcall( arg, rb_intern("keys"), 0 );
    rb_p( keys );
    
    return Qnil;
}

void Init_test ( void )
{
    rb_define_global_function("foo", test_foo, 1);
}

この拡張モジュールでは、グローバルメソッド foo() が定義されます。このメソッド foo() は、ハッシュを受け取り、キーの一覧を出力するものです。Ruby API の rb_p() が使われていますが、これは Ruby のグローバルメソッド、p() と同様の働きをするものです。
 extconf.rb は次のように書きます。

require 'mkmf'
create_makefile('test')

拡張モジュールのコンパイルが終わったら、次のコードで動作を確認してみましょう。

require "test.so"

foo(ENV)

定数 ENV は、環境変数をハッシュとして扱うのに用いられます。

$ ruby test.rb
["ALLUSERSPROFILE", "APPDATA", "COMMONPROGRAMFILES", "COMPUTERNAME",
...中略...
OMAIN", "USERNAME", "USERPROFILE", "VFONTCAP", "WINDIR", "_"]
$

実行してみると、環境変数のキー名一覧が表示されます。

 Ruby API で直接操作できない場合や、ユーザー定義のメソッドを呼び出すような場合、このように rb_funcall()rb_intern() を組み合わせて実行します。

(2003/01/25)


ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙