GaucheをCで拡張したいって言ったらどうする?

どうもしないですよね。
じゃあ、今日はこの辺で終わります。

と。

大概の人なら、面倒臭いので軽くあしらうでしょう。
僕ですか?僕は、大抵面倒臭い方の立ち位置にいるので、あまりそういう経験はありません。

まあ、書く内容はタイトル通りなんですが、一番シンプルな形で掲載するつもりなので、その辺はご了承のほど、よろしくお願いいたします。

きっかけはと言うと、FFTW3をGaucheで使うためのラッパーをもらったことがあって、「へえ、こんなことができるんだあ」と思ったことに由来します。それで、手元にあったWAVファイル操作のCライブラリのラッパーを書いたりもしたんですが……。
すみません。今回はどちらも掲載できません。
FFTW3の方は作者が別の方ですし、WAVはCのライブラリが自分のではないので、結果として両方公開できないということです。
ちなみに、PythonでFFTW3を使うとか言う記事を書いたことがありますが、FFTW3自体の説明が不要でしたし、ラッパー本体も自作のものだったので公開できたんです。
従って、今回は以下の点に関するだけの記事となります。

  • ラッパーの雛形的なもの

そんな訳で、相変わらず他のサイト様の方がよりわかりやすく充実してるので、この記事の存在意義について異論を投げかけたくなるかもしれませんが、振り上げた拳はとりあえず下ろして、落ち着いてみましょう。
それでもダメなら……しょうがないので、今日はもう寝ましょう。朝になれば、嫌なことは忘れているかもしれません。

構成は、

  • 雛形
  • おまけ
  • まとめ

です。

それでは雛形の紹介です。

ラッパー本体の雛形:

最初に、こちらの動作環境はUbuntu、およびFreeBSDです。あらかじめ、ご理解頂けると幸いです。

単純な例をあげたいので、そうですね、まずはどんな風に使うのかっていうイメージからいきましょう。
今回つくるモジュールの名前は、wrapです。

(use wrap)

(wrap-hello 4)

これをtest.scmとでもして保存し、ラッパーモジュールが同じディレクトリにあるとして、実行方法と結果は、

% gosh -I. test.scm
Hello World!
Hello World!
Hello World!
Hello World!
% 

です。まあ、やってることは単純です。

それでは、ラッパーモジュールがどのように作られるかです。今回作るモジュールはwrapとしました。
それを前提として、準備すべきファイルは4つあります。

  • wrap.c
  • wrap.h
  • wraplib.stub
  • wrap.scm

以上。それぞれの名前に注目してください。名前に注意するのはファイル名だけでなく、この後のソースコードにおいても共通です。
断っておきますが、stubファイルは別に聖人を一撃で倒せる霊装ではありません
唐突に何を言い出すんだと思った方は、パッシングスルーで鮮やかに切り抜けてください。

さて。

では詳細な説明は他所様におまかせして、ソースコードだけ貼っときます。てゆうか、他所様の説明を僕が読んでここに書くという作業には伝言ゲームのお約束が確実に働くので、その危険性を最初から回避しようということです。

まず、wrap.c

#include <gauche.h>
#include <gauche/extend.h>
#include "wrap.h"

int HelloWorld(int n){
    for(int i=0;i<n;i++)
        printf("Hello World!\n");

    return 0;
}

ScmObj WRAP_hello(int n){
    HelloWorld(n);
}

ScmObj Scm_Init_wrap(){
    ScmModule *mod;
    SCM_INIT_EXTENSION(wrap);
    mod = SCM_MODULE(SCM_FIND_MODULE("wrap",TRUE));
    Scm_Init_wraplib(mod);
}

wrap.h

extern ScmObj WRAP_hello(int n);

extern void Scm_Init_wraplib(ScmModule *module);

wraplib.stub

"#include \"wrap.h\""

(define-cproc wrap-hello (n::<int>)
  (call "WRAP_hello"))

最後に、wrap.scm

(define-module wrap
  (export wrap-hello))
(select-module wrap)
(dynamic-load "wrap")
(provide "wrap")

コンパイルは、例のごとくMakefileのせときます。

GOSH           = /usr/local/bin/gosh
GAUCHE_CONFIG  = /usr/local/bin/gauche-config
GAUCHE_PACKAGE = /usr/local/bin/gauche-package

DISTFILES = *~ \#*\#* .\#*
GEN_FILES = wrap.so

wrap_SRCS = wrap.c wraplib.stub

all : $(GEN_FILES)

wrap.so: $(wrap_SRCS)
	$(GAUCHE_PACKAGE) compile --verbose wrap $(wrap_SRCS)

clean :
	rm -rf $(DISTFILES)

distclean: clean
	$(GAUCHE_PACKAGE) compile --clean wrap $(wrap_SRCS)

makeすると、うまくいけばwrap.soが生成されます。これが、ラッパーモジュールになります。

さて、細かい説明は省くにしても、せめて自分が気になった点ぐらいはまとめておこうかと思います。

  • wrap.cでは、Cの関数であるHelloWorld()をGaucheから呼び出すためのラッパー関数として、WRAP_hello()を定義しています。
  • 同じくwrap.cにおいて、最後のScm_Init_wrap()の部分なんですが、『wrap』の部分だけが任意です。それ以外を変えると多分エラー吐かれます。(任意とは、たとえば『c2gosh』とかでラッパーモジュールを名づけたいときに、『wrap』の部分を『c2gosh』に変えるということです)
  • wraplib.stubでは、Cで定義したラッパー関数名とGaucheにおける関数名とを繋ぎ合わせています。つまり、ここで定義した名前だけがGaucheから見えることになります。
  • Makefileは……まあ、こんなものです。

