Home

C関数をラップしてOCamlに接続する方法 (How to wrap C functions to OCaml)

このドキュメントは、OCamlからC関数を呼び出す方法を紹介する小さなチュートリアルです。 CとOCamlが混在したアプリケーションを作成したり、Cで書かれたライブラリ内の必要な関数を呼び出したり、あるいは、Cで書かれたライブラリに対する完全なバインディングを書いたりしたい場合に役立ちます。


目次


クイックスタート (Quick start)

可能なかぎり簡単なケースとして、整数パラメータを一つ伴うC関数のコールから始めましょう。 .mlファイルに次のような行を書くことで実現できます。

external send_an_int: int -> unit = "get_an_int"

externalキーワードは、OCamlワールドの外側からその関数が来たことをコンパイラに指示します。 コンパイラが必要としているのは、OCamlの領域から見えるその関数の名前とその型、そして呼び出されるC関数の名前です。

OCamlの側で引用符で括られて指定されている名前を持つC関数はCのソースファイルで定義します。 この関数はCAMLprim valueという戻り値の型を持たねばならず、またパラメータの型はvalueでなければなりません。 OCamlからCの関数へ渡されるパラメータは全てvalue型です。 インクルードヘッダ<caml/mlvalues.h>では、value型をCのネイティブ型へと変換するマクロを提供しています。 このケースではCの整数へと変換するので、そのマクロはInt_val()です。 そして、OCaml関数の戻り値はunitですので、このC関数はunitを返さねばなりません。 それはVal_unitマクロで行ないます。

#include <stdio.h>
#include <caml/mlvalues.h>

CAMLprim value
get_an_int( value v )
{
    int i;
    i = Int_val(v);
    printf("%d\n", i);
    return Val_unit;
}

もちろん、標準ライブラリやそのほかのライブラリなど、必要なCヘッダをインクルードすることは考えてください。

CとOCamlのコンパイル (Compiling C and OCaml together)

次にこの二つのファイルを一緒にコンパイルします。 必要なコマンドラインはこのようになります。

ocamlc -i funs.ml > funs.mli
ocamlc -c funs.mli
ocamlc -c funs.ml
ocamlc -c wrap.c
ocamlmklib  -o  _wrap_stubs  wrap.o
ocamlc -a  -custom  -o funs.cma  funs.cmo  -dllib dll_wrap_stubs.so

でも、簡単なMakefileを使うともっと扱いやすくなります。

wrap.o: wrap.c
        ocamlc -c $<

dll_wrap_stubs.so: wrap.o
        ocamlmklib  -o  _wrap_stubs  $<

funs.mli: funs.ml
        ocamlc -i $< > $@

funs.cmi: funs.mli
        ocamlc -c $<

funs.cmo: funs.ml funs.cmi
        ocamlc -c $<

funs.cma:  funs.cmo  dll_wrap_stubs.so
        ocamlc -a  -o $@  $<  -dllib -l_wrap_stubs

funs.cmx: funs.ml funs.cmi
        ocamlopt -c $<

funs.cmxa:  funs.cmx  dll_wrap_stubs.so
        ocamlopt -a  -o $@  $<  -cclib -l_wrap_stubs

clean:
        rm -f *.[oa] *.so *.cm[ixoa] *.cmxa

wrap.cファイルの中でライブラリを使う場合には、コンパイルの手順を余分に追加してそのライブラリをリンクする必要があります。 これは後の別のパラグラフで述べます。

ocamlc -c wrap.cというコマンド部分については、次に示すどちらかと置き換えることで、gccに対する引数を追加指定することができます。

ocamlc -c -cc "gcc -o wrap.o" wrap.c
gcc -c -I"`ocamlc -where`" wrap.c

また、ocamlc -c wrap.cというコマンドはocamlc -verbose -c wrap.cと置き換えてみることをお勧めします。 こうすることで、ocamlcコンパイラは実行する内部コマンドを全て出力するようになります。

そうしてトップレベルで.cmaファイルを開けば、結果をテストすることができます。

% ocaml funs.cma

# open Funs;;
# send_an_int 9 ;;
9
- : unit = ()

あるいは、テストスクリプトを使う手もあります。

#load "funs.cma" ;;
open Funs ;;

let () =
  send_an_int 5;
;;

中間の.cma/.cmxaバインディングを作ることなく、CとOCamlが混在したアプリケーションを作成したいだけの場合、両方のオブジェクトを直接コンパイルすることもできます。

make wrap.o
make funs.cmx
ocamlopt wrap.o funs.cmx -o myapp
./myapp

OK、全てうまくいったのでしたら、そのほかの型を変換する方法について見ていきましょう。 浮動小数点数と文字列です。

external send_a_float: float -> unit = "get_a_float"
external send_a_string: string -> unit = "get_a_string"
CAMLprim value
get_a_float( value v )
{
    float f;
    f = Double_val(v);
    printf("%f\n", f);
    return Val_unit;
}

CAMLprim value
get_a_string( value v )
{
    char *s;
    s = String_val(v);
    printf("%s\n", s);
    return Val_unit;
}

Double_val()という変換マクロはCのfloatとdoubleの両方に対して使うことができます。


基本型のリターン (Returning Basic Types)

さて、CからOCamlへこれらの基本型を返す方法について見ていきましょう。

external get_an_int: unit -> int = "send_an_int"
CAMLprim value
send_an_int( value unit )
{
    int i;
    i = 2.6;
    return Val_int(i);
}

変換マクロのVal_int()は先に見たマクロとよく似ています。 これによってCの整数をOCamlの整数値へと変換します。

Cコードでlong整数を使用している場合には、Int_val()の代わりにLong_val()を使ってください。

同様に、Bool_val()を使って、OCamlのbooleanをCの整数に変換することができます。 また、Cの整数をOCamlのbooleanに変換するには、Val_bool()を使います。 簡便のためにVal_trueVal_falseというマクロもあります。

メモリに割り当てられるデータ型 (Allocated Types)

浮動小数点数と文字列は整数とは少し異なったやり方をします。 というのも、これらの型はOCamlにおいてはメモリを割り当てなければならないからですが、ここでOCamlについて知っておくべき重要な事柄の一つは、メモリ割り当てが行なわれる(新しい値が生成される)とガベージコレクションが行なわれるかもしれないということです。 もしそのメモリ割り当てがCのreturnステートメントのある最後の箇所で与えられているのであれば全く問題ありません。 しかし、後で見るようなそれ以外のケースにおいては、余分なステートメントを使わなければならないということに注意してください。

