Cで書くGaucheライブラリ


ボトルネックになりそうな処理を部分的にCで書くこともありそうですが、すでにCで書かれたライブラリ資産をGaucheから利用する際のバインディングを書く・・・というのが主な用途でしょうか

以下の文章は、Cで書かれたGaucheのライブラリ(と、Gauche本体)を見ながら書いてますが、私がまだ全然把握できてないので間違いを多分に含む可能性があります
(とどのつまり、あくまで個人的なメモ書きの粋を出ないわけです)


stubファイルって?

GaucheのC拡張モジュールは普通にCのコードでゴリゴリ書いても良い。
ただ、SchemeオブジェクトのCデータ型への変換や、Cで書いた関数をScheme関数へ登録するのは面倒なので、stubという見た目がSchemeっぽいコードの中にCのコードを埋め込んだような独特の形式が採用されている。

(define-cproc foo-proc (arg_scm_obj arg_str::<string> arg_int::<int>)
  (return "foo_c_proc"))

このstub形式はCのコードへと展開され、SchemeコードとCコードを糊付けする役割を担うことになる。 例えば上記の例の場合、foo-procというScheme関数を定義している。 このScheme関数は「なんらかのオブジェクト」「<string>オブジェクト」「<number>オブジェクト」を受け取り、それを

へ、それぞれデータの加工を行いfoo_c_proc関数へと渡し、その戻り値を返している。 foo_c_procのプロトタイプは以下のようになる。

 ScmObj foo_c_proc(ScmObj arg_scm_obj, ScmString * arg_str, int arg_int);

Gauche-devel-jp Cとの連携
Gauche:MeCab
ドキュメントがあまりない現状、こういった情報は重宝


Cコード中でのSchemeオブジェクトの生成に関して

ScmString*型のデータから文字列を(const char *)型で取得するには、Scm_GetStringConstを使用
逆に(const char *)型のデータをScmStringで取得する場合は、SCM_MAKE_STR_COPYINGマクロを使用する

 // Scheme文字列(ScmString *)をCのchar配列として取得
 const char * c_str = (char *) Scm_GetStringConst(scm_str);
 
 // Cのchar配列をコピーして、Schemeオブジェクトとして取得
 ScmObj obj = SCM_MAKE_STR_COPYING(c_str);

Scheme数値からデータをint型で取得するには、SCM_INT_VALUEマクロを使用
int型やlong型の数値をSheme数値型で取得するには、SCM_MAKE_INTマクロを使用

 // Scheme数値オブジェクトからint型でデータを取得
 int c_int = SCM_INT_VALUE(scm_int)
 
 // int型の数値からSchemeオブジェトを取得
 ScmObj obj = SCM_MAKE_INT(c_int);
 
 // ("bar" "foo") となるリストを返す
 ScmObj ls = Scm_MakeList(1, SCM_MAKE_STR_COPYING("foo"));
 return Scm_Cons(SCM_MAKE_STR_COPYING("bar"), ls);

これらの方法で生成したSchemeオブジェクトはすでにGCの対象となっている


引数チェックの際問題があれば、以下のように通知する
(別にそれ以外のエラー通知にも使えるけど)

 if (argv == 0.0) Scm_Error("Attempted to divide by zero");

対象のSchemeオブジェクトの型を調べるためのマクロ(の、ほんの一部)

 // 以下のような感じで
 if (!SCM_NUMBERP(obj)) Scm_Error("number required");

Scheme入出力ポートを扱う

 // バイト単位
 int Scm_Getb(ScmPort *port);
 
 // 行単位、戻り値はScmObjだが、実態はScmString *
 ScmObj Scm_ReadLine(ScmPort *port);
 
 // SchemeのEOFオブジェクトを返すにはこんな感じで
 if (Scm_Getb(iport) == EOF) return SCM_EOF;
 
 // 内部でSCM_PORT_CLOSED_Pによるチェックを行っている
 // すでに閉じられたポートならば、エラー処理を行う
 CLOSE_CHECK(port_obj);

Cコード中で独自のクラスを定義する

関数の戻り値のオブジェクト中に、Schemeからは普通にアクセスできないデータを含めたいという場面に出くわすこともある。
よくあるのがCの構造体だろうか、この構造体はできればSchemeコードから直接触って欲しくはない。
Cの関数中で構造体の生成を行い、その関数から脱出する前に破棄してしまえば問題はないんだが、一度生成した構造体を何度も使いまわしたいということも多い。
GaucheのC APIには構造体をラップしてクラス化する機能が提供されている。こうしてラップされた構造体のメンバには、直接Schemeからは触ることができないので、メンバにアクセスする必要がある場合は、その都度C関数に引き渡す必要がある。

アロケータ関数の登録

インスタンスが生成された場合の処理を記述する
SCM_DEFINE_BUILTIN_CLASSでクラスの中身を定義し、Scm_InitBuiltinClassでクラスをSchemeコード中から見えるように登録する。
Scm_InitBuiltinClassで登録したクラスのインスタンスを生成するだけの場合は、単にSCM_NEWで生成後、SCM_SET_CLASSでインスタンスの属するクラスを指定するだけでよい
ただし、例えばそのインスタンスが生成時に何らかの資源を所有するよう仕向けたいなら、それらの処理もアロケータに記述すればよい。

ちなみにScm_InitBuiltinClass以外にもクラスを登録する機構は用意されている。

ファイナライザ関数の登録

インスタンスは必要とされなくなった(とGaucheが判断した)時点で自動的に破棄される。
ただ、そのインスタンスが何らかのGCの管理下にない資源を持っていた場合、それを開放せずにインスタンスが破棄されるのは問題がある
(メモリの開放、ファイルを閉じる、ロックの解除・・・などなど)
対処としては、インスタンスが破棄された際に呼び出されるファイナライザ関数を設定しておいて、資源の解放をその関数にやらせればよい

 // 以下の例だと、fooがインスタンスでFoo_finalizeがそのインスタンスのファイナライザとなる
 Scm_RegisterFinalizer(SCM_OBJ(foo), Foo_finalize, NULL);

そのクラスのインスタンスが全て同じファイナライザ関数を利用するなら、ファイナライザの登録はアロケータ内でやればよい
(滅多にないと思うが)同じクラスに属するにも関わらず、インスタンスによって利用するファイナライザが異なる、インスタンス生成後に保持する資源が決定されるような場合にはアロケータの外側でファイナライザを登録することになる

// ちなみにファイナライザの例
// ScmFooの実態は構造体で、メンバ変数bar_db_handleを専用の関数で開放する場合
static void Foo_finalize(ScmObj obj, void *data)
{
  ScmFoo * foo = SCM_FOO(obj);
  bar_db_close(foo->bar_db_handle);
}

Last modified : 2008/06/29 02:12:43 JST