CでRubyを実行する
コンパイルする
Rubyを組込むには、ruby.h
ヘッダが要ります。
このファイルには、プラットフォーム固有のruby/config.h
ヘッダが含まれます。
恐らく、これらのヘッダのincludeパスをコンパイラに伝える必要があるでしょう。
Rubyライブラリともリンクする必要があります。
私の環境では、最小限のコンパイラオプションは次の通りです。
しかしもし可能であれば、pkg-config
を使うと良いでしょう。
自分のOSに適したオプションが得られます。
こうしたやり方は、Rubyがマシン上の標準的でない場所にインストールされていたり、OSが標準的なヘッダないしライブラリを提供していないときは、うまくいかないかもしれません。 より頑健なやり方は、どこにあるのかRuby自体に問い合わせて、実行時ライブラリパスを準備することです。
Windows
Windowsでは、RubyInstallerをDevkit付きで使うことを強くお勧めします。
このツールにより、WindowsのコマンドラインでGCCを使えるようになり、ビルドフラグの取得にpkgconf
も使えます。
なお、GCCの引数はPowerShellでは正しく解析されないことがあります。
そのため、cmd
を代わりに使ってください。
また、Ruby 3.3のインストーラーは何らかの理由でpkgconf
の出力からライブラリの場所が欠けています。
そのため、手動で-L
オプションを加える必要があります。
前述のように、RbConfig::CONFIG["libdir"]
で配置できます。
筆者の場合、インストーラーにより、C:\Ruby33-x64\lib
に置かれました。
最後に、Windowsにはrpath
はありません。
そのため、ビルドされた実行するプログラムが実行できるようにするため、リンクされたDLLをそのそばに複製する必要があるでしょう。
これには、Ruby自体に必要なDLLも全て含まれます。
筆者の場合、インストーラはこれらをC:\Ruby33-x64\bin\ruby_builtin_dlls
に置いています。
起動と終了
RubyインタプリタをCやC++のプログラムに含めることはとても簡単です。
ヘッダーを含めて、
APIを使用する前にインタプリタを立ち上げるための関数を main
で呼び、
そして完了後に後片付けをする関数を呼べばよいのです。
ruby_init()
の最中にVMが実行に失敗したら、エラーを表示してプログラムが終了してしまいます!もっと柔軟にエラーを出したいときは、代わりに失敗したときにゼロではない値を返すruby_setup()
を使いましょう(残念ながら、この場合のエラーメッセージの出しかたはよくわかっていません1)。
rb_cleanup()
の最中にエラーが発生したときは、ゼロではない値を返します。
もしエラーが発生しなければ、渡した引数が返ります。
この仕様により、後片付けに失敗したときのエラーステータスを返す部分が少し短く書けます(先の例で実演したように)。
技術的には main
で ruby_init
や ruby_setup
を呼ぶ必要はありません。
しかし、RubyのVMは以降全てのRubyのコードが同じかこれより低層のスタックフレームから
実行されることを仮定しています(ガベージコレクションのためです)。
他の方法でも動くにせよ、
このことを確証する最も簡単な方法がプログラムのトップレベルで立ち上げを行うことなのです。
例えば深く入れ子になった関数でRubyを初期化して、沢山のスタックフレームを立ち上げ、
そして沢山のRubyのコードを実行するようなことはよくありません。
後片付けをするときにも、VMはRubyのコードを評価するかもしれません(at_exit
にブロックを渡したときなど)。
そしてそのときに例外が発生する可能性があります。
ruby_cleanup()
はこのような例外が発生したときに、
ゼロではない値を返してエラーメッセージを表示することで制御します。
代わりに ruby_finalize()
を呼ぶと、通常通り例外を発生させます。
(制御方法についてはExceptions節を参照)
別の例はこちらです。
制約
上記のスタックフレームの警告以外にも制約があります。
1つのプロセスに1つだけRubyのVMを動かせます。
起動と終了の方法を見ると、何度でもVMの破壊と創造を繰り返せるような気がしてくるかもしれませんが、
ruby_cleanup
はRubyのコードが全ての後片付けが完了したことだけを確認します。
VMの状態を再度初期化できるような状態までは後片付けしません。
もう一度 ruby_init
を呼び出すと、実行に失敗してしまうでしょう。
何かかの理由があってプログラムで複数のRubyのVMが必要になったら、 この制約を回避するために複数のプロセスに小分けにしなければいけません。
VMを設定する
これでRubyのVMの実行の骨子を会得しました。
でも、Rubyのコードの実行に先駆けてもう少し設定したいことがあるかもしれません。
エラーメッセージとかのためにRubyのスクリプトの名前(例:$0
)を設定したいときは、
以下のようにします。
gemが require
で呼び出せるようにするためにロードパスを設定するには、
次のようにします。
VMにはコマンドラインで ruby
するときと同じオプションを渡せます。
警告水準や冗長モード2の設定に手頃です。
ruby_options
への引数はmain関数と同じargc
とargv
です。そして、ruby
プログラムのmainと同じように、呼び出したときはVMは何らかのRubyのコードがあるものとしています。ロードするスクリプトのファイル名を与えていなかったり、-e
で実行するコードがないときは、stdin
から読み込もうとします。オプションを設定したいけれども、Rubyのコードを実行したく
ない ときには、"-e "
のように空行を渡せばよいです。
ruby_options()
はコンパイルされたRubyのコードを表現する “node” を返します。
場合によっては(文法エラーとか)nodeが不正で実行すべきでないときがあります。
ruby_executable_node()
はこのnodeを検査します。
nodeが妥当であれば、 ruby_exec_node()
で実行できます。
ruby_executable_node()
で(ポインタを介して)返る状態は、
コンパイルの最中やコードの実行時に例外が発生したら、ゼロではない値になります。
例外を自前で読むこともできますし、
ruby_cleanup()
に state
を渡して適切なエラーメッセージを表示させることもできます。
Rubyは今のところコードの他のコンパイル・実行を別々にする方法を提供していません3。
やったね
これでRubyとやりとりできました! C APIに戻りましょう。