external get_a_float: unit -> float = "send_a_float"
external get_a_string: unit -> string = "send_a_string"
CAMLprim value
send_a_float( value unit )
{
    double d;
    d = 2.6;
    return caml_copy_double(d);
}

CAMLprim value
send_a_string( value unit )
{
    char *s = "the walking camel";
    return caml_copy_string(s);
}

新たにOCamlの値を割り当てた後にもまだ入力値を使うようなもっと複雑な関数の場合には、その新しいOCamlの値が割り当てられたときに、ガベージコレクションされることによって入力値を使うことができなくなるというリスクがあります。 そういった場合には、CAMLparamN()(ここでNはパラメータの数)というマクロを関数の先頭で使わなければなりません。 また関数の最後では、returnの代わりにCAMLreturn()を使わなければなりません。 ローカルに割り当てられる値に対しては、CAMLlocalN()という宣言を使ってください。 たとえば、前述のsend_a_floatとsend_a_stringという関数は次のように書くことができます。

CAMLprim value
send_a_float( value unit )
{
    CAMLparam1(unit);
    CAMLlocal1(ml_f);
    double d;
    d = 2.6;
    ml_f = caml_copy_double(d);
    CAMLreturn(ml_f);
}

CAMLprim value
send_a_string( value unit )
{
    CAMLparam1(unit);
    CAMLlocal1(ml_s);
    char *s = "the walking camel";
    ml_s = caml_copy_string(s);
    CAMLreturn(ml_s);
}

これらのマクロを使う必要があるのか無いのか確信できないときには、ガベージコレクターとトラブらないままでいるようにマクロを使ったほうが良いです。 もし仮に必要なかったとしてもそれらのマクロが問題を起こすようなことはありません。 後述するケースで、厳密には不要なのにこれらのマクロを使用しているのはこういった理由からです。 そしてまた、多くのOCamlユーザが、必要な時にマクロを使うことを忘れてしまうという危険を避けるためにこれらのマクロは常に使うということを勧めています。

また、これらのマクロ群を使うときにはマクロが定義されている次のヘッダを追加してください。

#include <caml/memory.h>

構造を持ったデータ型 (Constructed Types)

さて次は、タプルや配列、リスト、レコードといったようなさらに複雑な構造を見たり作ったりする方法を見ていきましょう。

タプルへのアクセス (Accessing Tuples)

OCamlのタプルの内容にアクセスするために、タプルの各部にアクセスするField()マクロが用意されています。 その時、各フィールドは、ネイティブのCの型へ変換する必要のある通常の型の値です。

external inspect_tuple: int * float * string -> unit = "inspect_tuple"
CAMLprim value
inspect_tuple( value ml_tuple )
{
    CAMLparam1( ml_tuple );
    CAMLlocal3( vi, vf, vs );

    vi = Field(ml_tuple, 0);
    vf = Field(ml_tuple, 1);
    vs = Field(ml_tuple, 2);

    printf("%d\n", Int_val(vi));
    printf("%f\n", Double_val(vf));
    printf("%s\n", String_val(vs));

    CAMLreturn( Val_unit );
}

トップレベルで結果が期待通りかどうかを検査することができます。

# inspect_tuple (3, 2.4, "functional") ;;
3
2.400000
functional
- : unit = ()

また、このCの部分はもっと簡潔に書いて同じ結果を得ることができます。

CAMLprim value
inspect_tuple( value ml_tuple )
{
    printf("%d\n", Int_val(Field(ml_tuple,0)));
    printf("%f\n", Double_val(Field(ml_tuple,1)));
    printf("%s\n", String_val(Field(ml_tuple,2)));
    return Val_unit;
}

(ここではメモリ割り当てが行なわれていないからです。)

レコードのフィールドへのアクセスは、タプルに対するものと全く同じです。

type rec_t = { a:int; b:float; c:string }
external inspect_record: rec_t -> unit = "inspect_record"
CAMLprim value
inspect_record( value ml_record )
{
    printf("%d\n", Int_val(Field(ml_record, 0)));
    printf("%g\n", Double_val(Field(ml_record, 1)));
    printf("%s\n", String_val(Field(ml_record, 2)));
    return Val_unit;
}

フィールドのインデックス番号は、そのレコード型のOCamlでの宣言に出現するフィールドの順番です。

# inspect_record {a=26; b=4.3; c="camelids folks" } ;;
26
4.3
camelids folks
- : unit = ()

レコードの全てのフィールドの型がfloatからなるような特別な場合がありますが、このケースでは、そのレコードはCからはfloatの配列のように見えます。 この特別なケースについては以下を見てください。


配列へのアクセス (Accessing Arrays)

配列のフィールドへのアクセスは非常によく似ています。

external inspect_int_array: int array -> unit = "inspect_int_array"
CAMLprim value
inspect_int_array( value ml_array )
{
    CAMLparam1( ml_array );
    int i, len;
    len = Wosize_val(ml_array);
    for (i=0; i < len; i++)
    {
        printf("%d\n", Int_val(Field(ml_array, i)));
    }

    CAMLreturn( Val_unit );
}

ここでのトリックは、配列の要素数を取得することだけです。 また、配列がfloat arrayであれば、要素の数を取得して、その内容にアクセスするマクロは少し違ったものになります。

external inspect_float_array: float array -> unit = "inspect_float_array"
CAMLprim value
inspect_float_array( value ml_float_array )
{
    CAMLparam1( ml_float_array );
    int i, len;
    len = Wosize_val(ml_float_array) / Double_wosize;
    for (i=0; i < len; i++)
    {
        printf("%g\n", Double_field(ml_float_array, i));
    }

    CAMLreturn( Val_unit );
}

この二つの関数をテストするには次のようにします。

# inspect_int_array [| 2; 4; 3; 1; 9 |] ;;
2
4
3
1
9
- : unit = ()

# inspect_float_array [| 2.4; 5.8; 6.9; 12.2; 32.8 |] ;;
2.4
5.8
6.9
12.2
32.8
- : unit = ()

リストへのアクセス (Accessing Lists)

さて、ここでは、OCamlのリストの内容にアクセスする方法を扱います。

external inspect_list: string list -> unit = "inspect_list"
CAMLprim value
inspect_list( value ml_list )
{
    CAMLparam1( ml_list );
    CAMLlocal1( head );

    while ( ml_list != Val_emptylist )
    {
        head = Field(ml_list, 0);  /* accessing the head */
        printf("%s\n", String_val(head));
        ml_list = Field(ml_list, 1);  /* point to the tail for next loop */
    }

    CAMLreturn( Val_unit );
}

