RubyInstallerのRuby2.0でRubyInlineとOcraを使ってexe化する

Rubyのコード内にCのコードを埋め込んで、その部分だけCコンパイラコンパイルして拡張ライブラリ化し、呼び出す。これがRubyInlineの仕掛けである。Windowsでうまく動かないとか、Ocraでexe化するのがうまくいかないとか、色々あったが、最近のんは改善されてるのかなーと思って試してみた。
2010年に同じことを試した人が既にいて、
RubyInlineを使ったRubyのスクリプトをocraで実行ファイルにする(まとめ)
現在、RubyInlineのRuby1.9以降対応、Ocraの動作不良については解消されているようで、ここにかいてある方法のうち、
・自分のコードでENVを設定する
・RubyInlineのタイムスタンプ判定修正
の2点だけでRubyInlineを使ったコードをOcraでexe化できるようだ。
なお、今回使うサンプルコードはRuby1.9.1でRubyInline動かしたで使われているものを、ちょっと重いので処理を減らしてENV指定を追加したものである。

サンプルコード

ENV['INLINEDIR'] = File.dirname(File.expand_path(__FILE__))

def benchmark
  s = "a" * 1000

  test = Test.new
  t = Time.now
  1000.times{test.string_xor(s, s)}
  Time.now - t
end

class Test
  def string_xor(str1, str2)
    result = str1.clone
    str1.length.times do |i|
      result[i] = str2[i]
    end
    result
  end
end

b1 = benchmark

begin
  require 'inline'
  class Test
    inline do |builder|
      #RSTRING(str1) -> ptr を RSTRING_PTR(str1) に変更。 len も。以下同様。
      builder.c <<-EOF
        VALUE
        string_xor(VALUE str1, VALUE str2)
        {
          VALUE result = rb_str_new(RSTRING_PTR(str1), RSTRING_LEN(str1)); 
          int i;
          for (i = 0; i < RSTRING_LEN(str1); i++) {
            RSTRING_PTR(result)[i]  ^= RSTRING_PTR(str2)[i];
          }
          return result;
        }
      EOF
    end
  end
end

b2 = benchmark

p b1
p b2
p b1/b2

このコードのうち、一番上にあるものがexe化するのに必要なものである。exe化しないなら無くても動く。

手順

RubyInstallerでRuby2.0を入れて、Devkitも入れる。んで、RubyInlineとOcraをgemでインストールする。

gem install RubyInline
gem install Ocra

「C:\Ruby200\lib\ruby\gems\2.0.0\gems\RubyInline-3.12.2\lib\inline.rb」の532行目を修正する。

#      unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
      unless so_exists and File.mtime(rb_file) <= File.mtime(so_name) then

これは、exe実行時に展開されたファイルが同じタイムスタンプになっているとRubyInlineがコンパイルしようとするからで、同じならコンパイルしないようにする修正である。ファイルの展開はリアルな時間がかかるから、ひょっとすると展開タイミングやサイズによって時間がズレて実行時にエラーが出るかもしれない。ここは強制的にコンパイルしなくするようなオプションが欲しいところ。あるのかもしれんが。
こうしておいて、

ocra hoge.rb

とすると、そのディレクトリ以下に.ruby_inlineディレクトリが作られて、Cのコードとコンパイル結果のsoを出力、それを取り込んだexeファイルができあがる。

用途など

最近のRubyは速いので、相当重い計算が大量に集中するようなロジックでなければ必要ないかもしれん、とは思うのだが。用途的には経路探索とか、暗号化とか、圧縮とか?Shaderではできないような画像編集を高速にする、SoundEffectの音生成を高速にやる、DXRubyの内部構造体を直接操作して高速なキャラ移動計算とか、まあ、普通の用途ではないような気しかしない。
そもそもCコードを埋め込むことが必要になる状況というものが、いまどきあるかというと、無いことは無いんだろうけども、なかなか無いだろう。
手段はあるに越したことはないから、こういうこともできる、と覚えておく程度でいいのかもしれない。