Ruby C Extensionの作成 for Windows

RubyのC Extensionの作成方法について書いています。ただし、正当なやり方であるかどうかは分かりません。あまりWindows用に書かれていた記事がなかったので、書きました。本来の(Ruby開発をしている人たちが推奨する)やり方があるのだと思いますが、とりあえずこれで動いたというところまでです。

いろいろ読んでみるとextconf.rbを作れとか、mkmfが何だとかいろいろやり方があるみたいですが、とりあえずそういうことは無視します。RubyからDLLの関数呼び出せればそれでいいっていう人向けです。そのうちextconf.rbとかも書けたら書きます。

RubyのVersionは1.9.1を利用しています。コンパイラはVisual Studio 2008です。とりあえず関数を呼んで戻り値を得るところまでをやります。そこからはRubyのCの書き方に沿ってやればよいはずです。C Extensionがなんたるかとか、DLLがなんだとかいう話はしません。その辺が分からない人が読んでいるとは思えないので。

サンプルコード

概要

まずは、全体像です。Windowsですので基本的にはDLLを作ることになります。結局はプラグインなので、関数の名前付けや、最初に呼び出される初期化関数を規則に基づいて作っていけば、あとはRubyがやってくれます。

以下手順です

今回は、とりあえず整数と文字列を戻り値として返す2つの関数をCで実装し、DLLとして、それをRubyプログラムから呼び出して、その値を表示しようと思います。

プロジェクト名はMyExtです

プロジェクト準備

まずはプロジェクトを作ります。Visual Studio 2008から通常通りDLLを作成するプロジェクトで作ります。ただし今回は空のプロジェクトとして作成し、そこにMyExt.cを作成しています。

次にruby.hの準備です。Rubyのインストールフォルダのinclude\ruby-1.9.1の中から、ruby.hとRubyフォルダをコピーしてきます。

i386-mswin32というフォルダがありますが、これは中身だけ利用します。中にはconfig.hが入っています。これを先ほどコピーしたRubyフォルダ内にコピーします。

さらに、そのconfig.hを編集します。2行目にある以下の行をコメントアウトします。コンパイラチェックをはずしています。

#error MSC version unmatch: _MSC_VER: 1200 is expected.

最後にmsvcrt-ruby191.libをRubyのインストールフォルダのlib内からコピーしてプロジェクトと同じフォルダにコピーしておきます。当然プロジェクトの追加の依存ファイルにこのファイルを指定しておいてください。

プロジェクトフォルダは以下のような感じになります。

とりあえずコード

以下にMyExt.cを示します。まあ、これだけでだいたい分かってもらえると思います


#include "ruby.h"

VALUE method_get_string(VALUE self){
	return rb_usascii_str_new("MyExt String" , 12 );
}

VALUE method_add_ten(VALUE self, VALUE arg){
	int ret;
	Check_Type(arg , T_FIXNUM);
	ret = 10 + NUM2INT(arg);
	return INT2NUM(ret);
}

__declspec(dllexport) void Init_MyExt(){
	rb_define_global_function( "get_string" , (VALUE (*)())method_get_string , 0 );
	rb_define_global_function( "add_ten" , (VALUE (*)())method_add_ten, 1 );
}

関数を3つ書きます。コード上の最初の2つはRubyから呼び出す2つの関数です(後述)。最後の1つはRubyがこのDLLをロードしたときに呼び出す初期化用関数です。当然ながら名前の付け方が決まっています。Init_(ファイル名)となります。今回の場合はInit_MyExtです。そしてDLLでエクスポートしておきます。

まず重要なのがこの初期化です。ここで関数なりクラスなりRubyから利用する物を定義していきます。ここではグローバル関数を定義しています。コードまんまです。rb_define_global_runctionの引数は順に、Rubyから呼び出すときの名前、その関数ポインタ、引数の数、となります。ここの引数は実際のCの関数の引数より1つ少ないです。自分自身を表すselfを必ず第一引数に取るためです。

ここでは主に、rb_define_xxxxを使っていろいろな物を定義していきます。

前に戻って2つの関数ですが、それぞれ文字列を返す関数と、引数で与えられた整数に10を足して返す関数の2つです。

文字列を返す関数ではrb_usascii_str_newを使って、C文字列からRuby文字列を作成して返しています。このように常にRubyとCとの間で型の変換をしなくてはいけません。

整数に10を足す関数では、まず引数のargの型チェックをしています。次に、Cの整数(int)に変換して10を足してから、またRubyの整数に変換(INT2NUM)をして返しています。

ruby.hにはこのような変換用の関数やマクロが用意されているため、比較的直感的にExtensionを作ることができます。

コンパイル、そして呼び出し

これをコンパイルします。通常だと拡張子は.dllとなりますが、.soに変更してください。プロジェクトの設定で変更してもいいですし、後から変更しても構いません。

とりあえず、できあがったMyExt.soと同じフォルダにUseExt.rbを以下のように作成します

require 'MyExt'
puts add_ten(10)
puts get_string

実行すると20とMyExt Stringと表示されるはずです。

今回のサンプルはCで書きましたがC++でもきちんとInit_XXXXさえエクスポートしてあれば同様に書けるはずです。ただしエクスポートする際にextern Cするなど、いくつかの変更は必要になります。

参考文献

実際にC Extensionを書くには使うにはRubyの内部的な動作を知る必要があります。以下のサイトからある程度は得られるかもしれません