トップレベルでテストします。

# inspect_list ["hello"; "you"; "world"; "camelids"] ;;
hello
you
world
camelids
- : unit = ()

見てのとおり、リストはアイテムのペアからなっており、ペアの1番目のアイテムが頭部、2番目のものが尾部です。 尾部は他のリストか空のリストです。 空のリストは、OCamlでは[]と表わされ、Cの側ではVal_emptylistと表わされます。

この実例として、次のコマンドをタイプして、トップレベルでテストすることができます。

# external trans: (int * (int * (int * (int * int)))) -> int list = "%identity" ;;
external trans : int * (int * (int * (int * int))) -> int list = "%identity"

# trans (1, (2, (3, (4, 0)))) ;;
- : int list = [1; 2; 3; 4]

ここで、%identityというのは、内部表現は同一のまま、ある型を他の型へと変換することを可能にするためのOCamlの組み込み関数です。

また、ここで、最後に指定された整数が0であることにも注意してください。 <caml/mlvalues.h>ヘッダを読めばわかるように、Val_emptylistVal_int(0)として定義されているからです。


タプルの生成 (Creating Tuples)

さて、Cの側でタプルを作り、それをOCamlへ返す方法を見てみましょう。
external create_tuple: 'a -> 'b -> 'c -> 'a * 'b * 'c = "create_tuple"
CAMLprim value
create_tuple( value a, value b, value c )
{
    CAMLparam3( a, b, c );
    CAMLlocal1( abc );

    abc = caml_alloc(3, 0);

    Store_field( abc, 0, a );
    Store_field( abc, 1, b );
    Store_field( abc, 2, c );

    CAMLreturn( abc );
}
# create_tuple 38 'G' "www.ifrc.org" ;;
- : int * char * string = (38, 'G', "www.ifrc.org")

caml_alloc()の1番目のパラメータはフィールドの数、2番目のパラメータはその値のタグを示しています。 ここではタグは0ですが、これは、タプルのような通常のOCamlの値ではタグが必要無いからです。

また、これはレコードでも同様に動きます。 前に見たように、floatだけを含むレコードを除けば、レコードはタプルと同じ内部表現を持つからです。

これは配列でも使えます。 ただし、その配列の全ての要素が同じ型を持っていることを確認しなければなりません。 異なる内部表現を持つfloat array以外の全ての配列はこの方法で作ることができます。

リストのときと同じやり方をすれば、次のようにレコードとタプルが同じ内部表現を持つことを検証することができます。

# type rec_b = { i:int; c:char; s:string } ;;
type rec_b = { i : int; c : char; s : string; }

# external trans: rec_b -> int * char * string = "%identity" ;;
external trans : rec_b -> int * char * string = "%identity"

# trans { i=38; c='G'; s="www.ifrc.org" } ;;
- : int * char * string = (38, 'G', "www.ifrc.org")
# external create_rec_b: int -> char -> string -> rec_b = "create_tuple" ;;
external create_rec_b : int -> char -> string -> rec_b = "create_tuple"

# create_rec_b 38 'G' "www.ifrc.org" ;;
- : rec_b = {i = 38; c = 'G'; s = "www.ifrc.org"}

リストの生成 (Creating Lists)

この時点で、CでOCamlのリストを構築する方法がわかるはずです。 一例を挙げます。

external string_explode: string -> char list = "create_list"
CAMLprim value create_list( value ml_str )
{
    CAMLparam1( ml_str );
    CAMLlocal2( cli, cons );

    char *str = String_val(ml_str);
    int len = caml_string_length(ml_str);
    int i;

    cli = Val_emptylist;

    for (i = len - 1; i >= 0; i--)
    {
        cons = caml_alloc(2, 0);

        Store_field( cons, 0, Val_int(str[i]) );  // head
        Store_field( cons, 1, cli );              // tail

        cli = cons;
    }

    CAMLreturn( cli );
}
# string_explode "OCaml" ;;
- : char list = ['O'; 'C'; 'a'; 'm'; 'l']

float配列の生成 (Creating Floats arrays)

floatの配列は特別で、Cで作成するのは少し異なっています。 メモリ割り当ての際に、次のような長さのトリックとタグの両方に注意してください。
float_array = caml_alloc(length * Double_wosize, Double_array_tag);
また、要素を格納するにはStore_field()の代わりにStore_double_field()を使ってください。 そして、その中でcaml_copy_doubleを使う必要はありません。


enum/バリアント (Enums / Variants)

Cのenumをラップしたり、#defineで定数として定義されたパラメータをラップするのには同じ方法を使います。 どちらもパラメータの無い定数コンストラクタを使ってOCamlの列挙バリアントとしてラップすることができます。 Cの側からだとそれらのコンストラクタは0から(N - 1) (Nはそのバリアント型のコンストラクタの数) までに順序付けられた整数で表わされます。 ですので基本的にはswitchか、あるいはそのCの値と対応する全ての値を含んだ配列を使うだけです。 例を示します。

type moving =
  | WALKING
  | RUNNING
  | SWIMMING
  | FLYING

external send_enum: moving -> unit = "wrapping_enum_ml2c"
typedef enum _moving {
    WALKING,
    RUNNING,
    SWIMMING,
    FLYING
} moving;

CAMLprim value
wrapping_enum_ml2c( value v )
{
    moving param;
    switch (Int_val(v)) {
        case 0: param = WALKING; break;
        case 1: param = RUNNING; break;
        case 2: param = SWIMMING; break;
        case 3: param = FLYING; break;
    }
    switch (param) {
        case WALKING:  puts("Walking"); break;
        case RUNNING:  puts("Running"); break;
        case SWIMMING: puts("Swimming"); break;
        case FLYING:   puts("Flying"); break;
    }
    return Val_unit;
}

ここではOCamlのバリアントの順序に応じてswitchの中で適切な数値をマッチさせることが非常に重要です。

CからOCamlへと定数のバリアントを送り返すには、その対応するインデックスを使ってVal_int()を単にリターンしてください。

列挙されたものの中に取り得る値(取り得る定数)が多数あるような場合には、switchの代わりに配列を使うこともできます。

static const moving table_moving[] = {
    WALKING,
    RUNNING,
    SWIMMING,
    FLYING
};

