日曜日, 5月 30, 2010

CからOCamlを呼ぶ

今月ずっとやっていたことのまとめ。

CのコードからOCamlを呼び出す方法については、既に先人がいるのだけど、bigarrayと絡めるとあまり見当たらなかったので、まとめてみる。
OCamlのドキュメントだと、Chapter 18 Interfacing C with Objective Camlのあたりと、Chapter 29 The bigarray libraryのあたり。

基本編


CからOCamlのプログラムを呼び出す場合、大体以下の事を頭に入れておくとよいらしい。

  1. Cから呼び出したいOCamlの関数をCallback.registerに設定する

  2. C側でOCamlの関数を呼び出す前にmain関数でcaml_startup(argv)を呼ぶ

  3. caml_named_valueでCallback.registerに設定したOCaml関数を取り出す

  4. caml_callbackNでOCaml関数を呼ぶ

  5. OCamlとのやり取りにはvalue型の値が使われる

  6. OCaml関数への引数はCAMLparamNやCAMLlocalNで宣言(?)する(型によっては不要)

  7. Cの関数でCAMLparamNを通した値を戻り値にするときはCAMLreturnを使う(型によっては不要)



ではこれらを踏まえた簡単なサンプルを作ってみる。

ocaml.ml

let ocaml_process (a : int) (b : int) = a + b

(* 1. 呼びたい関数をCallback.registerに登録する *)
let _ = Callback.register "ocaml_process" ocaml_process

ただの足し算。

main.c

#include <stdio.h>

#include <caml/memory.h>
#include <caml/callback.h>

int main(int argc, char ** argv)
{
// 2. caml_startupを呼ぶ
caml_startup(argv);
// 3. OCamlの関数を取り出す
value *func = caml_named_value ("ocaml_process");

// 5. OCamlとのやり取りにはvalue型を使う
// Val_intでOcamlのintにします。
value a, b, result;
a = Val_int(2);
b = Val_int(4);
// 4. caml_callbackNでOCaml関数を呼ぶ
// 今回は引数が2つなのでN=2
// 引数が一つの場合はcaml_callbackと書き、数字はつけない
result = caml_callback2 (*func, a, b);

// 5. 戻ってきたvalue型の値をCの型に戻す
int print_result = Int_val(result);

printf("%d\n", result);
printf("%d\n", print_result);

return 0;
}

6番の7番についての記述が無いのはint型では必要ないから。必要となるのはblockedな値らしい。blockedってのが良く分かっていないが、とりあえずGCで管理されるような値という認識。
Int_valを使わない場合もあえてprintするようにしている。

これをビルドするためのMakefileはこんなの。ライブラリの位置はOSなどなどによって違うはず。

PROGRAM=call_ocaml
OCAML_LIB=/usr/lib/ocaml
OBJS = camlcode.o main.o

program : $(OBJS)
${CC} $(OBJS) -ldl -lm -L ${OCAML_LIB} -lasmrun -o ${PROGRAM}

main.o : main.c
${CC} -o $@ -c main.c

camlcode.o : ocaml.ml
ocamlopt -output-obj -o $@ ocaml.ml


さて、これを実行してみると、Int_valを使わない場合は13、使った場合は6がprintされることが分かる。
つまりOCamlのintを右に1シフトすると、Cのintになると。
詳しくはmlvalues.hを参照。Val_intの場合は何をやっているのかも分かったり。

argvにアクセス出来ない場合は?


一連の流れの中で、一番厄介なのはcaml_startup(argv)。main関数で呼べと言われても、mainに触れない場合なんてざらにあるわけで。何らかのフレームワークなんて使う場合は特にそう。それでもargvへのインターフェースが提供されていれば解決するけど、それも無いとお手上げ。
本当にそうなのか?というかargvへのインターフェースが無いと使えないなんて現実的じゃないだろう。と思って調べていたら、こんなページを発見。
無ければこんな感じに作ればいいらしい。

static void
ocaml_init ()
{
char *dummy_argv[2];
dummy_argv[0] = 0;
dummy_argv[1] = NULL;
caml_startup (dummy_argv);
}

ドキュメントにはこんなやり方書いていないのだけど、とりあえず動く。

bigarrayでやりとり


