OCamlからCの関数を呼ぶなどする

いまOCamlでゴニョゴニョするプログラムを作っているところなのですが、まあ普通にLinuxからOCamlでプログラミングするならJaneStreet corecore_extendedがあれば割と何でもできるんです。でもぼくが使っているのはMacOSなんです。しかもパッケージ管理とか嫌なので基本的にMacPortsを使っています*1。そんな軟弱な僕ですがやっぱり好きなシステムコールを使いたい!ということでOCamlからCのコードを呼ぶ方法を調べました。

基本的には、公式ドキュメントのInterfacing C with Objective Camlに従って適当にサンプルソースを写経していけば正しくつくれると思います。基本的には、OCamlの礼儀に従ってCで書いた関数がそのままOCamlから呼べるようになっています。RubyみたいにメソッドオブジェクトをblessおっとこれはPerlだっけ失礼、registerしなくても、ネイティブコンパイルでリンクしたときに見つけるようになっているみたいです。というわけでざっくりした書き方ですが、まずはインターフェースを定義します。sample.mliはこんな風にかきます:

val hello : string -> unit
val env : unit -> string
val hoge : unit -> (int * int) array

で、この関数をみっつともCで実装しちゃいます。でも直接実装できるわけじゃなくて、どうやらコンパイラは .mli ファイルをみて実装を .ml ファイルに探しにいって、…という sample.mlをば:

external hello : string -> unit = "hello"
external env : unit -> string = "env"
external hoge : unit -> (int * int) array = "hoge"

簡単でしょ?これはOCamlのSample.helloとかの型と、その名前を定義していることになります。つまりSample.hogeという関数の実体は、外部(external)なCの関数hogeというシンボルで見つけてくだせーということです。
ここで注意しないといけないのは、arrayを使っているところ。listじゃないのはどうやら理由がありそうなんですが。で、これが観に行く先のCの関数を書いてやります:

CAMLprim value hello(value arg){
  CAMLparam1(arg);
  printf("hello %s!\n", String_val(arg));
  CAMLreturn(Val_unit);
}

CAMLprim value env(void){
  CAMLparam0();
  CAMLreturn(caml_copy_string(__VERSION__));
}

CAMLprim value hoge(void){
  CAMLparam0();
  CAMLlocal2(v_res, v_flags);
  int i=10;
  v_res = caml_alloc(i, 0);
  while (--i >= 0) {
    value v_ev;
    v_ev = caml_alloc_small(2,0); 
    Field(v_ev, 0) = Val_int(i);
    Field(v_ev, 1) = Val_int(23);
    printf("putting (%d,%d)\n", i, 23);
    Store_field(v_res, i, v_ev);
  }
  CAMLreturn(v_res);
}

関数名そのまま書いていいところが気前がよいですね。多分dlopen(2)とかでそのまま引っ張っているのでしょう。C++コンパイラでやるときはextern "C"を忘れずに。いくつか注意するポイントをば:

  • CAMLprim修飾子は、OCamlから呼び出すヤツ(*.mlに名前を書いたヤツ)だけにつけてやります。まあexportするっていうくらいの意味ですね。
  • CAMLparamN系の関数は、引数で与えたvalueをOCaml in Cのお作法で扱うためのおまじないです。よーわかっておりません。たとえ引数がunitでもこれを0でやっておかないとコンパイルできません。
  • CAMLlocalN系の関数は、let xx = yy in のような感じでローカル変数を定義というか束縛してくれます。この値をそのままreturnすることができるように、いろいろやっているみたいです。
  • caml_allocは割と生のメモリ領域をGCで扱えるように確保してくれるモノです。
  • caml_alloc_smallも同様。JS core extendedからパクってきましたが、自信のある場合のみ使った方がよいかと思います。
  • FieldとかVal_intとかStore_fieldとかもマクロです。やってることはどうやら代入とかポインタの張り替えのようですが、マクロで隠蔽しておいた方がいろいろとやりやすいことがあるでしょう。
  • CAMLreturnこの関数内のどこかのvalueをリターン(OCaml的にいうと最後に評価、でいいのかな?)してくれます。

いっちばん注意すべきことは、この引数や戻り値で、Cの世界とOCamlの世界で型が整合しているかどうかをコンパイラもリンカもどうやらチェックしてくれないらしいという点です。ここを間違えると、私のようにlistとarrayを間違っていることに気付かずにハマり続けることになります。くれぐれもご注意いただきたく…。
今回私が必要としたのはこの程度の知識です。キニナル方は公式ドキュメントをご覧いただければと思います。

*1:一度Rubyでgemsを使って、やりたいことはひとつなのに大量の意味不明のパッケージを入れる羽目になってもう構成とかワケ分からんとかそういうことになって以来、依存解決が大変なモノは入れないことにしています。