CAMLprim value
wrapping_enum_ml2c( value v )
{
    moving param;
    param = table_moving[Long_val(v)];

    switch (param) {
        case WALKING:  puts("Walking"); break;
        case RUNNING:  puts("Running"); break;
        case SWIMMING: puts("Swimming"); break;
        case FLYING:   puts("Flying"); break;
    }
    return Val_unit;
}

ここではこの関連付けのテーブルの最後のインデックスを超えてアクセスしないように確認する必要があります。 これは、OCamlの側でアイテムを追加してCの側の配列にも追加しなければならないのを忘れたような場合に発生するでしょう。
あなたにできることは、呼び出されるインデックスが最後のインデックスより大きくないかどうか、そして、例外が発生しているかどうかをチェックすることです。


Cのビットフィールドの変換 (Convert C bit fields)

ヘッダファイルで次のようにCのビットフィールドを定義していると仮定してみてください。

#define ShiftMask      (1<<0)
#define LockMask       (1<<1)
#define ControlMask    (1<<2)
#define Mod1Mask       (1<<3)

これをラップするには、バリアントのリストに変換するのがもっとも一般的な解決法となります。

type state =
  | ShiftMask
  | LockMask
  | ControlMask
  | Mod1Mask

type state_mask = state list

あらかじめ、OCamlのバリアントと正確に同じ順序でCの値のテーブルを構築し、そして、camlのリストの全てのアイテムを繰り返しながら、それに従ってCのビットフィールドをセットします。

static const int state_mask_table[] = {
    ShiftMask,
    LockMask,
    ControlMask,
    Mod1Mask
};

static inline int
state_mask_val( value mask_list )
{
    int c_mask = 0; 
    while ( mask_list != Val_emptylist )
    {
        value head = Field(mask_list, 0);
        c_mask |= state_mask_table[Long_val(head)];
        mask_list = Field(mask_list, 1);
    }
    return c_mask;
}

また、CのビットフィールドをOCamlのバリアントのリストへ変換するには、全てのフィールドのビットをテストして、マッチした場合にそれに応じたOCamlの値をリストへプッシュする必要があります。

#define Val_ShiftMask    Val_int(0)
#define Val_LockMask     Val_int(1)
#define Val_ControlMask  Val_int(2)
#define Val_Mod1Mask     Val_int(3)

static value
Val_state_mask( int c_mask )
{
    CAMLparam0();
    CAMLlocal2(li, cons);
    li = Val_emptylist;

    if (c_mask & ShiftMask) {
        cons = caml_alloc(2, 0);
        Store_field( cons, 0, Val_ShiftMask );
        Store_field( cons, 1, li );
        li = cons;
    }
    if (c_mask & LockMask) {
        cons = caml_alloc(2, 0);
        Store_field( cons, 0, Val_LockMask );
        Store_field( cons, 1, li );
        li = cons;
    }
    if (c_mask & ControlMask) {
        cons = caml_alloc(2, 0);
        Store_field( cons, 0, Val_ControlMask );
        Store_field( cons, 1, li );
        li = cons;
    }
    if (c_mask & Mod1Mask) {
        cons = caml_alloc(2, 0);
        Store_field( cons, 0, Val_Mod1Mask );
        Store_field( cons, 1, li );
        li = cons;
    }

    CAMLreturn(li);
}

生データの交換 (Exchanging Raw Data)

生データを取り扱わなければならない場合は、OCamlの文字列を利用することで実現できます。 OCamlの文字列は、Cで言うところのバイト配列やvoid*です。 OCamlの側においては、文字列は'\000'を含むどのような文字でも取り扱うことができます。 しかし、その生のデータをコピーする場合、caml_copy_string()を使うと、そのコピーは最初のNULL文字で止まってしまいます。 というのも、NULL文字が文字列の終端であると認識されるからです。 ですので、caml_alloc_string()memcpy()を使うべきでしょう。

#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <string.h>

CAMLprim value get_raw_data( value unit )
{
    CAMLlocal1( ml_data );
    char * raw_data;
    int data_len;
    /* get raw_data and data_len here 
       (if raw_data is c-mallocated,
        you can use sizeof() for its length)
    */
    ml_data = caml_alloc_string (data_len);
    memcpy( String_val(ml_data), raw_data, data_len );
    return ml_data;
}

ここでは、ocamlのメモリ割り当てがあるにも関わらず、CAMLparam/CAMLreturnを使わないで済ますことができることに注意してください。 これは、そのメモリ割り当てよりも後にocamlの値が使われないからです。 あなたが書くコードに何かあるような場合には CAMLparam/CAMLreturn をぜひ使ってください。

データのかたまりがマトリックスのような何か(たとえば画像)を表現している場合には、Cと同じメモリレイアウトを持つためにこの種の作業に非常に便利なデータ構造であるOCamlのbigarrayを使いたいとおそらく思うでしょう。


あなたが書くコードにおいて、raw_dataバッファがCでメモリ割り当てされて、何らかの処理によってデータが格納されるような場合、単にOCamlの文字列を割り当てて、その文字列の最初のバイトへのポインタを取得することも可能です。 ですので、そのような場合だと、Cのmalloc()が一つと、Cのfree()が一つ、そしてmemcpy()のコールを節約することになります。

CAMLprim value get_raw_data( value some_param )
{
    CAMLparam( some_param );
    CAMLlocal1( ml_data );
    char * raw_data;
    int data_len;
    /* do initialise data_len */
    ml_data = caml_alloc_string( data_len );
    raw_data = String_val(ml_data);
    /* 
       Now you can use raw_data and fill it as if it was
       a buffer of type (char *) of length data_len.
       (once given to ocaml it will be garbage-collected as every ocaml value)
    */
    CAMLreturn( ml_data );
}

このraw_data[n]のようにバッファ内の一つのバイトへアクセスする必要があるときには、次のようにOCamlの値にするのと同じ直接のアクセスを実行することもできます。 Byte(ml_data, n).
(raw_dataが(unsigned char *)の場合は、Byte()Byte_u()に変更してください)


5個を超えるパラメータ (More than 5 parameters)

OCamlの関数には、五つを超えるパラメータを持つという特別なケースがあります。 そのような場合には、以下に見るように、二つのC関数を用意しなければなりません。

external lots_of_params: p1:int -> p2:int -> p3:int ->
                         p4:int -> p5:int -> p6:int -> p7:int -> unit
    = "lotprm_bytecode"
      "lotprm_native"