さて、世の中でかい数字の配列を使う機会というのはままあるわけですが、OCamlの配列は4M程度までしか扱えないらしい。4Mもあれば十分な気もするけど、足りない場合もあるし、充分なのか足りないのか良く分からない場合というのもある。
そんなときはbigarrayというものを使うそうな。これを使うと整数と浮動小数点数限定で16次元までの制限はるものの、横のサイズに制限は無しな素敵な配列が使えるらしい。
ついでにbigarrayのメモリレイアウトはCやFortrunと互換性があるので、これら2つの言語とOCamlをくっつけるときにも便利。

説明については、マニュアルのが詳しいので、Cの配列をbigarrayにするサンプルコードを示す。
value call_ocaml(){
int array[5] = {0, 1, 2, 3, 4};
value result;
CAMLparam1 (result);
CAMLlocal1 (barray);
long dims[1];
dims[0] = 5;
barray = alloc_bigarray(BIGARRAY_UINT8 | BIGARRAY_C_LAYOUT, 1, array, dims);
value *func = caml_named_value ("ocaml_process");
result = caml_callback (*func, barray);
CAMLreturn(result);
}

alloc_bigarrayがCの配列からbigarrayを作る関数。第1引数で配列の要素の型と、メモリレイアウトを指定。第2引数で配列の次元、第3引数にbigarrayに変換したいCの配列、第4引数に各次元の配列サイズを配列にして渡す。
厳密には「作る」のではなくポインタをOCamlに渡せる形にすると言った方が良いと思う。
それとbigarrayにすると言っても、Cの世界ではvalue型として扱われる。
OCaml関数でbigarrayを返り値にした場合も、Cで受けとるときはvalue型になる。つまりintの場合と同様に変換が必要になる。
biggarrayの場合はData_bigarray_val(big_array_value)というようにする。

簡単なサンプルコードを書いてみる。
ocaml.ml
let ocaml_process barray =
barray.{0}<-1;
barray;;

let _ = Callback.register "ocaml_process" ocaml_process

第1要素を1にしているだけ。

main.c
#include <stdio.h>
#include <caml/memory.h>
#include <caml/callback.h>
#include <caml/bigarray.h>

value call_ocaml(){
int array[5] = {0, 1, 2, 3, 4};
value result;
CAMLparam1 (result);
CAMLlocal1 (barray);
long dims[1];
dims[0] = 5;
barray = alloc_bigarray(BIGARRAY_UINT8 | BIGARRAY_C_LAYOUT, 1, array, dims);
value *func = caml_named_value ("ocaml_process");
result = caml_callback (*func, barray);
CAMLreturn(result);
}

int main(int argc, char ** argv)
{
caml_startup(argv);
int* result = (int*)Data_bigarray_val(call_ocaml());
printf("%d\n", result[0]);
return 0;
}

5要素の一次元配列を渡す。

Makefile
PROGRAM=call_ocaml
OCAML_LIB=/usr/lib/ocaml
OBJS = camlcode.o main.o

program : $(OBJS)
${CC} $(OBJS) -ldl -lm -L ${OCAML_LIB} -lasmrun -lbigarray -o ${PROGRAM}

main.o : main.c
${CC} -o $@ -c main.c

camlcode.o : ocaml.ml
ocamlopt -output-obj bigarray.cmxa -o $@ ocaml.ml


ポインタを渡している事を確認するのであれば、mainで配列を作ってOCamlからの返り値を投げ捨てた上で、元の配列を覗いてみるとよい。

日曜日, 5月 09, 2010

Ubuntu 10.04 入れた

10.04はLucid Lynx(透き通った山猫)。

特に問題はなくインストールできた。5/1に入れて一週間ほどたつけど、特に問題無し。
閉じるボタンの位置が変わったのはちょっと驚いたけど、普段はAlt + F4で閉じているので、そのうち気にならなくなるはず。

あえて不満を言うと、Fx3.5上のTwitterのWebインターフェースでの入力がやたら遅いという事と、デスクトップの「名前順に整理する」でアイコンがどこかにすっ飛んでいくことぐらい。
もしかしたらコアラの時からそうだったかもしれないけど、気になるほどではないかな。

ところで、透き通った山猫ってのはチェシャ猫みたいなものだろうか。