SWIGでiostreamを引数にとる関数のラップ(Rubyの場合)

とあるC++プログラムをRubyから使いたいということになりました。こんな時はCまたはC++のプログラムを、スクリプト言語から呼び出せるよう、ほぼ全自動でラッパーを生成してくれるSWIGの出番です。このSWIGはかなり賢いので、そんじゅそこらのプログラムでしたら、2,3行のインターフェイスファイルを書くだけで、スクリプト言語とC/C++の間を取り持ってくれます。
しかしながら困ったことに、今回はiostreamを引数にとる関数に遭遇してしまいました。intやchar *といった基本的な型であれば、スクリプト言語のオブジェクトを自動的にそれらの型に変換してくれるのですが、iostreamともなると少々手を入れてやる必要がありました。
なお、C++のiostreamをスクリプト側から使いたいという要望でしたら、こちらは簡単で、標準添付のstd_iostream.iというインターフェイスファイルが要望を満たしてくれます。

まずはじめに、実装が完成した際のプログラムのコードが、こんなにもエレガントになるということを語らせてください(笑)。以下に例を示します。invoked.hがSWIGでラップされる側、invoker.rbがそれを呼び出すRubyのコードです。

// invoked.h
#include <iostream>
struct InvokedCppClass {
    void stream_op(istream &in, ostream &out){
        // read from in and write to out ...
    };
};

// invoker.rb
require 'invoked.so'
invoked = Swig_Wrapped_Module::InvokedCppClass::new
open(src_file){|src|
    open(dist_file, 'w'){|dist|
        invoked.stream_op(src, dist)
    }
}

Rubyの文法と融合しているのが素敵だと思いませんか? 少なくとも僕は満足しています(笑)。

このようにラップをするためには、残念ながらSWIGで水面下でバチャバチャする必要があります。基本方針としては、typemapで呼び出し時に自動的にRubyのIOオブジェクトがiostreamに変換されるようにすることです。そのために、iostreamを生成するのに必要となるstreambufを使ってRubyのIOオブジェクトをラップするクラスを作成しました。この主要な部分をインターフェイスファイルに記述した結果、arg_std_iostream.iのようになりました。

使い方としては以下のようなインターフェイスファイルを作成してSWIGで処理をします。

// invoked.i
%module Swig_Wrapped_Module
%{
#include "invoked.h"
%}

%include arg_std_istream.i
%include invoked.h

確認した限りではファイルをストリームの元とする際は正常に動作しています。しかしcygwinのRubyにおいて、IO::pipeで作成したストリームでは読み書きするデータ量が多いとハングしてしまう問題を発見しました。おそらくcygwinならびにM$特有の問題だと思われるので、今回のコードが悪さをしているのではないと信じたいです。

またSWIGのRubyは多重継承を完全にはサポートしていないようで、ストリームはistreamまたはostream、すなわち読み書きのどちらか一方に絞る必要がありました。この記事のタイトルを正確に書くならば『...istreamまたはostreamを引数にとる...』ですね。

July 01, 2009 23:59 fenrir が投稿 : 固定リンク | | このエントリーを含むはてなブックマーク

コメント

コメントする