CAMLprim value
lotprm_native( value p1, value p2, value p3,
               value p4, value p5, value p6, value p7 )
{
    printf("1(%d) 2(%d) 3(%d) 4(%d) 5(%d) 6(%d) 7(%d)\n",
           Int_val(p1), Int_val(p2), Int_val(p3),
           Int_val(p4), Int_val(p5), Int_val(p6), Int_val(p7) );
    return Val_unit;
}

CAMLprim value
lotprm_bytecode( value * argv, int argn )
{
    return lotprm_native( argv[0], argv[1], argv[2],
                          argv[3], argv[4], argv[5], argv[6] );
}

そして、見てのとおり、バイトコードでコンパイルされるプログラムで呼び出される関数は異なったプロトタイプを持ちます。 ですので、バイトコードの関数においてはネイティブの関数をコールするというのが一般的でもっとも簡単な解決方法となります。

また、これらの関数ついてのもう一つ説明すると、ガベージコレクターから値を保護する必要があるときには、Nが1から5までの間だけについて、CAMLparamN()というマクロが使えます。 最初の五つのパラメータをCAMLparam5()に書き、追加してCAMLxparamN()を使ってください。 このマクロもまたNが1から5までの範囲で使えます。 たとえば、13個のパラメータがあるときは、CAMLparam5()を一つ、CAMLxparam5()を一つ、そしてCAMLxparam3()を一つ使ってください。 中にxが付かないマクロは一つだけしか使うことができませんが、xが付くマクロは複数使うことができます。 これらのマクロを使った同じ関数を次に示します。

CAMLprim value
lotprm_native( value p1, value p2, value p3,
               value p4, value p5, value p6, value p7 )
{
    CAMLparam5(p1, p2, p3, p4, p5);
    CAMLxparam2(p6, p7);

    printf("1(%d) 2(%d) 3(%d) 4(%d) 5(%d) 6(%d) 7(%d)\n",
           Int_val(p1), Int_val(p2), Int_val(p3),
           Int_val(p4), Int_val(p5), Int_val(p6), Int_val(p7) );

    CAMLreturn(Val_unit);
}

CAMLprim value
lotprm_bytecode( value * argv, int argn )
{
    return lotprm_native( argv[0], argv[1], argv[2],
                          argv[3], argv[4], argv[5], argv[6] );
}

例外の発生 (Raising Exception)

OCamlの定義済みの例外を起こすのは非常に簡単です。 たとえば、Invalid_argument例外を起こすCの関数は次のようになります。

    caml_invalid_argument("Error message");

そして、Failure例外を起こすには次のようになります。

    caml_failwith("Error message");

これらの関数を使うときにインクルードするヘッダファイル次のようになります。

#include <caml/fail.h>

カスタムの例外を定義するモジュールの場合は、公式マニュアルのこの部分で説明されているように Cからその例外を起こすことができます。


Cの構造体へのポインタ (Pointers to C structures)

Cのライブラリではよく構造体や構造体へのポインタで定義された型を使います。 OCamlはメモリ割り当てされた領域へのポインタを安全に取り扱うことができます。 この仕事を実現するのにもっとも単純な方法は、ポインタを扱える(value)型へ、あるいはその型から、ポインタをキャストすることです。
この単純な方法の不利な点は、割り当てられた値をC側から解放しなければならないことです。 通常これは、割り当てられたメモリを解放する関数を用意して、そのインターフェイスをOCamlの側へ提供するということを意味します。 ライブラリの場合には、ある特定の型へ関連付けられた破壊関数を使うことに相当します。

typedef struct _obj_st {
    double d;
    int i;
    char c;
} obj_st;

typedef obj_st *obj_p;


CAMLprim value
wrapping_ptr_ml2c( value d, value i, value c )
{
    obj_p my_obj;
    my_obj = malloc(sizeof(obj_st));
    my_obj->d = Double_val(d);
    my_obj->i = Int_val(i);
    my_obj->c = Int_val(c);
    return (value) my_obj;
}

CAMLprim value
dump_ptr( value ml_ptr )
{
    obj_p my_obj;
    my_obj = (obj_p) ml_ptr;
    printf(" d: %g\n i: %d\n c: %c\n",
            my_obj->d,
            my_obj->i,
            my_obj->c );
    return Val_unit;
}

CAMLprim value
free_ptr( value ml_ptr )
{
    obj_p my_obj;
    my_obj = (obj_p) ml_ptr;
    free(my_obj);
    return Val_unit;
}

次に、OCaml側の抽象型にポインタを隠すことができます。

type t
external abs_get: float -> int -> char -> t = "wrapping_ptr_ml2c"
external print_t: t -> unit = "dump_ptr"
external free_t: t -> unit = "free_ptr"
# let ty = abs_get 255.9 107 'K' in
  print_t ty;
  free_t ty;
  ;;
 d: 255.9
 i: 107
 c: K
- : unit = ()

Cのオブジェクトのファイナライゼーション (Finalisation of C objects)

Cのオブジェクトで表現された抽象型をファイナライズするのに、次のようにGc.finaliseを使わないようにしてください。

