Writing Gauche Extension Module in C
はじめに
Gauche の拡張を C で書く際のメモです。 そもそもは『C のライブラリを Gauche Extention としてラップしたい』という動機があります。 その際に、あちこち躓いたので、整理を兼ねてまとめてみました。 ですので、いろいろと間違えていることもあると思います。どうぞご指摘ください:-)
環境
このメモは以下の環境を想定しています。API は将来変更になるかもしれません。 (おそらく変更されるでしょう)
Gauche/C API のドキュメント
いまのところ、Gauche 自体のドキュメントには拡張をの書き方や内部のインターフェイスに関するドキュメントはありません。おそらく、今後変わっていくためでしょう。 それでも、手がかりはいろいろとあります。
まず、次のブログエントリーがわかりやすかったです。
つぎに、FFI については practical-scheme にメモがあります。
さらに、Gauche のソースの examples 下の spigot と mqueue-cpp が参考になります。
spigot: C で書かれた拡張
Gauche-0.9.3/example/spigot
mqueue-cpp: C++ で書かれた拡張
Gauche-0.9.3/example/mqueue-cpp
スケルトン
基本的には gauche-package でスケルトンを作っていくことになります。 詳しくは "gauche-packageを利用したgaucheの拡張モジュールの作り方"" にある通りなので、ここでは手順だけメモしておきます。
$ gauche-package generate example
$ cd example
$ ls
DIST Makefile.in configure.ac example.c example.h example.scm examplelib.stub test.scm
$ ./DIST gen
$ ls
DIST Makefile.in autom4te.cache configure configure.ac example.c example.h example.scm examplelib.stub test.scm
$ ./configure
$ make
$ make check
これで一通りの Gauche Extention Module ができました。
ScmForeignPointer
Gauche:FFI にあるように C の構造体をラップするには ScmForeignPointer マクロを使います。
ScmForeignPointerオブジェクトは、(staticにではなく)初期化時にScmClass構造体を作るんで、 ヘッダで宣言しておくのはクラス構造体へのポインタだけでいい。 Cソース側では初期化ルーチンで Scm_MakeForeignPointerClass を呼ぶと 新しいScmClassへのポインタが帰ってくるのでそれを保存しとく。 SCM_CLASS_DECLとかは不要。boxerについてもScm_MakeForeignPointerが有る程度面倒を見てくれる。
Gauche:FFI より引用
構造体のラップ
実際に C の構造体をラップするときにはおそらく次のようなものが必要です。
- C の構造体に対応する ScmClass と type
- xx_print と xx_cleanup 関数
- モジュールの初期化の設定
(※ 僕が勝手にこうするんじゃないかな?と推測したものなので、過不足があるかもしれません。)
例
次のように example 構造体があるとします。これを Gauche で扱えるようにラップしていきます。 説明は適当です:p
struct example {
int x;
};
マクロ定義
ExampleClass
と struct example
を扱うマクロを定義します。
これらは下の examplelib.stub
内の define-type
で使います。
example.h
extern ScmClass *ExampleClass;
#define EXAMPLE_P(obj) SCM_XTYPEP (obj, ExampleClass)
#define EXAMPLE_UNBOX(obj) SCM_FOREIGN_POINTER_REF (struct example*, obj)
#define EXAMPLE_BOX(obj) Scm_MakeForeignPointer (ExampleClass, obj)
Gauche 内での表示とメモリ解放のための関数
たぶん、そんな感じの関数だと思います。これらの関数は下の Scm_MakeForeignPointerClass
の第3引数と第4引数として渡します。
example.c
ScmClass *ExampleClass;
static void example_class_print (ScmObj obj, ScmPort* out, ScmWriteContext* ctx)
{
struct example* e = EXAMPLE_UNBOX(obj);
Scm_Printf(out, "#<example-class \"%p\">", e);
}
static void example_class_cleanup (ScmObj obj)
{
struct example* e = EXAMPLE_UNBOX(obj);
free(e);
}
タイプの定義
struct example*
に対応する Gauche内の型(?)を定義します。
examplelib.stub
(define-type <example-class> "struct example*" "example class" "EXAMPLE_P" "EXAMPLE_UNBOX" "EXAMPLE_BOX")
初期化
example.c
内で 初期化のための関数Scm_Init_example
というのがあります。
このなかに次のように ExampleClass
を Gauche が扱うために必要なものを教えてあげます。
example.c
ExampleClass =
Scm_MakeForeignPointerClass(mod,
"<example-class>",
example_class_print,
example_class_cleanup,
SCM_FOREIGN_POINTER_KEEP_IDENTITY | SCM_FOREIGN_POINTER_MAP_NULL);
まとめ
これで C の構造体をラップできたはずです。 あとはこれを Gauche から扱うための関数を書いてあげればいいでしょう:D
とは書いたものの、各関数やマクロが何をしているかなんとなくしかわかってません:p
本当にこれでメモリリークしてないのか?とか気になります...がひとまずこんなところで:p
Gist
これらをまとめたものは Gist にあげておきました。