monolithic kernel

VC++とSWIGでRubyの拡張ライブラリを作る

April 02, 2010

    RubyはC/C++を用いて拡張ライブラリを作成することができますが、用意されているAPIはあくまでもCレベルのものであり、C++のクラスをRubyから使えるようにするためにはラッパを作成する必要があります。今回は、ラッパ部分を自動生成してくれるツールであるSWIGを利用して作業を簡略化したいと思います。

    以下の内容は、Visual Studio 2008 ProfessionalとActiveScriptRuby 1.8.7(p249)、SWIG 1.3.40の組み合わせで試しています。ActiveScriptRubyとSWIGは以下のサイトからダウンロードできます。なお、ここではActiveScriptRubyとSWIGのディレクトリにパスを通す作業は省略しています。

    下準備

    最初に一度だけ必要な設定を行います。

    環境変数の設定

    Rubyのヘッダファイルを参照できるようにするため、環境変数を設定します。パス中のは各自Rubyをインストールしたディレクトリに置き換えてください。

    • RUBY_INCLUDE
      • <ruby-1.8>\lib\ruby\1.8\i386-mswin32
    • RUBY_LIB
      • <ruby-1.8>\lib

    VC++の設定

    VC++からRubyのヘッダファイルを参照できるようにするため、オプションのVC++ ディレクトリを開いて以下の項目を追加します。

    • インクルード ファイル
      • <ruby-1.8>\lib\ruby\1.8\i386-mswin32
    • ライブラリ ファイル
      • <ruby-1.8>\lib

    config.hを編集

    VC++ 2008だとActiveScriptRubyに同梱されているconfig.hのバージョンチェックで弾かれてしまうようなので無効化します。あまりよろしくないことだとは思いますが、動いたのでよしとしておきます。config.hは <ruby-1.8>\lib\ruby\1.8\i386-mswin32 にあります。

    config.h
    //#if _MSC_VER != 1200
    //#error MSC version unmatch
    //#endif

    動作確認

    プロジェクトの作成

    Visual Studioでプロジェクトを作成します。構成はWin32 プロジェクト、DLL、空のプロジェクトとします。以下、プロジェクト名はRubyTestに設定したものとして進めます。

    プロジェクトにモジュール定義ファイルを追加

    プロジェクトにモジュール定義ファイル(.DEF)を追加します。このファイルは外部にエクスポートする関数を指定するために必要です。Rubyは拡張ライブラリのロード時に”Init_ライブラリ名”の関数を呼び出すので、ここではInit_RubyTestをエクスポートするように設定しておきます。Init_RubyTestの定義自体はSWIGが自動的に生成してくれるので特に気にする必要はありません。

    RubyTest.def
    LIBRARY "RubyTest"
    EXPORTS  Init_RubyTest

    リンカの設定

    リンク時にRubyのライブラリファイルがリンクされるよう設定します。プロジェクトのプロパティでリンカ→入力と辿って追加の依存ファイルに”msvcrt-ruby18.lib”を指定すればOKです。

    テスト用のクラスを作成

    適当にクラスを作成します。中身は何でもいいのですが、ここではhello.cpp、hello.hppを作成して以下のように記述しました。

    hello.cpp
    #include "hello.hpp"
    #include <iostream>
    
    Hello::Hello() {}
    
    Hello::~Hello() {}
    
    int Hello::hello() {
        std::cout << "Hello, SWIG" << std::endl;
        return 12345;
    }
    hello.hpp
    #ifndef hello_HPP_INCLUDED__
    #define hello_HPP_INCLUDED__
    class Hello {
    public:
        Hello();
        ~Hello();
        void hello() const;
    private:
        Hello(const Hello&);
        void operator=(const Hello&);
    };
    #endif

    インターフェイスファイルの作成

    SWIGがラッパーを作成するために必要なインターフェイスファイルを用意します。hello.cpp、hello.hppと同じディレクトリにhello.iというファイルを作成し、以下のように記述します。

    hello.i
    %module RubyTest
    %{
    #include "hello.hpp"
    %}
    %include hello.hpp

    SWIGの実行

    コマンドプロンプトで以下のようなコマンドを実行すると、Helloクラスをラップしたhello_wrap.cxxが生成されます。生成されたファイルはプロジェクトに追加しておきます。

    swig -c++ -ruby hello.i

    ビルド

    この状態でビルドを行うと、成功すればRubyTest.dllが生成されるはずです。

    Rubyから呼び出してみる

    作成したモジュールを呼び出してみます。test.rbを作成し、以下のように記述します。

    test.rb
    require 'RubyTest'
    
    h = RubyTest::Hello.new
    puts "#{h.hello}"

    ruby test.rbで実行してみます。実行する前にtest.rbと同じディレクトリに先ほどビルドしたRubyTest.dllを生成しておきましょう。うまくいっていれば以下のように出力されるはずです。

    Hello, SWIG!
    12345

    おわりに

    Ruby向けの拡張ライブラリ作成は驚くほど簡単で拍子抜けしました。これなら重い処理はC++、さくっと書きたい部分はRubyのように組み合わせた開発が簡単にできますね。

    ただ、今回の方法ではクラスの構成が変わるたびにラッパを生成し直す必要があり非常に不便です。Makefileを使うという手もありますが、現在Visual Studioの設定でうまく自動化できないか試しているところです。うまくいった場合は、次回その方法を説明しようと思います。