let abs_get f i c =
  let t = abs_get f i c in
  Gc.finalise free_t t;     (* doesn't work *)
  (t)
;;

この関数は、ヒープに割り当てられたvalueにだけ作用するからです。
しかし、抽象型を、ヒープに割り当てられたvalueと一緒にした新しい型を作ることでこれを回避することができます。

type u = {t:t; s:string}

let free_t v = free_t v.t ;;
let print_t v = print_t v.t ;;

let abs_get f i c =
  let t = abs_get f i c in
  let u = {t=t; s=" "} in
  Gc.finalise free_t u;
  (u)
;;

これは単純な方法ですが、Cの側から同様の効果を実現する「より公式な」方法があります。 これは次のパラグラフで説明します。


custom operations構造体 (Custom Operations Structure)

caml_alloc_custom()関数を使うと、指定されたサイズのデータを格納することができます。 このデータのサイズはこの関数の2番目の引数として指定します。 1番目のパラメータはcustom_operations構造体で、この構造体は、このOCamlの値と関連付けられた関数を提供するのに使うことができます。 以下の例では関連付けられている関数がありませんので、custom_operations構造体は、デフォルトの関数で埋められています(デフォルトの関数は実際のところ<caml/custom.h>の中でNULLとして定義されています)。

#include <caml/custom.h>
#include <string.h>

typedef struct _obj_st {
    double d;
    int i;
    char c;
} obj_st;

static struct custom_operations objst_custom_ops = {
    identifier: "obj_st handling",
    finalize:    custom_finalize_default,
    compare:     custom_compare_default,
    hash:        custom_hash_default,
    serialize:   custom_serialize_default,
    deserialize: custom_deserialize_default
};

static inline value copy_obj( obj_st *some_obj )
{
    CAMLparam0();
    CAMLlocal1(v);
    v = caml_alloc_custom( &objst_custom_ops, sizeof(obj_st), 0, 1);
    memcpy( Data_custom_val(v), some_obj, sizeof(obj_st) );
    CAMLreturn(v);
}

CAMLprim value
get_finalized_obj( value d, value i, value c )
{
    CAMLparam3( d, i, c );
    CAMLlocal1(ml_obj);

    obj_st my_obj;
    my_obj.d = Double_val(d);
    my_obj.i = Int_val(i);
    my_obj.c = Int_val(c);

    ml_obj = copy_obj( &my_obj );

    CAMLreturn(ml_obj);
}

CAMLprim value
access_obj( value v )
{
    obj_st * my_obj;
    my_obj = (obj_st *) Data_custom_val(v);
    printf(" d: %g\n i: %d\n c: %c\n",
            my_obj->d,
            my_obj->i,
            my_obj->c );
    return Val_unit;
}
type obj
external new_obj: float -> int -> char -> obj = "get_finalized_obj"
external dump_obj: obj -> unit = "access_obj"

new_abs関数(訳注:new_objのこと?)によって得られた値は、前のパラグラフCの構造体へのポインタ(Pointers to C structures)にあるのと同様に明示的な解放は必要ありません。 OCamlは、ガベージコレクションが発生したときにこれらの値をファイナライズします。

# let a = Array.init 20 (fun i -> new_obj (float i) i 'A') in
  dump_obj a.(9);
  ;;
 d: 9
 i: 9
 c: A
- : unit = ()

カスタムファイナライゼーション (Custom Finalisation)

メモリ割り当てされているメンバを含むCの構造体というのは非常によくあることですが、そのようなケースでは、カスタムのファイナライゼーション関数を使って、こういったメンバを解放する必要があります。 以下の例でこれを見ますが、この例は前の例と非常に近いものです。 ここでは、Cの構造体のstrというメンバが割り当てられ、カスタムのファイナライゼーション関数で解放する必要があります。

typedef struct _fobj {
    double d;
    int i;
    char * str;
} fobj;

void finalize_fobj( value v )
{
    fobj * my_obj;
    my_obj = (fobj *) Data_custom_val(v);
    free( my_obj->str );
    puts("fobj freed done");
}

static struct custom_operations fobj_custom_ops = {
    identifier: "fobj handling",
    finalize:  finalize_fobj,
    compare:     custom_compare_default,
    hash:        custom_hash_default,
    serialize:   custom_serialize_default,
    deserialize: custom_deserialize_default
};

static inline value copy_fobj( fobj *some_obj )
{
  CAMLparam0();
  CAMLlocal1(v);
  v = caml_alloc_custom( &fobj_custom_ops, sizeof(fobj), 0, 1);
  memcpy( Data_custom_val(v), some_obj, sizeof(fobj) );
  CAMLreturn(v);
}

CAMLprim value
get_finalized_fobj( value d, value i, value str )
{
    CAMLparam3( d, i, str );
    CAMLlocal1(ml_obj);
    int len;
    fobj my_obj;

    my_obj.d = Double_val(d);
    my_obj.i = Int_val(i);

    len = caml_string_length(str) + 1;
    my_obj.str = malloc( len * sizeof(char) );
    memcpy( my_obj.str, String_val(str), len );

    ml_obj = copy_fobj( &my_obj );

    CAMLreturn(ml_obj);
}

CAMLprim value
access_fobj( value v )
{
    fobj * my_obj;
    my_obj = (fobj *) Data_custom_val(v);
    printf(" d: %g\n i: %d\n str: %s\n",
            my_obj->d,
            my_obj->i,
            my_obj->str );
    return Val_unit;
}
type fobj
external new_fobj: float -> int -> string -> fobj = "get_finalized_fobj"
external dump_fobj: fobj -> unit = "access_fobj"

printf()メッセージで値がファイナライズされるのがわかります。 下のスクリプトでは、Gc.full_majorをコールすることでファイナライゼーションが引き起こされます。

let () =
  begin
    let f = function i -> new_fobj (float i) i (String.make i '.') in
    let a = Array.init 20 f in
    dump_fobj a.(9);
  end;
  Gc.full_major();
  print_endline "end test";
;;

さらなる情報は、公式マニュアルのcustom_operations structureで知ることができます。
この構造体はシリアライゼーションなどのようなほかのカスタムオペレーションでも使うことができます。


引数付きバリアント (Variants with arguments)

今度はパラメータ付きのバリアントの場合です。 次の例のようになります。


type pvar =
  | P0_a
  | P1_a of int
  | P2_a of int * int
  | P0_b
  | P1_b of int
  | P2_b of int * int

external handle_pvar: pvar -> unit = "param_variant"

Cの側から見えるOCamlの値は二つの種類に分けることができます。 longとブロックです。 booleanや文字、整数、そして提示している例のスカラーのバリアントは、Cのlongとして表現されます。 その他の型はブロックです。 ある値がどちらの種類なのかをテストするには次のマクロが使えます。

  Is_long(v)
  Is_block(v)

これらのマクロを使って、バリアントがスカラーなのか、パラメータを持つのかをテストすることができます。

CAMLprim value
param_variant( value v )
{
    if (Is_long(v))
    {
        switch (Int_val(v))
        {
            case 0: printf("P0_a\n"); break;
            case 1: printf("P0_b\n"); break;
            default: caml_failwith("variant handling bug");
        }
    }
    else // (Is_block(v))
    {
        switch (Tag_val(v))
        {
            case 0: printf("P1_a(%d)\n",     Int_val(Field(v,0)) ); break;
            case 1: printf("P2_a(%d, %d)\n", Int_val(Field(v,0)), Int_val(Field(v,1)) ); break;
            case 2: printf("P1_b(%d)\n",     Int_val(Field(v,0)) ); break;
            case 3: printf("P2_b(%d, %d)\n", Int_val(Field(v,0)), Int_val(Field(v,1)) ); break;
            default: caml_failwith("variant handling bug");
        }
    }
    return Val_unit;
}

ここで、引数付きのバリアントのコンストラクタはTag_val(v)というマクロで扱うことができるということに注意を向けることができます。 また、振り分けのための数値にも気を付けてください。 この例でわかるように、スカラーとブロックは別々に番号付けがされています。

#load "funs.cma" ;;
open Funs ;;

let () =
  handle_pvar(P0_a);
  handle_pvar(P0_b);
  handle_pvar(P1_a(21));
  handle_pvar(P1_b(27));
  handle_pvar(P2_a(30, 34));
  handle_pvar(P2_b(70, 74));
;;

'a option型 (The type 'a option)

