RubyでCを実行する
コンパイルする
C言語のコードを、Rubyの拡張としてロードできるようにコンパイルするには、
ちょっとしたコンパイラのオプションが要ります。
Rubyの mkmf
標準ライブラリがあれば、
適切なmakefileを生成してくれるので、
コンパイルが楽になります。
準備
まず、C言語のコードがあるのと同じディレクトリに extconf.rb
というファイルをつくります。
準備する部分では、標準的なUNIXでの configure
スクリプトでするようなことをします。
例えば次のようなことです。
- 現在のプラットフォームの特徴を確認する
- 必要なライブラリと関数があることを確認する
- ビルドするのに必要なプログラムがあることを確認する
よくある機能は mkmf
で提供されます(でも、必要とあらばRubyをまったく使わなくてもいいです)。
例えば、SDL2を使っていて、それとは別に int
の大きさを知らなければいけないような拡張であれば、
こんな感じで呼び出すことになります。
create_header
は、それより前に呼んだ mkmf
の関数の結果に基づいて、
前処理器の定義を含む extconf.h
ファイルをつくります。
この例では extconf.h
は次のような内容を含みます。
このヘッダはC言語のファイルに取り込むとよいでしょう。
そうすれば様々なプラットフォームでコードが使えるようになります。
気に留めておいてほしいことは、
mkmf
関数がビルドの失敗を示す値を返したら、
extconf.rb
スクリプトを頓挫させられますし、そうするべきだということです。
例えば、ある拡張にSDL2が 必須 であれば、
have_library('SDL2')
が false
を返したときは、
何らかの有意味なエラーメッセージを吐いて終了するようなスクリプトにしましょう。
とりあえずMakefileを生成して、
よくわからないコンパイルエラーで利用者を置き去りにするよりはマシです。
create_makefile
はもちろんMakefileをつくりますが、
その引数が特に大切です:
C言語のコードの入口を決めるものであり、
コンパイルされたライブラリの名前であり、
Rubyで require
するときの引数でもあるのです!
これは拡張の名前であるべきです。
生成されるMakefileの変数のいくつかを変更するには、
Rubyでの対応する大域変数を変更します:
$CFLAGS
や $CPPFLAGS
や $LDFLAGS
がそうです。
自動的な生成方法がうまくいかないときは、
Makefileでのオブジェクトファイルのリストを $objs
大域変数で指定することもできます。
全ての mkmf
関数とそれぞれのオプションはオンラインで充分に文書化されています。
extconf.h
を生成する関数に加えて、
また違ったソースファイルの配置や依存関係を制御する様々な関数があります。
実行
あとはもう、これ以上簡単になりようがありません
1 もちろん、C言語のコードがコンパイルできないことには話が進まないですけどね。
初期化
C言語のコードでは ruby.h
を含めてAPIを使えるようにしたいのではないでしょうか。
その他には、 require
したときにRubyの仮想機械がライブラリを呼べるように、
関数を定義するだけでよいです。
関数の名前は、 extconf.rb
で create_makefile
に渡した引数で決まります。
ここまでの例で "foobar"
を使ってきたので、
次の内容の foobar.c
ファイルをつくります。
ファイル名
拡張に1つのC言語のファイルだけしかないなら、
さっきやったように拡張の名前と同じファイル名にしましょう。
逆に複数のC言語のファイルからなる場合、
拡張に 同じ名前を使わない ようにしましょう。
リンクする段階でMakefileが拡張名と同じ .o
ファイルを生成すると、
それと同じファイル名にコンパイルされる .c
ファイルがある場合に衝突してしまうからです。
また、 conftest.c
というファイル名も、 mkmf
によって書き込まれるかもしれないので避けます。
やったね
これにてmakeで.so
(またはその他のライブラリ)にコンパイルして、Rubyでrequire
できるようになりました。
純粋なC言語のコードでInit
関数を実装していくこともできますが、たぶんいったん戻って、もっと面白いことをするためにC
APIを学ばれるのではないでしょうか。
Gem
いい感じに拡張が動くようになったら、簡単に配布できるRubyのgemとしてまとめたいはずです。
rubygems.orgにはgemをつくる上での詳細な手引きがありますが、C拡張に関して言えば extconf.rb
についてspecに記すだけでよいです2。
gemが複数の拡張に依存していたら、
ext/
のサブディレクトリにそれぞれ固めて、
specに各々の extconf.rb
を記します。