ちなみに、コンパイルにおいて、たとえばFFTW3などを使う場合には、以下のような方法もあります。
Makefileにて:

CFLAGS = -O2 `pkg-config --cflags fftw3`
LIBS   = `pkg-config --libs fftw3`

wrap.so : $(wrap_SRCS)
	$(GAUCHE_PACKAGE) compile --verbose --cflags="$(CFLAGS)" --libs="$(LIBS)" wrap $(wrap_SRCS)

同じ部分は省きました。ここでのwrapがFFTW3のラッパーだったら、と仮定してのものです。

おまけ:

あんまりにも、あんまりな例だったので。
導入篇を終えて、今度は入門篇です。

まず、GaucheからCのラッパー関数foo()にリストを渡したいとき、

(define lis '(1 2 3 4))

(foo (list->vector lis))

のように、Vectorに変換する方法しか僕は知りません。ちなみにfoo()では、受け取ったリストを配列として扱うこととします。

あるラッパー関数bar()から配列をGaucheへ返したい場合には、

ScmObj WRAP_bar(){
    int i;
    double d[10];
    ScmObj v, *e;

    for(i=0;i<10;i++)
        d[i]=i*3.14;

    v = Scm_MakeVector(10, Scm_MakeFlonum(0));
    e = SCM_VECTOR_ELEMENTS(v);

    for(i=0;i<10;i++){
        e[i]=Scm_MakeFlonum(d[i]);
    }

    return v;
}

とすれば良いんですが、ここで返されるのがGauche側ではVectorであることに注意してください。従って、リストとして戻り値を扱うには、

(print (vector->list (wrap-bar-pi)))

みたいにしなければなりません。これも、僕の知識不足が故のことだと思います。

まとめ:

さて、では以上の内容をまとめたものを載せておきます。
何よりも、我が身のために。流石に上の例だけでは分からなすぎます。

wrap.c

#include <gauche.h>
#include <gauche/extend.h>
#include "wrap.h"

int HelloWorld(int n){
    for(int i=0;i<n;i++)
        printf("Hello World!\n");

    return 0;
}

ScmObj WRAP_hello(int n){
    HelloWorld(n);
}

ScmObj WRAP_bar(){
    int i;
    double d[10];
    ScmObj v, *e;

    for(i=0;i<10;i++)
        d[i]=i*3.14;

    v = Scm_MakeVector(10, Scm_MakeFlonum(0));
    e = SCM_VECTOR_ELEMENTS(v);

    for(i=0;i<10;i++){
        e[i]=Scm_MakeFlonum(d[i]);
    }

    return v;
}

ScmObj WRAP_foo(ScmObj vector, int vector_len){
    int i;
    double d[vector_len];
    for(i=0;i<vector_len;i++)
        // vectorのi番目の要素を取り出し、double型にキャスト
        d[i]=Scm_GetDouble(Scm_VectorRef((ScmVector *)vector, i, Scm_MakeFlonum(0)));

    for(i=0;i<vector_len;i++)
        printf("%lf\n",d[i]*10);

    // これがないと何故かエラー……なんで?
    return 0;
}

/* schemeモジュールとして登録? */
ScmObj Scm_Init_wrap(){
    ScmModule *mod;
    SCM_INIT_EXTENSION(wrap);
    mod = SCM_MODULE(SCM_FIND_MODULE("wrap",TRUE));
    Scm_Init_wraplib(mod);
}

wrap.h

extern ScmObj WRAP_hello(int n);
extern ScmObj WRAP_bar();
extern ScmObj WRAP_foo(ScmObj vector, int vector_len);

extern void Scm_Init_wraplib(ScmModule *module);

wraplib.stub

"#include \"wrap.h\""

(define-cproc wrap-hello (n::<int>)
  (call "WRAP_hello"))

(define-cproc wrap-bar-pi ()
  (call "WRAP_bar"))

(define-cproc wrap-foo (vector vector_len::<int>)
  (call "WRAP_foo"))

wrap.scm

(define-module wrap
  (export wrap-hello
          wrap-bar-pi
          wrap-foo))
(select-module wrap)
(dynamic-load "wrap")
(provide "wrap")

テスト用のtest.scm

(use wrap)

(wrap-hello 4)
(print (vector->list (wrap-bar-pi)))

(define test-lis '(1 2 3 4 5 6 7 8 9 10))
(wrap-foo (list->vector test-lis) (length test-lis))

コンパイル先ほどのMakefileが流用できます。

実行と結果

% gosh -I. test.scm
Hello World!
Hello World!
Hello World!
Hello World!
(0.0 3.14 6.28 9.42 12.56 15.700000000000001 18.84 21.98 25.12 28.26)
10.000000
20.000000
30.000000
40.000000
50.000000
60.000000
70.000000
80.000000
90.000000
100.000000

これをうま〜く応用すれば、FFTW3のラッパーは存外簡単に書けます。

結論:

こんなこともできるんだなあと痛く感動した。