type 'a option = None | Some of 'a

(前のセクションで見たように)'a option型は一つの引数を持ったバリアントにすぎませんが、素早く参照できるようにここにコードを示します。

#define Val_none Val_int(0)

static inline value
Val_some( value v )
{   
    CAMLparam1( v );
    CAMLlocal1( some );
    some = caml_alloc(1, 0);
    Store_field( some, 0, v );
    CAMLreturn( some );
}
CAMLprim value
rand_int_option( value unit )
{
    int d = random() % 4;
    if (d)
      return Val_some( Val_int(d) );
    else
      return Val_none;
}
external rand_int: unit -> int option = "rand_int_option"
# Array.init 10 (fun _ -> rand_int()) ;;
[|Some 3; None; Some 1; None; Some 3; Some 3; Some 2; None; None; Some 3|]

これの逆のものは次のようになります。

external say: string option -> unit = "maybe_say"
#define Some_val(v) Field(v,0)

CAMLprim value
maybe_say( value speech )
{
    if (speech == Val_none)
        printf("Nothing\n");
    else
        printf("Something: %s\n", String_val(Some_val(speech)) );
    return Val_unit;
}
# say None ;;
Nothing
- : unit = ()

# say (Some "Camelus bactrianus") ;;
Something: Camelus bactrianus
- : unit = ()

オプショナルなラベル付きパラメータ (optional labeled parameter)

非常に似た方法で、ラベル付きのオプショナルパラメータを書くことができます。

external say_lbl: ?speech:string -> unit -> unit = "maybe_say_label"

ここでは、型がstringであって、string optionではないことに注意してください。 また、Cの側からだと、string optionのように見えることに注意してください。

CAMLprim value
maybe_say_label( value speech, value unit )
{
    if (speech == Val_none)
        printf("Nothing\n");
    else
        printf("Something: %s\n", String_val(Some_val(speech)) );
    return Val_unit;
}

パラメータのリストの最後にunitパラメータを追加しなければならないことがよくあります。 このunitパラメータは、使われない場合でも、Cの関数に追加する必要があります。

# say_lbl () ;;
Nothing
- : unit = ()

# say_lbl ~speech:"le chameau songeur" () ;;
Something: le chameau songeur
- : unit = ()

多相バリアント (Polymorphic Variants)

