RubyのC API
始める前に
この手引きを完全に理解するためには、C言語はそこそこ、Rubyは かなり 慣れ親しんでいるとよいでしょう。
RubyのC APIを使うのに発展的なC言語の概念は要りませんが、APIは膨大で大半が文書化されていません。APIを使い始めだすと、いつの日にか、はっきりしない関数やマクロの挙動を調べるためにRubyのソースコードを探ることになるでしょう。Rubyのソースはそこそこ洗練されたC言語で書かれているので、すいすい読めはするでしょうけれども。
C APIは、普通のRubyのコードよりもデカくてゴチャっとした書き方だと見なせます。とはいえ、簡素で明快なRubyの様式は、ひとたびAPIの言葉に翻訳されるとかなり非直感的になることがあります。Rubyの内部的な仕組みと設計の背景にある思想に裏打ちされたしっかりとした直感があれば、長い旅路のさなかでも正しいAPI関数へと進み続けられるでしょう。
分岐点
公式のRubyインタプリタはC言語で書かれています。 Rubyでできるあらゆることは、RubyのC APIを呼び出す関数を使ってもできます。 いったいなぜそんなことをするのでしょうか? もっともな理由が2つあります。
- 意匠を凝らしたC言語ないしC++のアプリケーションを書いている最中で、 Rubyの動的な柔軟性をコードのいくつかの箇所に使って、 効果を引き出そうとするとき。 アプリケーションの内部でRubyインタプリタを実行でき、 Rubyのコードの結果をAPIで受け取ることができます。
- 意匠を凝らしたRubyのアプリケーションを書いている最中で、
C言語(もしくは既にあるC言語のライブラリ)の速度と能力を
コードのいくつかの箇所に使って、
効果を引き出そうとするとき。
APIを使えば、RubyにC言語のコードをさらけ出すことができます。
そうしてできた特別なライブラリをコンパイルするとRubyから
require
できます。
目標によってC言語のコードの組み立てかたは変わってきます。
C言語にRubyインタプリタを組込みたければ、C言語でRubyを実行するをお読みください。
コンパイルされたC言語のライブラリを require
したければ、RubyでC言語を実行するをお読みください。
どちらかを読み終えたら、ここに戻ってきてAPIについて学びましょう。
Eval
ちゃちゃっと雑にCでRubyを動かすには、 eval
します。
これはやりたいことをするためのAPI関数が見つからないときは選択肢に入ります1。rb_eval_string_protect()
はRubyのコードの結果を返し、何か例外が発生したときはゼロではない値を state
に設定します。VALUE
はあらゆるRubyのオブジェクトのためのCのデータ型です。次の節で後述します。
state
がゼロではないとき、result
は nil
を表す VALUE
になっているでしょうから、そのときは例外に対処しなければいけません。代わりに rb_eval_string()
を使うこともできます。この関数は引数
state
を取らず、通常の方法で例外を発生させます。これら2つの場合での制御方法については例外の節を参照してください。
Rubyでのeval
とは異なり、これらの関数は何かしらをrequire
したときに、独立したbindingのようなものの中で文字列を評価します。
そのため文字列中の局所変数は他所から参照できませんし、逆もまた然りです。
しかし、Rubyで eval
するときのように、
これらの関数を使うことはあまりよくありません。
パーサが起動されなければいけないのが非効率ですし、
Cで書くことの利点が失われています。
Rubyのメソッドを呼びたいだけであれば、
もっといい方法を後述します。
VALUE
先に進む前に、
VALUE
について理解しなければいけません。
VMの内部をめちゃくちゃにしてしまう危険があるので、
APIからは直接Rubyのオブジェクトを扱えません2。
その代わり、CのコードはRubyのオブジェクトへの ポインタ
を保持したり受け渡したりします(Rubyの変数がオブジェクトへのポインタを持つのと同じです)。
ポインタはさまざまなAPIの関数やマクロに渡すことができ、
そのためRubyのオブジェクトに安全にアクセスしたり操作したりできます。
VALUE
はAPIで定義されたCの型で、このポインタを扱います。
おそらく最も頻繁に湧いてくる疑問は次のようなものでしょう。
「この VALUE
は正しい型なのだろうか?」
この疑問に答えるマクロは2つあり、
そのどちらもT_
定数を引数に取ります。
この定数は比較したい型に対応するRubyのクラスです。
例えば T_STRING
や T_ARRAY
などです。
この検査はサブクラスでも同様にはたらきます。
Array
のサブクラスであることを確認したいときは T_ARRAY
を使いますし、
Object
のサブクラスであることを確認したいときには T_OBJECT
を使います3。
つまり、この検査は is_a?
と同じではない ということでもあります。
Rubyのありとあらゆるものについて is_a? Object
であるにせよ、
T_OBJECT
に対する検査は、
他にもっと適した定数がないときにのみ真となります。
特定のクラスについては、前述したものより少し便利なマクロが使えます。
いくつかの型を取りうる VALUE
を扱いたいときは、前述のマクロはちょっと不恰好です。そんな場合は TYPE()
マクロを使って T_
定数を取得し、switch
文で制御できます。
定数
ほとんどの標準的なRubyの定数はAPIで大域的な VALUE
が定義されています。したがってそうした定数を使うのにAPIの呼び出しは要りません。モジュールは rb_m
で前置されます。例えばrb_mKernel
です。クラスは rb_c
で前置されます。例えばrb_cObject
です。Exception
のサブクラスは rb_e
で前置されます。例えばrb_eRubtimeError
です。標準的なIOストリームは
rb_
で前置されます。例えばrb_stderr
です。nil
, false
, true
は Q
で前置されます。例えば
Qnil
です4。便宜上、 Qfalse
はCの偽値 (0
) でもあります。
変換
いくつかのRubyのクラスはCの型に対応します。 この対応する組み合わせは、CとRubyの間のデータのやりとりにうってつけです。
Fixnum
Rubyの Fixnum
はCの long
に対応します。
FIX2LONG()
マクロにより、 long
を Fixnum
に変換できます。
より小さいCの型については、
FIX2UINT()
, FIX2INT()
, FIX2SHORT()
があります。
しかし、これらのマクロは変換時に数値が大きすぎて収まらない場合は RangeError
を生じます。
逆向きの変換もできて、
LONG2FIX()
は long
に加えて long
より小さい全てのCの整数型からの変換に使えます。
Bignum
Rubyの Bignum
は Fixnum
より大きいあらゆる整数に使えます。例えば long long
が必要なときとかです。rb_big2()
と rb_big2u()
を使えば、 Bignum
からそれぞれ long long
や
unsigned long long
に直せます(直せなければRangeError
が発生します)。
逆向きの変換については Numeric を参照してください。
Float
Rubyの Float
はCの double
に対応します。
RFLOAT_VALUE()
マクロを使えば、 Float
から double
が得られます。
逆向きの変換については Numeric を参照してください。
Numeric
たくさんの “NUM” マクロがあり、
ダックタイピングっぽい使いかたをしています。
これらのマクロはCの型を適切と思われるRubyの Numeric
のサブクラス(のインスタンス)に変換します。
INT2NUM()
はint
からの変換用UINT2NUM()
はunsigned int
からの変換用LONG2NUM()
はlong
からの変換用ULONG2NUM()
はunsigned long
からの変換用LL2NUM()
はlong long
からの変換用ULL2NUM()
はunsigned long long
からの変換用DBL2NUM()
はdouble
からの変換用
そして、逆方向のマクロもあります。
これらのマクロは、あらゆる Numeric
な数値から欲しいCの型の値への変換を試みるものです。
値が範囲に収まらなかったときは RangeError
を発生させますし、
暗黙の数値の変換ができなかったときは TypeError
が発生します(なので Numeric
ではないオブジェクトを渡しても大丈夫です)。
NUM2CHR()
はchar
からの変換用(unsigned char
からでも変換できます)NUM2SHORT()
はshort
からの変換用NUM2USHORT()
はunsigned short
からの変換用NUM2INT()
はint
からの変換用NUM2UINT()
はunsigned int
からの変換用NUM2LONG()
はlong
からの変換用NUM2ULONG()
はunsigned long
からの変換用NUM2LL()
はlong long
からの変換用NUM2ULL()
はunsigned long long
からの変換用NUM2DBL()
はdouble
からの変換用
これらのマクロについて 特に 注意すべき点は、符号なしの型に変換に変換する どの
マクロも、負値を渡したときに例外が発生しないということです(驚くべきことにバグではありません)。NUM2CHR()
にはもう2つ妙なところがあります。変換前の値が int には大きすぎるときに RangeError
のみ生じることと、文字列を渡したときに
TypeError
を発生させず最初の文字の数値を返すということです。
変換が安全だとわかっていれば、 前の節のマクロのほうを使うべきです。 なぜなら範囲の確認を省けるためです。
String
Rubyの String
はだいたいCの char*
に対応します。もっとも簡素なマクロは StringValueCStr()
です。このマクロは String
からnull終端付きの char*
を返します。ただしこれには問題があって、Rubyの String
が
nullを含むかもしれません。そのような場合には StringValueCStr()
は ArgumentError
を発生させます!その代わりに StringValuePtr()
マクロと RSTRING_LEN()
マクロを使えば、それぞれ(終端が付いていない可能性がある)char*
とその長さの long
値を取得できます。
逆に、null終端付きの char*
からRubyの String
への変換には、rb_str_new_cstr()
が使えます。
そして、もし String
にnullを含めたいときは、rb_str_new()
を使います。
rb_str_new()
は char*
と文字列の長さ(型は long
)を引数に取ります。
これらの文字列のエンコーディングは ASCII-8BIT
なのですが、Ruby側のコードで望んだものではないこともあります。
そんなときは文字列の VALUE
を rb_str_export_locale()
に渡して、自分のロケールのエンコーディングでの
VALUE
を取得できます。
もっと複雑な文字列を構築したければ、printf
のような関数
rb_sprintf()
があります。この関数では通常の変換での全ての指定子だけではなく、APIで定義された指定子 PRIsVALUE
も使えます。PRIsVALUE
は対応する VALUE
を引数に取ります。この変換指定子はオブジェクトに to_s
メッセージを送って文字列に置き換えます。+
フラグを加えることで inspect
を使った結果の文字列に置き換えることもできます。
この独自の指定子はAPIにあるあらゆる printf
っぽい関数で使えるでしょう。PRIsVALUE
は i
変換指定子を間借りすることで実現されているので、Rubyに「それって実は VALUE
なんじゃないか」と勘違いさせないように、int
を文字列にするときは d
を使うべきです。
Symbol
APIではRubyの Symbol
に対応するCの型 ID
が定義されています。Rubyが Symbol
をメソッドや変数名として受け渡しするように、多くのAPI呼び出しでメソッドや変数の名前としてID
を使うようにしています。Symbol
と
ID
を変換するには、 SYM2ID()
と ID2SYM()
マクロを使います。Symbol
ではなくCの文字列 char*
と相互に変換したいときもあるでしょう。char*
から ID
を取得するためには rb_intern()
を、その逆向きでは
rb_id2name()
を使います。
多くのAPI関数が ID
を必要としていますが、適切な ID
が手元にないことも沢山あるでしょう。
そのため、代わりに char*
を引数に取って自動で rb_intern()
してくれる関数も沢山あります。
これらの関数はより読みやすく、 rb_intern()
の呼び出しでのオーバーヘッドは無視できる程度なので、本手引きでは可能な限り char*
バージョンのAPI関数が使われる傾向にあります。
特定のCの文字列をAPIの呼出しで頻繁に使う場合は、ID
に変換して保管しておき、ID
バージョンの関数を使うことでいくらかの効率性の向上を確かめられるかもしれません(Rubyのヘッダから自分で見つける必要がありますが)。
send
この節には直接Rubyのメソッドを呼び出すAPI関数が含まれます。rb_eval_string()
などより、できるだけ常にこれらの関数を贔屓すべきです。これらの関数では構文解析の過程が飛ばされますし、いくつかのコンパイル時検査が免除されるのでより速いのです。
一番簡単な方法はオブジェクトにメソッドをこのように送ることです。
これは大雑把には次のRubyコードと同じです。
最初の引数はレシーバです。その次はメソッド名用のID
です。3つ目の引数はメソッド引数の数です。この引数が必要なのはrb_funcall()
がvarargs関数だからです。それから実際メソッド引数に行き着きます。
これに代えて4つ目の引数が引数のCの配列を指すVALUE*
になっているrb_funcallv()
を使うことができます。これにもRubyでのpublic_send
のような派生、rb_funcallv_public()
があります。
ブロックを渡す
Proc
をブロックとしてメソッドに渡したければ話は早いです。
関数はほぼrb_funcallv()
と同じですが後ろにprocが付きます。
ブロック用のprocがなければ、ブロックを表現する何らかの類のC関数を定義する必要があります。それからrb_funcallv()
の異なる派生形で、ブロック用に2つ引数が追加されたものがあります。
rb_block_call()
への最後の引数はブロック関数のスコープの外側にある値を渡すのに便利ですが、この例ではその必要はありません(なのでnil
にしています)。
また、1つの値だけがyieldされることを確信していない限り、最初の引数を使うことはお勧めしません。
いつでも全ての引数をargv
から手に入れられるので、危うきに近寄らずともいいじゃないですか5。
組み込みの機能
多くのRubyの組み込みクラスには、その中でも飛びっきり便利なメソッド用に定義されたAPI関数があります。
それらを使えば、rb_funcall()
を使ってばかりで冗長になるのを避けられたり、より良いコンパイル時の検証がもたらされるかもしれません。
関数があまりにも多すぎて一覧にするにはここは狭すぎるので、ヘッダファイルruby/intern.h
を眺めることを推奨します。
関数はおおよそrb_(クラス)_(メソッド)
のように命名されていて少なくとも1つのVALUE
引数(レシーバ)を取ります。
例えばrb_ary_pop()
はArray#pop
用ですし、rb_obj_dup()
はObject#dup
用、といった具合です。
require
APIには何らかのRubyコードをスクリプトから読み込むための、require
と等価なものがあります。
require
するときは例外が発生し得ます。
対処方法については次節を読んでください。
スクリプトを複数回読み込みたければ、load
用の関数もあります。
ちょうどRubyのload
と同じように、こうした関数は読み込まれたコードを匿名のモジュールに包んで大域名前空間を保護するのに使えます。単に非ゼロの値を2つ目の引数に渡せばよいのです。
例外
raise
例外を投げるには以下を使ってください。
1つ目と2つ目の引数は例外クラスと文言で、Rubyのraise
のような感じです。
大きな違いは、文言が(ちょうどrb_sprintf()
のような)書式文字列である点で、有用な文言をより簡単に構築できます。
またrb_exc_new_cstr
、rb_exc_new
、rb_exc_new_str
を直接使って例外オブジェクトを構築することもできます。これら全ては例外クラスを1つ目の引数に受け取り、そうしてそれぞれの関数に対応する文字列に対してはたらきます。すなわち例外はそれぞれnull終端文字列、非null終端文字列、String
オブジェクトを使って構築されます。それからrb_exc_raise
で例外オブジェクトを投げられます。
rescue
APIを使って例外を捕捉するにはいくつか方法があります。
全ての方法で保護対象のコードは単一のVALUE
を取って返す関数の中にある必要があります。
厳密にこの型の関数を救出したいのでない限り、恐らくこの形式で所望のコードを走らせる梱包関数を作る必要があるでしょう。救出された例外にアクセスする方法もまた、救出される方法とは独立です。
rb_errinfo()
により、欠かせないRubyの$!
(1つも例外が起こらなければQnil
になります)のVALUE
が与えられます。Rubyとは異なり読んだ後で例外を手動で消し去らなければなりません6。さもなくばその後のAPI呼び出しが古い値を読んで別の例外が起こったと考えるかもしれません。
次に救出するための方法をいくつか眺めていきます。どれでも好きなものを使ってよいですが、一般に正しい選択はAPIの用途により決まると考えています。
rb_rescue2
Rubyによって読み込まれるライブラリをコンパイルしている場合、話は簡単です。
APIで投げられるいかなる例外もいつも通りRubyのコードで救出できます。
APIで例外を救出したければRubyのrescue
と似ているrb_rescue2()
を使うことができます。
始めから2つの引数は保護する関数とその引数です。その次の2つは例外が投げられた場合に呼ばれる関数とその引数です。rb_rescue2()
はvarargs関数なのでその後には救出したい例外クラスのリストが来ます。最後の引数は常に0
で、クラスのリストの末尾を示します。Rubyのrescue
のように、このリストにない例外は何も救出されません。もし(Rubyでの空のrescue
のように)StandardError
を救出したいだけなら、rb_rescue2()
の最初の4つの引数だけを取るrb_rescue()
を使うことができます。
Rubyとは違い、APIではそれぞれの例外クラス用に救出コードを走らせる簡単な方法を提供していません。一旦欲しい全てのクラスを救出して、switchの類を使って個別に取り扱う必要があります。
またAPIはRubyのelse
と等価なもの、つまり 何ら
例外が投げられなかったときに走るコード、を直接は提供していません。このための1つの方法はrb_rescue2()
の返り値を使うことです。もし1つも例外が投げられなければ最初の(危険な)関数の返り値を、さもなくば2つ目の(救出)関数の返り値を返します。これらが返すもの、ここではQtrue
とQfalse
としましょう、を受け取ることによって、どちらの場合になっているのかを検出できます。
rb_protect
RubyインタプリタをCに埋め込みたい場合、例外を投げ得るAPI関数を呼ぶときは 極めて慎重 にならねばなりません。
捕捉されない例外はVMをセグフォルトさせてプログラムをキルするのです。
rb_eException
と共にrb_rescue2()
を呼ぶようにできますが、全ての例外を救出する別の手法があります。
rb_rescue2()
と同様、最初2つの引数は保護する関数を呼び出すためのものです。しかし、rb_eval_string_protect()
のように、例外が投げられればQnil
を返しstate
を非ゼロの値に設定します。例外を再度投げたければstate
をrb_jump_tag()
に渡してください(これは他の*_protect()
関数からの状態についても同じことが言えます)。
ensure
rb_ensure()
はrb_rescue()
に似ていますが、例外を関知しないことと2つ目の関数が 常に
1つ目のものの後に呼ばれる点で異なります。充分に単純に思われるかもしれませんが、これが意味しているのは、もしRubyでするような通常のbegin;
rescue; end
の構造が欲しければ、梱包に別の層が必要になってくるということです。
Rubyでのensure
のように、ensure_func()
の返り値は決して使われません。何も例外が起きなければrb_rescue()
はbegin_func()
の値を返します。ここで、begin_func()
はdangerous_func()
の値を返します。もし例外が発生したらrb_rescue()
はrescue_func()
の値を返します。
定義、宣言
ここまでVMのメモリ内で直接オブジェクトを作ったり変更したりしてきましたが、どのAPI呼び出しも Rubyのコードの内部
での目に見える効果はありませんでした。
rb_str_new_cstr()
で作られたString
は、既定ではCからのみアクセスできます。
Rubyから見えるようにするにはいくつかの方法がありますが、全て同じ汎用的な仕組みではたらきます。その共通するところはRubyがアクセスできる何らかの名前を定義するという点です。例えば変数名やメソッド名などです。しかし注意を呼び掛けておきましょう。Rubyとは違い、APIでは不正な名前を与えることができます。クラスにfoo
(定数でない)やインスタンス変数にbar
(@
がない)を名付けようとするとRubyはSyntaxError
やNameError
を投げるでしょうが、APIは嬉々としてそうした名前で作り、不正な名前をRubyにさらけ出さない
ようにして対処します。そうしたいわけではないでしょうから、選ぶ名前はダブルチェックしてくださいね。
この節のほとんどのAPI関数はRubyのメタプログラミングに近いです。APIを使って何かしようとするときは、Rubyでメタプログラミングのメソッド呼び出しだけを使ってするとしたらどうするだろうと考えると役立つことがあります。例えばclass
Foo; def bar; end
とするのではなく、Foo = Class.new; Foo.define_method(:bar)
{}
と考えるのです。
大域変数
大域変数を扱う一番簡単な方法は以下です。
Rubyの大域変数に頻繁にアクセスするときはVALUE
を準備しておけば自動的に同期が取られます。
VALUE
はRubyで大域変数を作る前に初期化するべきで、そうしておけばCでも大域変数になります。
Rubyが使っている間はスコープを外れてほしくはないですからね。
rb_define_hooked_variable()
にすると、そうした操作で通常の同期をしたい場合にゲッターやセッターにNULL
を渡すことができます。
もしくはrb_define_virtual_variable()
で完全にglobal
を投げ出すこともできますが、もちろんゲッターとセッターはそうした場合で定義されている必要があります。
Rubyに露出 しない
大域変数VALUE
をCで作った場合、中途半端に掃除されてしまうのを防ぐためにガベージコレクタにそのことを伝えなければなりません。
クラスとインスタンス変数
インスタンス変数の取得と設定は大域変数にアクセスする単純な方法と似ていますが、もちろん変数を取得してくるオブジェクトが必要です。
大域変数もそうでしたがインスタンス変数を同期する自動化された方法はありません。
全てのインスタンス変数を巡回するにはrb_ivar_foreach
を使ってください。
クラス変数については、メソッドはrb_cv_get()
とrb_cv_set()
があり、もちろん最初の引数はクラスオブジェクトです。
定数
定数は似たように定義されていますが、それらを配下に置くためのモジュールを伴います。
Qundef
を設定すると定数は未定義にされます。定数のVALUE
の取得に関しては少しずつ違います。定数が指定されたモジュールで定義されて
いない ときに何が起こってほしいのかに依って呼び出すべきAPI関数が決まります。
これら全てのAPIはプライベート定数も取得します。
モジュールとクラス
モジュールの定義は超簡単です。
クラスも同じやり方ですが、こちらはスーパークラスも必要になります。
メソッド
ここから面白くなってきます。メソッド定義のためのAPI呼び出しには多くの種類がありますが、どれかを使う前にそのメソッドを呼び出すC関数が必要です。その関数はVALUE
を返しメソッドのレシーバ用のVALUE
引数を1つ持たねばなりません。他の引数を定義するのには3つの方法があります。
なので本当のところAPIでは2つの種類のメソッドのみが定義できるのです。且つは固定数の引数を取り、且つは全ての引数を一飲みします。Rubyの素敵な引数の機能はどうなったのでしょうか。オプション引数、オプションハッシュ、ブロック、これらの混在はどこにあるのでしょうか。
引数を解析する
さて、様々な個数の引数を受け付けられたら、メソッド内で自力で全ての仕組みをコードに書くことができ、あたかもRubyで凝ったメソッドを定義したように
振る舞わ
せられます。ありがたいことにAPIにはちょうどそういうことをするための早道があります。それにはC配列関数の定義を使うとよく、それからargc
とargv
を以下の流れに沿って渡すことができます。
ここでfmt
はメソッド引数がRubyでどのような見た目をしているのかを記述する書式文字列です。文字列は最大6個の文字を持つことができ、そこではそれぞれの文字はそれぞれ異なる節の引数を記述するのです。6つの節と(順番通りに)対応する文字は以下の通りです。
- 先頭にある必須引数の数:数字
- オプション引数の数:数字
- 展開される引数:
*
- 後ろに続く必須引数の数:数字
- キーワード引数:
:
- ブロック引数:
&
それぞれの節は省略できるので、必要ではないものについてはそのための文字を空けたままにできます。書式文字列の解析は貪欲に行われる点に注意してください。1*
は必須引数と展開を持つメソッドを示しています。1つの
オプション 引数と展開であってほしければ01*
と指定せねばなりません。書式文字列にしたがってそれぞれの Ruby
の引数用にVALUE*
を渡さなければなりません。渡されるポインタの数は6つの節の「合計」に等しいのですが、関心のない引数についてはNULL
を渡すことができます。例えば書式文字列21*&
には5つのVALUE*
が渡されなければなりません(2つの必須引数、1つのオプション引数、1つの展開、1つのブロックです)。
rb_scan_args()
は渡したVALUE*
を使ってargv
を開封し、もし誤った数の引数が渡されたときは符牒が合わない旨の例外を投げます。
rb_scan_args()
の返り値を使って、関数がどう呼ばれたのかを確定することもできます。そうすることでRubyで渡された引数の数が返ります。
ブロックの扱い
Cのメソッドがブロック付きで呼ばれたかどうかを確認する方法は2つあります。
Procとしてブロックを捕捉する方法は2つあります。メソッド引数にrb_scan_args()
を使っている場合、書式文字列に&
を含めるだけで取得できます。rb_scan_args()
を使っていなければメソッドのブロックをProcに変換するProc.new
と等価なAPI呼び出しがあります。
VALUE block;
block = rb_block_proc();
ブロックを捉えたくなければ、yieldする方法が2つあります。
rb_yield_values()
に似たrb_yield_values2()
もあり、varargs
の代わりに2つ目の引数がVALUE*
になっています7。
super
メソッドでsuper
を呼びたいことがあるかもしれません。
Rubyとは異なり、引数を1つも与えなければrb_call_super()
はメソッドの引数をsuperに暗黙に渡すことはありません。明示的に正しいargc
及びargv
を渡さなければならないのです(self
を自動的に渡します)。そういった理由からrb_call_super()
を使いたければCの配列スタイルのメソッド定義を使うことをお勧めします。
定義
Cの関数を用意するのは大変ですが、Ruby用にメソッドを定義するのは簡単です。メソッドをつくるAPI呼び出しは全て最低でもメソッド名
(char*
)、Cの関数へのポインタ、引数を表すargc
を取ります。argc
は以下のようなものです。
- 引数が一定数のときは、その引数の数(レシーバは数えません)
- Cの配列中の引数の数が可変なときは、
-1
- Rubyの配列中の引数の数が可変なときは、
-2
これ以降の全てはかなりそれ自体が分かりやすいものです。
モジュール と そのシングルトンクラス中でメソッドを定義する早道もあります。例えばMath
で多用されており、例としてinclude
Math
とすることで一々メソッド呼び出しでMath.
と打つのを避けられます。
その他のもの
クラスやメソッドの定義のための単純なAPI関数があります。
データ
今となってはAPIを使ってRubyのクラスを作ったり操作したりできるでしょう。しかしCの世界でデータをカプセル化するRubyのクラスを作るにはどうすればよいのでしょうか。データが自然にVALUE
に翻訳できるのであれば簡単です。いつも通り変換してインスタンス変数に代入すればよいです。でもデータに、Rubyでいうところのこれ、といったものがないとしたらどうでしょうか(例:何らかのCのライブラリで定義されたデータ構造)。
APIを使うと、所望のVALUE
を作り、Rubyのオブジェクトの中にCのデータを指すvoid*
を格納することで、Cのデータをカプセル化できます。そうしてCのデータにアクセスする必要が生じたら、ポインタを開封して正しい型に変換し戻せばよいのです。しかしどの時点でこのカプセル化は起こるのでしょうか。その質問へは次の質問でもって答えましょう。new
を使ってオブジェクトを作るようRubyに伝えたとき何が起こるでしょうか。基本的にはこうです。
私達がよく知っている インスタンスメソッド initialize
を呼ぶ前に、new
は最初に クラスメソッド
allocate
を呼んで実際にオブジェクトを作るのです。これこそが、Cのデータを包み込むオブジェクトが欲しい場合に定義する必要のあるメソッドです。以下の例はinitialize
によって設定できるint
を包むクラスFoo
を作っています。
ほとんどの場合(struct
のような)もっと複雑なものを包むことになりそうですが、基本は変わりません。Cのデータをアロケートした後は、TypedData_Wrap_Struct()
8マクロを使ってVALUE
中のポインタを包みます。この梱包には3引数要ります。オブジェクトのクラス(クラスメソッドにいるのでself
です)、構造体へのポインタ、そして梱包されるデータのポインタです。引っ掛かりやすいところは構造体ポインタにあります。このポインタはRubyが内部的に使う追加情報を提供するものです。
wrap_struct_name
はRubyによって使われる文字列で、型を識別するために使われます。意味が通っていて一意である限り本当に何でも構いませんfunction
はガベージコレクタによって使われるいくつかの関数ポインタを含む構造体ですdmark
については後述しますが、CのデータがRubyのオブジェクトを指さない限り不要ですdfree
が呼ばれるのは、オブジェクトが破棄されオブジェクトによってアロケートされた全てのメモリが解放されるときですdsize
はがRubyから呼ばれるのはオブジェクトがどの位メモリを取っているのか確認するときです。省略も 可能 ですが、含めておくのが誠実というものですdata
は任意のデータを指すことができます。Cのデータをクラス水準で包んだものと考えてください。こちらも必須ではありませんflags
を使うとオブジェクトがガベージコレクトされたときの追加の最適化を有効にします。dfree
関数がGVLを解放しない限り(そうしたいことなんてあるのでしょうか)、RUBY_TYPED_FREE_IMMEDIATELY
を安全に設定することで僅かに効率性の向上が得られます
これらの要素を設定しない場合はRubyがうっかりゴミデータを読まないようにゼロにしてしまうべきです。上の例でC99の明示初期化子構文を使っているのはそのためです。どの要素を省いてもコンパイラによって安全に掃除された状態になっています
Cのデータを包むVALUE
は、TYPE()
マクロに関しては型T_DATA
を持ちます。これはRuby固有のオブジェクトとCのデータを包んでいるオブジェクトとの間の区別を明確にする補助になります。
一旦Cのデータを包み込む作業が全部終わったら、元に戻すのは簡単です。TypedData_Get_Struct()
は開封するオブジェクト、通底するデータのCの型、前と同じ構造体ポインタ、そしてデータを代入するポインタを取ります。
このアロケーションと初期化はRAIIを誤魔化すことはないので、C++を使っているなら恐らくデータを開封するときに配置newを使うことになるでしょう。
アロケーションと初期化を分離するのが難しければデータをstruct
に包み実際のアロケーションをinitialize
ですることもできます。
(前の例のような)単純な場合ではコードをもっと冗長にならないようにできます。
例にあるようにデータを解放するのにfree()
を呼ぶだけなら、dfree
にRUBY_DEFAULT_FREE
を渡すことでRubyが代わりに解放してくれます(メモリリークがお好みでなければNULL
を使わないでください)。
同様にアロケーションで上の例のように単にmalloc()
だけでいいなら、TypedData_Make_Struct()
がアロケーションをやってくれて、且つ梱包もしてくれます。
以前の例を以下のように短くできます。
マークする
上の型構造体中のdmark
ポインタはオブジェクトの「マーク関数」へのポインタです。これはガベージコレクタの「マーク・アンド・スイープ」アルゴリズムに因んでいます。マーク・アンド・スイープの背景にある基本的な考え方において、ガベージコレクタがメモリを解放する必要があるときに2つの工程を実施します。最初の工程(マーク)では全ての
参照されている Rubyのオブジェクトを巡回し活性と印を付けます。それから2つ目の工程(スイープ)では全ての アロケートされた
Rubyのオブジェクトを巡回し、活性と印が付けられていないものを解放します。
このことはCのデータを包むことに関係してきます。
なぜかというとRubyのVALUE
を含むCのstruct
を包む可能性があるからです。
ガベージコレクタはこのVALUE
を消し去る使命があります。
ガベージコレクタは(Cのポインタではなく)Rubyで参照されたVALUE
だけはそれと気付きますが、この場合はVALUE
を活性だと印を付けられません。
結果として、ガベージコレクタがメモリを解放する必要が生じるやいなや、Cのデータは不在のRubyのオブジェクトを参照することになってしまうでしょう。
なおこういった類の、Cのデータの中にRubyのデータを包むことは本当に悪い考え方で、正にこれに類する問題が理由です。
でももし本当にそうしなければならないとすれば……。
以下の例ではVALUE
を含むCのstruct
を包みます。マーク関数は解放関数と同じシグネチャを持っており、struct
中のVALUE
に印を付けて回ることだけに関するものです。
struct
がVALUE
のCの配列へのポインタを含む場合は、代わりに2つの引数を取るrb_gc_mark_locations()
を使うことができます。この2引数は配列の先頭と末尾へのポインタです(末尾は先頭のポインタに配列の長さを足したものと等しいです)9。
スレッド
Cのスレッドの中のRuby
沢山のAPI呼び出しをしたり沢山のRubyのコードをCから走らせたりする場合、ある時点でふと我に返って、「APIを使ってこの遅いRubyのコードを全部走らせているな。スレッドっぽいものでコードを速く保てるかもしれないぞ」と思うかもしれません。なるほど理に適った考えですが、Ruby VMは全くもってスレッド安全ではないと気に留めておくことに目をつぶればの話です。理想的にはAPIのコード全部は単一スレッドで走らせるべきです。そうでなければ恐らく全てのAPI呼び出しをロックされたミューテックスに包む必要があるでしょう。同時に複数のスレッドがAPIとやり取りすることがないようにするためです。
単にAPIを使う普通のRubyのThread
を作りたいだけなら(そして次節で述べるGVLが気にならなければ)、簡単な方法があります。
他のThread
の関数はruby/intern.h
にあります(しかしどんなものであれいつでもrb_funcall()
があります)。
Rubyのスレッドの中のC
他方で、重いCのコードをAPIでRubyにさらけ出す場合(例えばCのライブラリを包む拡張を書いている場合など)、大域的VMロック (GVL)
と呼ばれる頭痛の種には時間を取って考えてみることです。ほとんどのAPIはThread
安全ではないですから、一度に単一のThread
だけが走るように、GVLはほぼ全てのRubyのコードをロックしてしまいます。これが俗にThread
では真の並列性がないと言われる所以です。
またVMはRubyにさらけ出すCのコードには漏れなくGVLを適用します。
CのコードをThread
の中から呼ぶときに暴発を心配せずAPIを使えるのはそのためです。
このことの欠点はCのコードを走らせるのに時間が掛かることで、Thread
で呼ぶことによる効率性の利点が見られないということです。
なぜならそのスレッドが走っているときは他の全てのスレッドをブロックしているからです。
しかしGVLはAPI呼び出しを保護するためだけに必要なものです。
APIを使わないCのコードがあるとき、スレッド中でコードを走らせる前にGVLを免除してほしいとVMに伝え、処理が完了したあとにGVLを再装填できます。
こうすれば真の並列性が得られます。
GVLを封鎖したり解除したりすると効率性への影響はあるので、ブロックされたスレッドによる顕著な問題を抱えていると気付いたときだけこの方法に頼るようにしてください。
こういったことをするコードはRubyで開発している人にとっては酷だと思われるので、実際には使用する別のヘッダを含める必要があるでしょう。まずGVLを解除する少しだけ単純なやり方を見ていきます。
GVLなしに走る関数がvoid*
を使うデータを取得したり返したりしているので、ポインタを介してデータを渡すためにstruct
を定義したいところでしょう。
上のようにGVLを解除するとコードは確かに並列で走るものの、(シグナルやThread.kill
などによって)中断できないことに気付かれるでしょう。中断できるようにするには末尾の2引数を使ってブロック解除関数を渡さなければいけません。
ブロック解除関数は中断のイベント中に呼ばれます。動作させるためには恐らくポインタを両方の関数に渡して、中断が一方から他方へと伝わるようにするために使う必要があるでしょう。中断された関数は早期に返る前に必要な片付けを実施します。
代えて中断された関数が特別な片付けをする 必要
がないならば、(ブロック解除引数を無視する)組み込みのブロック解除関数RUBY_UBF_IO
10が使えます。これは単純に中断を走っているスレッドに転送します11。
GVLを解除するためにこうした一通りの骨折りをした後で、ブロックが解除されたスレッドでAPI呼び出しをする必要があるとわかった場合のみ、一時的にGVLを再装填する関数があります。
参考文献
extension.rdoc
Rubyには確かに公式のAPIドキュメントがあります。(私の感想としては)少々むらがあってお勧めしにくいのですが、特定の話題についてはもう少しつまびらかにされていたりもします。筆者が意図的に書かずにおいたところは沢山ありますが、それは有益と思われなかったりもっと良いドキュメントがどこかにあったりするからなのです。
ヘッダ
最も敷居の高い資料はRubyのヘッダ自体だと思っています。
完全なAPI(すなわちruby.h
を含めることで手に入る全てのもの)はゆうに千を越える関数、マクロ、定数、大域変数からなります。
そのほとんどは全くドキュメントがありません。
しかしほとんどのものは理に適った命名でヘッダから何をするものなのかを調べることができるでしょう。
必要になるもののほとんどはruby/ruby.h
とruby/intern.h
のヘッダにあります。
前者はVMとメタプログラミング関数についての全てがあり、後者にはRubyに組み込みクラスとやり取りする関数の全てがあります。
ruby.h
では取り込まれ ない ヘッダもあり、含めることで追加のAPI機能が得られるものがあります。
いつの日か本手引きで節を改めることがあるかもしれません。
ruby/debug.h
(実験的)プロファイリングとコードの追跡用の関数ruby/encoding.h
文字列エンコーディングを扱う関数ruby/io.h
RubyのIOクラス用の追加の関数ruby/re.h
RubyのRegexpクラス用の追加の関数ruby/thread.h
GVLを扱うための関数ruby/version.h
バージョンを調べる関数。これを機能を検出するコードに使わないようにruby/vm.h
(実験的)VM制御用の関数
ソース
どこにもドキュメント化されていないヘッダ中の関数を見付けたら、次なる一手はRubyのソースコードです。
ソースコードを一読する際は常にヘッダを控えておきましょう。ソースコードには本当に役立ちそうでいてAPIにありそうに 見える 関数が沢山あります。ほとんどの場合その役に立つ関数を呼び出すために梱包するAPI関数があることでしょう。
例
例のページには短くてコンパイルできる、実際のAPIの例があるので読んでみてください。
貢献する
これで本手引きを読み終わりましたが、何か著しく書き漏らしたものがあるでしょうか。 粗忽な誤りがなかったでしょうか。このサイトのソースをGithubで確認してイシューを報告したりプルリクエストを送ってください。 またGithubから全てのコード例をダウンロードできます。
脚註
-
rb_eval_string_wrap()
もあり、きっと便利なのでしょう。 しかし実はバグがあり、rb_eval_string_protect()
と同じものになっています。 ↩ -
まったくの嘘っぱちです。 APIにより、確実にオブジェクトの内部データ構造を滅茶苦茶にできます(名前が大文字Rで始まるものを探してみてください)。 しかし一般には感心しませんし、必要でもないのです。 ↩
-
未定義値を表す
Qundef
もあります。 しかし、Rubyでそれと同じものはなく、滅多に使われません。 実際、こうして稀に出てくることはありますが、Rubyで通常のVALUE
が期待されるところにQundef
があると、VMはセグフォします。 ↩ -
ドキュメントではブロックを突破する用に、
rb_iter_block()
とrb_iter_break_value()
について言及しています。 しかし、単に早期に戻ることはできないのでしょうか。 筆者はこれらの使い途が思いつきません。 ↩ -
ドキュメントには、
rb_protect
の最中に「捕捉した例外を無視する[場合][when]は……エラー情報を消去しなければいけない」と記述されています。 どのような場合には消去してくれるのかについて書かれたドキュメントは見つかっていません。 常に消去しなければいけないように思われます。 ↩ -
そして、
rb_yield_block()
という使われない引数を2つ取るものがあり、Rubyではどこからも呼ばれません。 妙です。 ↩ -
TypedData*
マクロは、Ruby 1.9.2以降では、データを包む好ましいやり方です。 古いバージョンのRubyを使っているなら、Githubで本手引きの古いバージョンを確認すれば、かつてどのように使われていたのかがわかります。 ↩ -
謎めいた名前の
rb_gc_mark_maybe()
もありますが、いつ必要になるのかわかりません。 ↩ -
RUBY_UBF_PROCESS
を使うこともできます。 しかし、旧式のコードの残滓と思われます。 ともあれ全く同じ効果があります。 ↩ -
関数
rb_thread_call_without_gvl2()
もあります。thread.c
のドキュメントには、「中断を検知すると直ちに戻る」とありますが、どういう意味なのかよく分かりません。 ブロック解除関数がスレッドをキルしなければ、戻る前にスレッドがひとりでに終わるまでずっと待機することになります。 ↩