type plm_var =
  [ `plm_A
  | `plm_B
  | `plm_C ]

external plm_variant: plm_var -> unit = "polymorphic_variant"

多相バリアントのCでの表現は、そのハッシュ値を計算する関数を使って取得することができます。

CAMLprim value
polymorphic_variant( value v )
{
    if (v == caml_hash_variant("plm_A"))  puts("polymorphic variant A");
    if (v == caml_hash_variant("plm_B"))  puts("polymorphic variant B");
    if (v == caml_hash_variant("plm_C"))  puts("polymorphic variant C");
    return Val_unit;
}
let () =
  plm_variant `plm_A;
  plm_variant `plm_B;
  plm_variant `plm_C;
;;

もしパフォーマンスが問題となるのでしたら、毎回caml_hash_variant()のコールをする代わりに、それぞれのバリアントに対する結果を取得して、定数を生成し、switchで使用することができます。

#include <caml/mlvalues.h>
#include <stdio.h>

int main()
{
    printf("#define  MLVAR_plm_A  (%d)\n", caml_hash_variant("plm_A") );
    printf("#define  MLVAR_plm_B  (%d)\n", caml_hash_variant("plm_B") );
    printf("#define  MLVAR_plm_C  (%d)\n", caml_hash_variant("plm_C") );
    return 0;
}

これは次のようにコンパイルできます。

> empty.ml
ocamlc -o empty.o -output-obj empty.ml
ocamlc -c variant.c
gcc -o variant.exe variant.o empty.o -L"`ocamlc -where`" -lcamlrun -lm -lcurses

./variant.exeは次のような結果を出力します。

#define  MLVAR_plm_A   (-1993467801)
#define  MLVAR_plm_B   (-1993467799)
#define  MLVAR_plm_C   (-1993467797)

これをCコードの先頭でインクルードすることができます。 従って、前の関数polymorphic_variant()は次のように置き換えることができます。

CAMLprim value
polymorphic_variant( value v )
{
    switch (v)
    {
        case MLVAR_plm_A:  puts("polymorphic variant A");  break;
        case MLVAR_plm_C:  puts("polymorphic variant B");  break;
        case MLVAR_plm_D:  puts("polymorphic variant C");  break;
        default:
            caml_failwith("unrecognised polymorphic variant");
    }
    return Val_unit;
}

同じdefine群を得るもう一つの方法は次のようになります。

CAMLprim value
print_polymorphic_variant_val( value v )
{
    printf("%d", (long) v );
    fflush(stdout);
    return Val_unit;
}
external pmvar_print_i: pmvar -> unit = "print_polymorphic_variant_val"

let () =
  let p = Printf.printf in
  p "#define  MLVAR_plm_A \t %!";  (pmvar_print_i `plm_A);  p "\n%!";
  p "#define  MLVAR_plm_B \t %!";  (pmvar_print_i `plm_B);  p "\n%!";
  p "#define  MLVAR_plm_C \t %!";  (pmvar_print_i `plm_C);  p "\n%!";
;;

OCamlとCの両方で、各文字列を出力した後、標準出力をフラッシュしていることに注意してください(OCamlでは"%!"でフラッシュすることができます)。 これは、OCamlとCの標準出力は別々のチャンネルで、同期していないからです。


ライブラリのリンク (Linking against a library)

さて、Cの部分でCのライブラリを使いたい場合を仮定してください。 たとえば、MyLibと呼ばれる架空のライブラリだとしましょう。 これはCプログラムでは次のようにコンパイルします。
cc -o my_prog -L/lib/path -lMyLib my_prog.c
この場合は次に示すように、モジュールをコンパイルする際-lMyLibという引数を入れなければなりません。

dll_wrap_stubs.so: wrap.o
        ocamlmklib  -o  _wrap_stubs  $<  \
            -L/lib/path  -lMyLib

funs.cmxa:  funs.cmx  dll_wrap_stubs.so
        ocamlopt -a  -o $@  $<  -cclib -l_wrap_stubs \
            -ccopt -L/lib/path  \
            -cclib -lMyLib

funs.cma:  funs.cmo  dll_wrap_stubs.so
        ocamlc -a  -o $@  $<  -dllib -l_wrap_stubs \
            -ccopt -L/lib/path  \
            -cclib -lMyLib

ネイティブコードのモジュールの場合は、リンカに対する--cclibとコンパイラとリンカに対する--ccoptを前置しなければなりません。
ocamlcで-dllibを使っていることに注意してください。

詳細は関連するマニュアルページocamlcocamloptを読んでください。


Cからのcaml関数の呼び出し (Call Caml functions from C)

Cからcamlの関数を呼び出すには、Callback.registerを使って、それぞれのcamlの関数を識別する文字列を指定しなければなりません。 Cからこのcamlの関数を取得するのにはcaml_named_value("ID")が使えます。 そして、以下のC コードにあるように、caml_named_value()への呼び出しをstatic変数でキャッシュすることができます。 しかし、camlのガベージコレクターがそれを解放した場合に備えてNULLと等しいかどうかを、やはりテストしなければなりません。

let print_hello () =
  print_endline "Hello World";
;;

let () =
  Callback.register "Hello callback" print_hello;
;;
#include <caml/callback.h>

void hello_closure()
{
    static value * closure_f = NULL;
    if (closure_f == NULL) {
        closure_f = caml_named_value("Hello callback");
    }
    caml_callback(*closure_f, Val_unit);
}

Cによるスタートアップ (Start-up from C)

Cでメインプログラムを作って、そしてそこからcaml部分を呼び出したいような場合には、以下のようにすることができます。

file "ml_part.ml":
let print_hello () =
  print_endline "Hello World";
;;

let () =
  Callback.register "Hello callback" print_hello;
;;
file "main.c":
#include <caml/mlvalues.h>
#include <caml/callback.h>

void hello_closure()
{
    static value * closure_f = NULL;
    if (closure_f == NULL) {
        closure_f = caml_named_value("Hello callback");
    }
    caml_callback(*closure_f, Val_unit);
}

int main(int argc, char **argv)
{
    caml_main(argv);
    hello_closure();
    return 0;
}

main関数の中の最初の命令はcaml_main(argv)でなければなりません。 これは、プログラムのcaml部分を初期化するためのものです。 ルートレベル(例では、print_endline "something" ;;だとか、let () = (* code *) ;;に定義されているものすべて)にあるcamlの命令は、この時点で評価され、実行されます。

コンパイル:
ocamlopt -output-obj ml_part.ml -o ml_part_obj.o

gcc -c main.c  -I"`ocamlc -where`"

gcc -o prog.opt  \
        main.o  ml_part_obj.o   \
        -L"`ocamlc -where`"     \
        -lm -ldl -lasmrun

最後の行は-lm-ldlライブラリに触れていますが、これは次のようにすれば知ることができます。
grep NATIVECCLIBS `ocamlc -where`/Makefile.config

ですので、Makefileのポータビリティの点で言うと、この二つの引数-lm -ldl$(NATIVECCLIBS)という変数で置き換えることができます。 これをするには、次のように`ocamlc -where`/Makefile.configファイルを単にインクルードすることが必要です。

prog.opt: main.o  ml_part_obj.o
        gcc -o $@  $^   \
            -L"`ocamlc -where`"     \
            $(NATIVECCLIBS) -lasmrun

 -include  $(shell ocamlc -where)/Makefile.config

OCamlマニュアルでの関連する説明は次のチャプターです。 18.7.5  Embedding the Caml code in the C code


C++との混用 (Mixing with C++)

mymod_stubs.ccファイルの内容:
#include <iostream>
#include <string>

extern "C" {
#include <caml/memory.h>
#include <caml/mlvalues.h>
}

extern "C" value my_hello_cc (value v_str) {
  CAMLparam1 (v_str);

  std::cout << "Hello " << String_val(v_str) << "!\n";

  CAMLreturn (Val_unit);
}
mymod.mlファイルの内容:
external my_hello : string -> unit = "my_hello_cc"
caller.mlファイルの内容:
let _ =
  Mymod.my_hello "Blue Camel";
;;

次のようにネイティブコードにコンパイルします。

g++ -o mymod_stubs.o -I`ocamlc -where` -c mymod_stubs.cc
ocamlopt -c mymod.ml
ocamlmklib -o mymod mymod_stubs.o
ocamlmklib -o mymod mymod.cmx

ocamlopt -I . -cclib -lstdc++ mymod.cmxa caller.ml -o caller.opt

そしてそれを実行します。

% ./caller.opt 
Hello Blue Camel!

さてバイトコードのコンパイルです。

g++ -o mymod_stubs.o -I`ocamlc -where` -c mymod_stubs.cc
ocamlc -c mymod.ml
ocamlmklib -o mymod -lstdc++ mymod_stubs.o
ocamlmklib -o mymod  mymod.cmo

ocamlc -I . mymod.cma  caller.ml  -o caller.byte

そしてそれを実行します。

% ocaml mymod.cma caller.ml
Hello Blue Camel!

% ocamlrun -I . caller.byte
Hello Blue Camel!

このセクションは、Anne Pacalet によるチュートリアル (仏語) に大きくインスパイアされました。 そのチュートリアルはここそこで見ることができます。


これは入門となるチュートリアルでした。 詳細については、公式のOCamlマニュアルのこのテーマのページを参照してください。

このページや公式のマニュアルでも答えが見つからない疑問があるような場合には、ocaml_beginnersメーリングリストへ参加して助けを得ることもできます。

また、OCaml-Humpに登録されている他のOCamlバインディングを追えば、実例を見つけることもできるでしょう。


このドキュメントを保存しているような場合には、更新や修正をウェブ上のオリジナルの場所でチェックすることができます(訳注:この和訳版は../../harddiskdive/にあります)。


© 2007-2008 Florent Monnier
© 2009,2010 KINUGAWA Akihito (Japanese translation)
You can use this document according to the terms of the GNU/FDL license.
Which means basically that you can redistribute this document with or without modifications, as long as you keep the copyright and the license.
Thanks to Eric Norige, for corrections of the English.