この章では C で記述したユーザ定義プリミティブに Caml のコードをリンクしたり、逆に Caml の関数からコールしたりする方法について解説します。
18.1 |
Overview and compilation information |
|
ユーザプリミティブは実装ファイルや struct...end モジュール式中で external キーワードを使って宣言します。
external name : type = C-function-name
これは変数名 name を type 型の関数として定義します。この関数を呼び出すと与えられた C 関数が実行されます。例えば、標準ライブラリのモジュール Pervasives で input プリミティブはこのように宣言されています。
external input : in_channel -> string -> int -> int -> int
= "input"
引数をとるプリミティブは常にカリー化されます。C の関数名は ML の関数名と同じである必要はありません。
このように定義された外部関数は、インターフェイスファイルや sig...end signature で通常の値として扱えます。
val name : type
このようにすると、この関数の実装が C の関数であるということは隠されます。また、明示的に外部関数であることを示すこともできます。
external name : type = C-function-name
このようにすると、このモジュールを使用する側は、対応する Caml の関数を通さず、直接 C の関数を呼び出すようになるので、若干効果的です。
プリミティブの arity (引数の数) は external 宣言で与えられた Caml の型をみて、関数適用の矢印の数を数えて自動的に決定します。例えば、先ほど示した input の場合は arity 4 で、C の関数 input は 4 引数の関数として呼び出されます。同様に、
external input2 : in_channel * string * int * int -> int = "input2"
は arity 1 で、C の関数 input2 は 1 引数 (quadruple な Caml 値) の関数になります。
プリミティブの atity を決定するとき、型の略記は展開されません。つまり、
type int_endo = int -> int
external f : int_endo -> int_endo = "f"
external g : (int -> int) -> (int -> int) = "f"
f は arity 1 になりますが、g は arity 2 になります。こうすることで、関数値を返すプリミティブを宣言できるようになります。上の f がその例です。返す値が関数型であるときは型の略記で名前をつけるということを覚えておいてください。(just remember to name the functional return type in a type abbreviation.)
18.1.2 |
Implementing primitives |
|
arity n が 璽 5 であるユーザプリミティブは、value 型の引数を n 個とり、n 型の結果を返す C の関数として記述します。value 型とは Caml の値の実体を示す型で、Caml のデータ構造だけでなく、ベースとなる型 (integers, floating-point numbers, strings, ...) のオブジェクトをエンコードしたものです。value 型や、それに対応する変換関数やマクロは後で細説します。例えば、input プリミティブの実装となる C の関数の宣言は以下のようになります。
CAMLprim value input(value channel, value buffer, value offset, value length)
{
...
}
Caml のプログラムでプリミティブ関数に何かの引数を適用すると、その引数式の値を引数として C の関数を呼び出します。関数が返す値は、関数適用の結果として Caml のプログラムに戻ることになります。
arity が 5 以上のユーザプリミティブは 2 つの C の関数として記述するようにしてください。1 つ目の関数は、バイトコードコンパイラ ocamlc と連動するもので、Caml の値 (引数の値) の配列へのポインタと、引数の数を表す整数の、2 つの引数を受け取ります。2 つ目の関数は、ネイティブコードコンパイラ ocamlopt と連動するもので、引数を直接受け取ります。例えば、7 引数のプリミティブ Nat.add_nat の C の関数は以下の 2 つになります。
CAMLprim value add_nat_native(value nat1, value ofs1, value len1,
value nat2, value ofs2, value len2,
value carry_in)
{
...
}
CAMLprim value add_nat_bytecode(value * argv, int argn)
{
return add_nat_native(argv[0], argv[1], argv[2], argv[3],
argv[4], argv[5], argv[6]);
}
プリミティブ宣言中では、以下のようにして C の関数名を 2 つ与えてください。
external name : type =
bytecode-C-function-name native-code-C-function-name
例えば add_nat の場合は、次のように宣言します。
external add_nat: nat -> int -> int -> nat -> int -> int -> int -> int
= "add_nat_bytecode" "add_nat_native"
ユーザプリミティブの実装は、実質的に 2 つの作業にわかれます。1 つ目は、与えられた引数を Caml の値から C の値にデコードしたり、逆に返値を Caml の値としてエンコードしたりする作業、2 つ目は、実際に引数から返値を計算する作業です。非常に単純なプリミティブ以外は、2 つの異なる C の関数を用意して、上記 2 つの作業をそれぞれ実装するようにしたほうがいいと思います。1 つ目の関数は C の値を引数にとり C の値を返すプリミティブの実体で、2 つ目の関数はスタブコードと呼ばれる 1 つ目の関数のラッパで、引数を Caml の値から C の値に変換して 1 つ目の関数を呼び出し、その返値を Caml の値に変換するものです。例えば input プリミティブのスタブコードは以下のようになります。
CAMLprim value input(value channel, value buffer, value offset, value length)
{
return Val_long(getblock((struct channel *) channel,
&Byte(buffer, Long_val(offset)),
Long_val(length)));
}
(ここで、Val_long や Long_val などは value 型の変換マクロです。後で解説します。CAMLprim はコンパイラ指示語のマクロで、関数をエクスポートさせて Caml からアクセスできるようにします。)
実際の作業は getblock 関数が行います。getblock 関数は以下のように記述します。
long getblock(struct channel * channel, char * p, long n)
{
...
}
Objective Caml の値を処理する C コードを書くには、以下のインクルードファイルを使用してください。
Include file |
Provides |
caml/mlvalues.h |
value 型や変換マクロの定義 |
caml/alloc.h |
アロケーション関数 (構造化された Caml オブジェクトを生成するときに使う) |
caml/memory.h |
メモリに関するさまざまな関数やマクロ (GC インターフェイスや、内部書き換え式の構造など) |
caml/fail.h |
例外を発生する関数群
(see section 18.4.5) |
caml/callback.h |
C から Caml へのコールバック (セクション 18.7) |
caml/custom.h |
カスタムブロックの処理群 (セクション 18.9) |
caml/intext.h |
カスタムブロックの、ユーザ定義のシリアライゼーションやデシリアライゼーション関数を書くための処理群 (セクション 18.9) |
これらのファイルは Objective Caml 標準ライブラリディレクトリの caml/ サブディレクトリの中にあります (通常は /usr/local/lib/ocaml) 。
18.1.3 |
Statically linking C code with Caml code |
|
Objective Caml のランタイムシステムは、バイトコードインタプリタ、メモリマネージャ、プリミティブ処理を実装となる C 関数の集合の 3 つの部分からなります。バイトコード命令には、関数のテーブル (プリミティブのテーブル) を指定されたオフセットでひいて得られる C の関数を呼び出すものがあります。
デフォルトモードで Caml のリンカが出力するのは、標準のプリミティブ集合からなる標準ランタイムシステム用のバイトコードです。この標準集合にないプリミティブを参照しようとしたら、「利用できない C プリミティブ」エラーとなります (ただし C ライブラリの動的ロードがサポートされていない場合。以下のセクション 18.1.4 を見てください) 。
「カスタムランタイム」モードでは、Caml のリンカはオブジェクトファイルをスキャンして、必要なプリミティブの集合を決定し、ネイティブコードリンカを呼んで適当なランタイムシステムをビルドします。このときネイティブコードリンカには、以下のものが渡されます。
- 必要なプリミティブのテーブル
- バイトコードインタプリタ、メモリマネージャ、標準プリミティブの 3 つを提供するライブラリ
- Caml リンカのコマンドラインに与えられた、ユーザプリミティブの実装を提供するライブラリやオブジェクトコードファイル (.o)
これで必要なプリミティブを持ったランタイムシステムがビルドされます。Caml リンカは、このカスタムランタイムシステム用のバイトコードを生成します。バイトコードはカスタムランタイムシステムの終端に追加されるので、この出力ファイル (カスタムランタイム + バイトコード) を起動すれば、自動的にこのバイトコードが実行されます。
「カスタムランタイム」モードでのリンクは、ocamlc に以下の引数を指定して実行して行ってください。
- -custom オプション。
- 必要な Caml オブジェクトファイルのファイル名 (.cmo や .cma) 。
- 必要なプリミティブを提供する C オブジェクトファイルやライブラリのファイル名 (.o や .a) 。Unix や Windows では、標準ライブラリディレクトリのどこかにある libname.a というファイル名のライブラリは、-cclib -lname として指定することもできます。
ネイティブコードコンパイラ ocamlopt を使うときは、-custom フラグは必要ありません。ocamlopt のリンクは最終的に常にスタンドアロンな実行ファイルをビルドします。Caml/C の混ざった実行ファイルのビルドは、ocamlopt に以下の引数を指定して実行して行ってください。
- 必要な Caml オブジェクトファイルのファイル名 (.cmx や .cmxa) 。
- 必要なプリミティブを提供する C オブジェクトファイルやライブラリのファイル名 (.o や .a 、.so 、.dll) 。
OCaml 3.00 から、Caml ライブラリファイル .cma、 .cmxa の中に、C ライブラリの名前だけでなく -custom オプションも記録できるようになりました。例として、Caml オブジェクトファイル a.cmo と b.cmo があって、これらが libmylib.a の C コードを参照しているとき、これらから Caml ライブラリ mylib.cma を生成することを考えます。以下のようにライブラリが生成されたら、
ocamlc -a -o mylib.cma -custom a.cmo b.cmo -cclib -lmylib
このライブラリのユーザは単純に mylib.cma をリンクするだけとなります。
ocamlc -o myprog mylib.cma ...
システムは自動的に -custom と -cclib -lmylib オプションを追加し、以下のようにしたときと同じように動作します。
ocamlc -o myprog -custom a.cmo b.cmo ... -cclib -lmylib
もちろんライブラリをビルドするとき余分なオプションをつけないで
ocamlc -a -o mylib.cma a.cmo b.cmo
ライブラリのユーザがリンク時に -custom と -cclib -lmylib オプションをつける必要があるということにもできます。
ocamlc -o myprog -custom mylib.cma ... -cclib -lmylib
けれど前者の方が、ライブラリのユーザには便利です。
18.1.4 |
Dynamically linking C code with Caml code |
|
OCaml 3.03 から、-custom を使った C コードの静的リンク以外の方法も可能になりました。このモードでは Caml リンカは、カスタムランタイムシステム用ではなく、純粋なバイトコードの実行ファイルを生成します。この実行ファイルには C コードを持つ動的ロードライブラリの名前が記録されています。標準 Caml ランタイムシステム ocamlrun はバイトコードの実行に移る前に、このライブラリを動的にロードして、必要なプリミティブの参照を解決します。
今のところ、この機能は Linux と Windows (native Windows port) でサポート/動作確認されています。FreeBSD 、Tru64 、Solaris 、Irix ではサポートはされていますが、まだ十分にテストできていません。他の Unix や、Windows/Cygwin 、MacOS ではまだサポートされていません。
C コードを Caml コードに動的リンクするには、まず C コードを、Unix では共有ライブラリに、Windows では DLL にコンパイルする必要があります。1) まず、C ファイルをコンパイルします。この際、position-independent コードを生成するように、適切なフラグを C コンパイラに与えてください。2) 次に得られたオブジェクトファイルから、共有ライブラリを生成します。こうして得られた共有ライブラリや DLL ファイルを、ocamlrun がプログラム起動時に見つけられる位置 (セクション 10.3 を見てください) にインストールしてください。3) 最後に、ocamlc コマンドに以下のオプションをつけて実行してください。
- 必要な Caml オブジェクトファイルのファイル名 (.cmo や .cma) 。
- 必要なプリミティブを提供する C 共有ライブラリのファイル名 (.so や .dll) 。Unix や Windows では、標準ライブラリディレクトリのどこかにある dllname.so (または .dll) というファイル名のライブラリは、-dllib -lname として指定することもできます。
-custom フラグは設定しないでください。設定するとセクション 18.1.3 で説明したような静的リンクとなってしまいます。Unix では、ocamlmklib ツールを使うと、2) と 3) のステップを自動でおこなえます。 (セクション 18.10 を見てください) 。
静的リンクの場合と同じように、Caml の .cmo ライブラリアーカイブの中に C のライブラリの名前を記録できます (この方法は推奨されています) 。今回も Caml オブジェクトファイル a.cmo と b.cmo があって、これらが dllmylib.so の C コードを参照していて、これらから Caml ライブラリ mylib.cma を生成することを考えます。以下のようにライブラリが生成されたら、
ocamlc -a -o mylib.cma a.cmo b.cmo -dllib -lmylib
このライブラリのユーザは単純に mylib.cma をリンクするだけと鳴ります。
ocamlc -o myprog mylib.cma ...
システムは自動的に -dllib -lmylib オプションを追加して、以下のようにしたときと同じように動作します。
ocamlc -o myprog a.cmo b.cmo ... -dllib -lmylib
このメカニズムを使うと、ライブラリ mylib.cma のユーザは、このライブラリが (-custom を使った) 静的リンクを行って C コードを参照しているのか、動的リンクを行っているのか関知する必要がありません。
18.1.5 |
Choosing between static linking and dynamic linking |
|
C コードを Caml コードにリンクする 2 種類の方法を説明し終えた上で、それぞれのプラス面とマイナス面について述べてみます。Caml と C ライブラリの混ざったプログラムを書く際、どちらの方法を選ぶかの参考にしてください。
動的リンクの主な利点は、バイトコード実行ファイルがプラットフォーム非依存になれるということです。つまり、バイトコード実行ファイルはマシンコードを含まないので、プラットフォーム A でコンパイルされたものは、必要な共有ライブラリが使用できるプラットフォーム B 、C ……でも実行可能だということです。それに対して、ocamlc -custom で生成された実行ファイルは、生成したプラットフォームに併せて調整されたカスタムランタイムシステムを含んでいるので、そのプラットフォームでしか動きません。さらに、動的リンクのほうは出力される実行ファイルのサイズが小さくなります。
動的リンクにはさらに利点があります。ライブラリの最終的なユーザは、C コンパイラ、C リンカ、C ランタイムライブラリをマシンにインストールしていなくてもよいのです。これは Unix や Cygwin においては大したことではありませんが、ocamlc -custom を行えるようにするためだけに Microsoft Visual C をインストールしたくはない多くの Windows ユーザにとってはうれしいことです。
動的リンクには 2 つ欠点があります。まず、実行ファイルがスタンドアロンでなくなり、実行するマシンには ocamlrun だけでなく、共有ライブラリもインストールされている必要があります。スタンドアロンな実行ファイルを頒布したい場合は、ocamlc -custom -ccopt -static や ocamlopt -ccopt -static を使って静的リンクしたもののほうがいいでしょう。また、動的リンクは「DLL 地獄」問題を引き起こします。共有ライブラリのバージョンが正しいか、起動時に確かめるよう、注意する必要があります。
動的リンクの 2 つ目の欠点は、ライブラリの構築が複雑になることです。position-independent なコードをコンパイルし、共有ライブラリをビルドするための C コンパイラやリンカのフラグは Unix システムごとにさまざまです。また、動的リンクはすべての Unix システムでサポートされているわけではありません。その場合のために、ライブラリの Makefile に静的リンクをおこなうように書く必要があります。ocamlmklib はこのようなシステム依存の部分をある程度隠すためにあります (セクション 18.10 を見てください) 。
まとめますと、native Windows port では、ポータビリティの問題がないことや、エンドユーザにとっての利便性を考えると、動的リンクが強く推奨されます。Unix では、安定したよく使われているライブラリに対しては、プラットフォーム非依存なバイトコードが得られる動的リンクがいいと思います。しかし新しいライブラリや滅多に使われていないライブラリに対しては、静的リンクのほうがずっと簡単にセットアップできます。
18.1.6 |
Building standalone custom runtime systems |
|
ocamlc -custom のように、Caml コードを C ライブラリをリンクするたびにカスタムランタイムシステムをビルドするのは、たまに面倒なことになります。(質の悪いリンカや遅いリモートファイルシステムのせいなどで) ランタイムシステムをビルドするのがすごく遅いシステムであるとか、プラットフォーム非依存なバイトコードファイルではないので、目的のプラットフォームごとに ocamlc -custom でリンクしなくてはならなくなるとかいった問題です。
ocamlc -custom に代わる方法として、必要な C ライブラリを含んだカスタムランタイムシステムを別途ビルドしておいて、その上で動く「純粋な」(ランタイムシステムを含んでいない) バイトコード実行ファイルを生成する方法があります。これは ocamlc に -make_runtime や -use_runtime のフラグを用いることで可能です。例えば、「Unix」と「Threads」ライブラリの C の部分を含んだカスタムランタイムシステムを作るには、以下のようにします。
ocamlc -make-runtime -o /home/me/ocamlunixrun unix.cma threads.cma
このランタイムシステムの上で動くバイトコード実行ファイルを生成するには、以下のようにします。
ocamlc -use-runtime /home/me/ocamlunixrun -o myprog \
unix.cma threads.cma your .cmo and .cma files
バイトコード実行ファイル myprog は通常右のように起動します。
myprog args or /home/me/ocamlunixrun myprog args.
バイトコードライブラリ unix.cma と threads.cma は、ランタイムシステムをビルドするときと、バイトコード実行ファイルをビルドするときの 2 回指定する必要があることに注意してください。前者は ocamlc に必要な C プリミティブを伝えるために、後者は unix.cma と threads.cma のバイトコードをを実際にリンクするために必要です。
すべての Caml オブジェクトは C の value 型として表されます。この型は、この型の値を処理するマクロと一緒に、インクルードファイル caml/mlvalues.h のなかで定義されています。value 型のオブジェクトは、以下のいずれかになります。
-
unbox な整数。
- ヒープの中のブロックへのポインタ (このブロックは以下の
alloc_*
関数のどれかを使って確保されます) 。
- ヒープの外のオブジェクトへのポインタ (例えば、malloc で確保されたブロックへのポインタや、C の変数へのポインタです) 。
整数値は 31 ビット符号付き整数です (64 ビットアーキテクチャでは 63 ビットです) 。これは unbox です (メモリ割り当てされていません) 。
ヒープのブロックはガベージコレクトされるので、構造は厳しく制限されています。それぞれのブロックにはヘッダがあり、ヘッダにはブロックのサイズ (ワード単位) と、ブロックのタグがあります。タグはブロックの中身がどのように構造化されているかを表します。No_scan_tag より小さいタグは、そのブロックが well-form な値を含む構造化ブロックであることを示し、ガベージコレクタはこの well-form な値を再帰的に辿ります。No_scan_tag と同じかそれより大きいタグは、そのブロックが未加工ブロックであることを示し、ガベージコレクタはこの中身をスキャンしません。大小比較や構造化された入出力のようにアドホックな多相プリミティブによって、構造化ブロックと未加工ブロックはタグによってさらに以下のように分類されます。
Tag |
Contents of the block |
0 to No_scan_tag-1 |
構造化ブロック (Caml オブジェクトの配列) 。それぞれのフィールドは value です。 |
Closure_tag |
関数値を表す closure 。最初のワードはコード部分へのポインタで、残りのワードは環境を含む value です。 |
String_tag |
文字列。 |
Double_tag |
double 精度の浮動小数点小数値。 |
Double_array_tag |
double 精度の浮動小数点小数値の配列やレコード。 |
Abstract_tag |
抽象データ型を表すブロック。 |
Custom_tag |
ユーザ定義の終了処理、比較、ハッシュ、シリアライゼーション、デシリアライゼーション関数を持つ抽象データ型を表すブロック。 |
18.2.3 |
Pointers outside the heap |
|
ヒープの外のアドレスを指すワードアラインされたポインタは、value 型と安全に、相互にキャストできます。このようなポインタには、malloc が返したポインタや、(最低でも 1 ワードのサイズを持つ) C の変数に &
演算子を用いて得られるポインタがあります。
警告: malloc が返したポインタを value 型にキャストして Caml に返したら、free を使ってポインタを明示的に解放するのは、Caml の世界からはまだこのポインタへアクセスできるかもしれないため、危険な場合があります。さらに悪いときには、あるメモリ空間を free で解放したあと、そこが Caml のヒープの部分として再割り当てが行われるかもしれません。元々 Caml のヒープの外を指していたポインタは、今では Caml のヒープの中を指していて、ガベージコレクタが混乱してしまいます。この問題を避けるために、ポインタは Abstract_tag か Custom_tag のタグで示されるような Caml のブロックでラップしたほうがいいでしょう。
18.3 |
Representation of Caml data types |
|
This section describes how Caml data types are encoded in the
value type.
Caml type |
Encoding |
int |
Unboxed integer values. |
char |
Unboxed integer values (ASCII code). |
float |
Blocks with tag Double_tag. |
string |
Blocks with tag String_tag. |
int32 |
Blocks with tag Custom_tag. |
int64 |
Blocks with tag Custom_tag. |
nativeint |
Blocks with tag Custom_tag. |
Tuples are represented by pointers to blocks, with tag 0.
Records are also represented by zero-tagged blocks. The ordering of
labels in the record type declaration determines the layout of
the record fields: the value associated to the label
declared first is stored in field 0 of the block, the value associated
to the label declared next goes in field 1, and so on.
As an optimization, records whose fields all have static type float
are represented as arrays of floating-point numbers, with tag
Double_array_tag. (See the section below on arrays.)
Arrays of integers and pointers are represented like tuples,
that is, as pointers to blocks tagged 0. They are accessed with the
Field macro for reading and the modify function for writing.
Arrays of floating-point numbers (type float array)
have a special, unboxed, more efficient representation.
These arrays are represented by pointers to blocks with tag
Double_array_tag. They should be accessed with the Double_field
and Store_double_field macros.
Constructed terms are represented either by unboxed integers (for
constant constructors) or by blocks whose tag encode the constructor
(for non-constant constructors). The constant constructors and the
non-constant constructors for a given concrete type are numbered
separately, starting from 0, in the order in which they appear in the
concrete type declaration. Constant constructors are represented by
unboxed integers equal to the constructor number. Non-constant
constructors declared with a n-tuple as argument are represented by
a block of size n, tagged with the constructor number; the n
fields contain the components of its tuple argument. Other
non-constant constructors are represented by a block of size 1, tagged
with the constructor number; the field 0 contains the value of the
constructor argument. Example:
Constructed term |
Representation |
() |
Val_int(0) |
false |
Val_int(0) |
true |
Val_int(1) |
[] |
Val_int(0) |
h::t |
Block with size = 2 and tag = 0; first field
contains h, second field t |
As a convenience, caml/mlvalues.h defines the macros Val_unit,
Val_false and Val_true to refer to (), false and true.
Objects are represented as zero-tagged blocks. The first field of the
block refers to the object class and associated method suite, in a
format that cannot easily be exploited from C. The remaining fields of
the object contain the values of the instance variables of the object.
Instance variables are stored in the order in which they appear in the
class definition (taking inherited classes into account).
Like constructed terms, values of variant types are represented either
as integers (for variants without arguments), or as blocks (for
variants with an argument). Unlike constructed terms, variant
constructors are not numbered starting from 0, but identified by a
hash value (a Caml integer), as computed by the C function
hash_variant (declared in <caml/mlvalues.h>):
the hash value for a variant constructor named, say, VConstr
is hash_variant("VConstr").
The variant value `VConstr is represented by
hash_variant("VConstr"). The variant value `VConstr(v) is
represented by a block of size 2 and tag 0, with field number 0
containing hash_variant("VConstr") and field number 1 containing
v.
Unlike constructed values, variant values taking several arguments are
not flattened. That is, `VConstr(v, v') is
represented by a block of size 2, whose field number 1 contains
the representation of the pair (v, v'), but not as a
block of size 3 containing v and v' in fields 1 and 2.
18.4 |
Operations on values |
|
-
Is_long(v) is true if value v is an immediate integer,
false otherwise
- Is_block(v) is true if value v is a pointer to a block,
and false if it is an immediate integer.
-
Val_long(l) returns the value encoding the long int l.
- Long_val(v) returns the long int encoded in value v.
- Val_int(i) returns the value encoding the int i.
- Int_val(v) returns the int encoded in value v.
- Val_bool(x) returns the Caml boolean representing the
truth value of the C integer x.
- Bool_val(v) returns 0 if v is the Caml boolean
false, 1 if v is true.
- Val_true, Val_false represent the Caml booleans true and false.
-
Wosize_val(v) returns the size of the block v, in words,
excluding the header.
- Tag_val(v) returns the tag of the block v.
- Field(v, n) returns the value contained in the
nth field of the structured block v. Fields are numbered from 0 to
Wosize_val(v)-1.
- Store_field(b, n, v) stores the value
v in the field number n of value b, which must be a
structured block.
- Code_val(v) returns the code part of the closure v.
- string_length(v) returns the length (number of characters)
of the string v.
- Byte(v, n) returns the nth character of the string
v, with type char. Characters are numbered from 0 to
string_length(v)-1.
- Byte_u(v, n) returns the nth character of the string
v, with type unsigned char. Characters are numbered from 0 to
string_length(v)-1.
- String_val(v) returns a pointer to the first byte of the string
v, with type char *. This pointer is a valid C string: there is a
null character after the last character in the string. However, Caml
strings can contain embedded null characters, that will confuse
the usual C functions over strings.
- Double_val(v) returns the floating-point number contained in
value v, with type double.
- Double_field(v, n) returns
the nth element of the array of floating-point numbers v (a
block tagged Double_array_tag).
- Store_double_field(v, n, d) stores the double precision floating-point number d
in the nth element of the array of floating-point numbers v.
- Data_custom_val(v) returns a pointer to the data part
of the custom block v. This pointer has type void * and must
be cast to the type of the data contained in the custom block.
- Int32_val(v) returns the 32-bit integer contained
in the int32 v.
- Int64_val(v) returns the 64-bit integer contained
in the int64 v.
- Nativeint_val(v) returns the long integer contained
in the nativeint v.
The expressions Field(v, n),
Byte(v, n) and
Byte_u(v, n)
are valid l-values. Hence, they can be assigned to, resulting in an
in-place modification of value v.
Assigning directly to Field(v, n) must
be done with care to avoid confusing the garbage collector (see
below).
-
Atom(t) returns an ``atom'' (zero-sized block) with tag t.
Zero-sized blocks are preallocated outside of the heap. It is
incorrect to try and allocate a zero-sized block using the functions below.
For instance, Atom(0) represents the empty array.
- alloc(n, t) returns a fresh block of size n
with tag t. If t is less than No_scan_tag, then the
fields of the block are initialized with a valid value in order to
satisfy the GC constraints.
- alloc_tuple(n) returns a fresh block of size
n words, with tag 0.
- alloc_string(n) returns a string value of length n characters.
The string initially contains garbage.
- copy_string(s) returns a string value containing a copy of
the null-terminated C string s (a char *).
- copy_double(d) returns a floating-point value initialized
with the double d.
- copy_int32(i), copy_int64(i) and
copy_nativeint(i) return a value of Caml type int32,
int64 and nativeint, respectively, initialized with the integer
i.
- alloc_array(f, a) allocates an array of values, calling
function f over each element of the input array a to transform it
into a value. The array a is an array of pointers terminated by the
null pointer. The function f receives each pointer as argument, and
returns a value. The zero-tagged block returned by
alloc_array(f, a) is filled with the values returned by the
successive calls to f. (This function must not be used to build
an array of floating-point numbers.)
- copy_string_array(p) allocates an array of strings, copied from
the pointer to a string array p (a
char **
).
The following functions are slightly more efficient than alloc, but
also much more difficult to use.
From the standpoint of the allocation functions, blocks are divided
according to their size as zero-sized blocks, small blocks (with size
less than or equal to Max_young_wosize
), and large blocks (with
size greater than Max_young_wosize
). The constant
Max_young_wosize
is declared in the include file mlvalues.h. It
is guaranteed to be at least 64 (words), so that any block with
constant size less than or equal to 64 can be assumed to be small. For
blocks whose size is computed at run-time, the size must be compared
against Max_young_wosize
to determine the correct allocation procedure.
-
alloc_small(n, t) returns a fresh small block of size
n 璽 Max_young_wosize words, with tag t.
If this block is a structured block (i.e. if t < No_scan_tag), then
the fields of the block (initially containing garbage) must be initialized
with legal values (using direct assignment to the fields of the block)
before the next allocation.
- alloc_shr(n, t) returns a fresh block of size
n, with tag t.
The size of the block can be greater than
Max_young_wosize
. (It
can also be smaller, but in this case it is more efficient to call
alloc_small instead of alloc_shr.)
If this block is a structured block (i.e. if t < No_scan_tag), then
the fields of the block (initially containing garbage) must be initialized
with legal values (using the initialize function described below)
before the next allocation.
Two functions are provided to raise two standard exceptions:
-
failwith(s), where s is a null-terminated C string (with
type
char *
), raises exception Failure with argument s.
- invalid_argument(s), where s is a null-terminated C
string (with type
char *
), raises exception Invalid_argument
with argument s.
Raising arbitrary exceptions from C is more delicate: the
exception identifier is dynamically allocated by the Caml program, and
therefore must be communicated to the C function using the
registration facility described below in section 18.7.3.
Once the exception identifier is recovered in C, the following
functions actually raise the exception:
-
raise_constant(id) raises the exception id with
no argument;
- raise_with_arg(id, v) raises the exception
id with the Caml value v as argument;
- raise_with_string(id, s), where s is a
null-terminated C string, raises the exception id with a copy of
the C string s as argument.
18.5 |
Living in harmony with the garbage collector |
|
Unused blocks in the heap are automatically reclaimed by the garbage
collector. This requires some cooperation from C code that
manipulates heap-allocated blocks.
All the macros described in this section are declared in the
memory.h header file.
Rule 1
A function that has parameters or local variables of type value must
begin with a call to one of the CAMLparam macros and return with
CAMLreturn or CAMLreturn0.
There are six CAMLparam macros: CAMLparam0 to CAMLparam5, which
take zero to five arguments respectively. If your function has fewer
than 5 parameters of type value, use the corresponding macros
with these parameters as arguments. If your function has more than 5
parameters of type value, use CAMLparam5 with five of these
parameters, and use one or more calls to the CAMLxparam macros for
the remaining parameters (CAMLxparam1 to CAMLxparam5).
The macros CAMLreturn and CAMLreturn0 are used to replace the C
keyword return. Every occurence of return x must be replaced by
CAMLreturn (x), every occurence of return without argument must be
replaced by CAMLreturn0. If your C function is a procedure (i.e. if
it returns void), you must insert CAMLreturn0 at the end (to replace
C's implicit return).
Note:
some C compilers give bogus warnings about unused
variables caml__dummy_xxx at each use of CAMLparam and
CAMLlocal. You should ignore them.
Example:
void foo (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
...
CAMLreturn0;
}
Note:
if your function is a primitive with more than 5 arguments
for use with the byte-code runtime, its arguments are not values and
must not be declared (they have types value * and int).
Rule 2
Local variables of type value must be declared with one of the
CAMLlocal macros. Arrays of values are declared with
CAMLlocalN.
The macros CAMLlocal1 to CAMLlocal5 declare and initialize one to
five local variables of type value. The variable names are given as
arguments to the macros. CAMLlocalN(x, n) declares
and initializes a local variable of type value [n]. You can
use several calls to these macros if you have more than 5 local
variables. You can also use them in nested C blocks within the
function.
Example:
value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = alloc (3, 0);
...
CAMLreturn (result);
}
Rule 3
Assignments to the fields of structured blocks must be done with the
Store_field macro (for normal blocks) or Store_double_field macro
(for arrays and records of floating-point numbers). Other assignments
must not use Store_field nor Store_double_field.
Store_field (b, n, v) stores the value
v in the field number n of value b, which must be a
block (i.e. Is_block(b) must be true).
Example:
value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = alloc (3, 0);
Store_field (result, 0, v1);
Store_field (result, 1, v2);
Store_field (result, 2, v3);
CAMLreturn (result);
}
Warning:
The first argument of Store_field and
Store_double_field must be a variable declared by CAMLparam* or
a parameter declared by CAMLlocal* to ensure that a garbage
collection triggered by the evaluation of the other arguments will not
invalidate the first argument after it is computed.
Rule 4 Global variables containing values must be registered
with the garbage collector using the register_global_root function.
Registration of a global variable v is achieved by calling
register_global_root(&v) just before a valid value is stored in v
for the first time.
A registered global variable v can be un-registered by calling
remove_global_root(&v).
Note: The CAML macros use identifiers (local variables, type
identifiers, structure tags) that start with caml__. Do not use any
identifier starting with caml__ in your programs.
We now give the GC rules corresponding to the low-level allocation
functions alloc_small and alloc_shr. You can ignore those rules
if you stick to the simplified allocation function alloc.
Rule 5 After a structured block (a block with tag less than
No_scan_tag) is allocated with the low-level functions, all fields
of this block must be filled with well-formed values before the next
allocation operation. If the block has been allocated with
alloc_small, filling is performed by direct assignment to the fields
of the block:
Field(v, n) = vn;
If the block has been allocated with alloc_shr, filling is performed
through the initialize function:
initialize(&Field(v, n), vn);
The next allocation can trigger a garbage collection. The garbage
collector assumes that all structured blocks contain well-formed
values. Newly created blocks contain random data, which generally do
not represent well-formed values.
If you really need to allocate before the fields can receive their
final value, first initialize with a constant value (e.g.
Val_unit), then allocate, then modify the fields with the correct
value (see rule 6).
Rule 6 Direct assignment to a field of a block, as in
Field(v, n) = w;
is safe only if v is a block newly allocated by alloc_small;
that is, if no allocation took place between the
allocation of v and the assignment to the field. In all other cases,
never assign directly. If the block has just been allocated by alloc_shr,
use initialize to assign a value to a field for the first time:
initialize(&Field(v, n), w);
Otherwise, you are updating a field that previously contained a
well-formed value; then, call the modify function:
modify(&Field(v, n), w);
To illustrate the rules above, here is a C function that builds and
returns a list containing the two integers given as parameters.
First, we write it using the simplified allocation functions:
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = alloc(2, 0); /* Allocate a cons cell */
Store_field(r, 0, Val_int(i2)); /* car = the integer i2 */
Store_field(r, 1, Val_int(0)); /* cdr = the empty list [] */
result = alloc(2, 0); /* Allocate the other cons cell */
Store_field(result, 0, Val_int(i1)); /* car = the integer i1 */
Store_field(result, 1, r); /* cdr = the first cons cell */
CAMLreturn (result);
}
Here, the registering of result is not strictly needed, because no
allocation takes place after it gets its value, but it's easier and
safer to simply register all the local variables that have type value.
Here is the same function written using the low-level allocation
functions. We notice that the cons cells are small blocks and can be
allocated with alloc_small, and filled by direct assignments on
their fields.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i2); /* car = the integer i2 */
Field(r, 1) = Val_int(0); /* cdr = the empty list [] */
result = alloc_small(2, 0); /* Allocate the other cons cell */
Field(result, 0) = Val_int(i1); /* car = the integer i1 */
Field(result, 1) = r; /* cdr = the first cons cell */
CAMLreturn (result);
}
In the two examples above, the list is built bottom-up. Here is an
alternate way, that proceeds top-down. It is less efficient, but
illustrates the use of modify.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (tail, r);
r = alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i1); /* car = the integer i1 */
Field(r, 1) = Val_int(0); /* A dummy value
tail = alloc_small(2, 0); /* Allocate the other cons cell */
Field(tail, 0) = Val_int(i2); /* car = the integer i2 */
Field(tail, 1) = Val_int(0); /* cdr = the empty list [] */
modify(&Field(r, 1), tail); /* cdr of the result = tail */
return r;
}
It would be incorrect to perform
Field(r, 1) = tail directly, because the allocation of tail
has taken place since r was allocated. tail is not registered as
a root because there is no allocation between the assignment where it
takes its value and the modify statement that uses the value.
This section outlines how the functions from the Unix curses library
can be made available to Objective Caml programs. First of all, here is
the interface curses.mli that declares the curses primitives and
data types:
type window (* The type "window" remains abstract *)
external initscr: unit -> window = "curses_initscr"
external endwin: unit -> unit = "curses_endwin"
external refresh: unit -> unit = "curses_refresh"
external wrefresh : window -> unit = "curses_wrefresh"
external newwin: int -> int -> int -> int -> window = "curses_newwin"
external mvwin: window -> int -> int -> unit = "curses_mvwin"
external addch: char -> unit = "curses_addch"
external mvwaddch: window -> int -> int -> char -> unit = "curses_mvwaddch"
external addstr: string -> unit = "curses_addstr"
external mvwaddstr: window -> int -> int -> string -> unit = "curses_mvwaddstr"
(* lots more omitted *)
To compile this interface:
ocamlc -c curses.mli
To implement these functions, we just have to provide the stub code;
the core functions are already implemented in the curses library.
The stub code file, curses.o, looks like:
#include <curses.h>
#include <mlvalues.h>
value curses_initscr(value unit)
{
CAMLparam1 (unit);
CAMLreturn ((value) initscr()); /* OK to coerce directly from WINDOW * to
value since that's a block created by malloc() */
}
value curses_wrefresh(value win)
{
CAMLparam1 (win);
wrefresh((WINDOW *) win);
CAMLreturn (Val_unit);
}
value curses_newwin(value nlines, value ncols, value x0, value y0)
{
CAMLparam4 (nlines, ncols, x0, y0);
CAMLreturn ((value) newwin(Int_val(nlines), Int_val(ncols),
Int_val(x0), Int_val(y0)));
}
value curses_addch(value c)
{
CAMLparam1 (c);
addch(Int_val(c)); /* Characters are encoded like integers */
CAMLreturn (Val_unit);
}
value curses_addstr(value s)
{
CAMLparam1 (s);
addstr(String_val(s));
CAMLreturn (Val_unit);
}
/* This goes on for pages. */
The file curses.c can be compiled with:
cc -c -I/usr/local/lib/ocaml curses.c
or, even simpler,
ocamlc -c curses.c
(When passed a .c file, the ocamlc command simply calls the C
compiler on that file, with the right -I option.)
Now, here is a sample Caml program test.ml that uses the curses
module:
open Curses
let main_window = initscr () in
let small_window = newwin 10 5 20 10 in
mvwaddstr main_window 10 2 "Hello";
mvwaddstr small_window 4 3 "world";
refresh();
for i = 1 to 100000 do () done;
endwin()
To compile this program, run:
ocamlc -c test.ml
Finally, to link everything together:
ocamlc -custom -o test test.cmo curses.o -cclib -lcurses
(On some machines, you may need to put -cclib -ltermcap or
-cclib -lcurses -cclib -ltermcap instead of -cclib -lcurses.)
18.7 |
Advanced topic: callbacks from C to Caml |
|
So far, we have described how to call C functions from Caml. In this
section, we show how C functions can call Caml functions, either as
callbacks (Caml calls C which calls Caml), or because the main program
is written in C.
18.7.1 |
Applying Caml closures from C |
|
C functions can apply Caml functional values (closures) to Caml values.
The following functions are provided to perform the applications:
-
callback(f, a) applies the functional value f to
the value a and return the value returned by f.
- callback2(f, a, b) applies the functional value f
(which is assumed to be a curried Caml function with two arguments) to
a and b.
- callback3(f, a, b, c) applies the functional value f
(a curried Caml function with three arguments) to a, b and c.
- callbackN(f, n, args) applies the functional value f
to the n arguments contained in the array of values args.
If the function f does not return, but raises an exception that
escapes the scope of the application, then this exception is
propagated to the next enclosing Caml code, skipping over the C
code. That is, if a Caml function f calls a C function g that
calls back a Caml function h that raises a stray exception, then the
execution of g is interrupted and the exception is propagated back
into f.
If the C code wishes to catch exceptions escaping the Caml function,
it can use the functions callback_exn, callback2_exn,
callback3_exn, callbackN_exn. These functions take the same
arguments as their non-_exn counterparts, but catch escaping
exceptions and return them to the C code. The return value v of the
callback*_exn functions must be tested with the macro
Is_exception_result(v). If the macro returns ``false'', no
exception occured, and v is the value returned by the Caml
function. If Is_exception_result(v) returns ``true'',
an exception escaped, and its value (the exception descriptor) can be
recovered using Extract_exception(v).
18.7.2 |
Registering Caml closures for use in C functions |
|
The main difficulty with the callback functions described above is
obtaining a closure to the Caml function to be called. For this
purpose, Objective Caml provides a simple registration mechanism, by
which Caml code can register Caml functions under some global name,
and then C code can retrieve the corresponding closure by this global
name.
On the Caml side, registration is performed by evaluating
Callback.register n v. Here, n is the global name
(an arbitrary string) and v the Caml value. For instance:
let f x = print_string "f is applied to "; print_int n; print_newline()
let _ = Callback.register "test function" f
On the C side, a pointer to the value registered under name n is
obtained by calling caml_named_value(n). The returned
pointer must then be dereferenced to recover the actual Caml value.
If no value is registered under the name n, the null pointer is
returned. For example, here is a C wrapper that calls the Caml function f
above:
void call_caml_f(int arg)
{
callback(*caml_named_value("test function"), Val_int(arg));
}
The pointer returned by caml_named_value is constant and can safely
be cached in a C variable to avoid repeated name lookups. On the other
hand, the value pointed to can change during garbage collection and
must always be recomputed at the point of use. Here is a more
efficient variant of call_caml_f above that calls caml_named_value
only once:
void call_caml_f(int arg)
{
static value * closure_f = NULL;
if (closure_f == NULL) {
/* First time around, look up by name */
closure_f = caml_named_value("test function");
}
callback(*closure_f, Val_int(arg));
}
18.7.3 |
Registering Caml exceptions for use in C functions |
|
The registration mechanism described above can also be used to
communicate exception identifiers from Caml to C. The Caml code
registers the exception by evaluating
Callback.register_exception n exn, where n is an
arbitrary name and exn is an exception value of the
exception to register. For example:
exception Error of string
let _ = Callback.register_exception "test exception" (Error "any string")
The C code can then recover the exception identifier using
caml_named_value and pass it as first argument to the functions
raise_constant, raise_with_arg, and raise_with_string (described
in section 18.4.5) to actually raise the exception. For
example, here is a C function that raises the Error exception with
the given argument:
void raise_error(char * msg)
{
raise_with_string(*caml_named_value("test exception"), msg);
}
In normal operation, a mixed Caml/C program starts by executing the
Caml initialization code, which then may proceed to call C
functions. We say that the main program is the Caml code. In some
applications, it is desirable that the C code plays the role of the
main program, calling Caml functions when needed. This can be achieved as
follows:
-
The C part of the program must provide a main function,
which will override the default main function provided by the Caml
runtime system. Execution will start in the user-defined main function
just like for a regular C program.
- At some point, the C code must call caml_main(argv) to
initialize the Caml code. The argv argument is a C array of strings
(type char **) which represents the command-line arguments, as
passed as second argument to main. The Caml array Sys.argv will
be initialized from this parameter. For the bytecode compiler,
argv[0] and argv[1] are also consulted to find the file containing
the bytecode.
- The call to caml_main initializes the Caml runtime system,
loads the bytecode (in the case of the bytecode compiler), and
executes the initialization code of the Caml program. Typically, this
initialization code registers callback functions using Callback.register.
Once the Caml initialization code is complete, control returns to the
C code that called caml_main.
- The C code can then invoke Caml functions using the callback
mechanism (see section 18.7.1).
18.7.5 |
Embedding the Caml code in the C code |
|
The bytecode compiler in custom runtime mode (ocamlc -custom)
normally appends the bytecode to the executable file containing the
custom runtime. This has two consequences. First, the final linking
step must be performed by ocamlc. Second, the Caml runtime library
must be able to find the name of the executable file from the
command-line arguments. When using caml_main(argv) as in
section 18.7.4, this means that argv[0] or argv[1] must
contain the executable file name.
An alternative is to embed the bytecode in the C code. The
-output-obj option to ocamlc is provided for this purpose.
It causes the ocamlc compiler to output a C object file (.o file)
containing the bytecode for the Caml part of the program, as well as a
caml_startup function. The C object file produced by ocamlc -output-obj can then be linked with C code using the standard C
compiler, or stored in a C library.
The caml_startup function must be called from the main C program in
order to initialize the Caml runtime and execute the Caml
initialization code. Just like caml_main, it takes one argv
parameter containing the command-line parameters. Unlike caml_main,
this argv parameter is used only to initialize Sys.argv, but not
for finding the name of the executable file.
The native-code compiler ocamlopt also supports the -output-obj
option, causing it to output a C object file containing the native
code for all Caml modules on the command-line, as well as the Caml
startup code. Initialization is performed by calling caml_startup as
in the case of the bytecode compiler.
For the final linking phase, in addition to the object file produced
by -output-obj, you will have to provide the Objective Caml runtime
library (libcamlrun.a for bytecode, libasmrun.a for native-code),
as well as all C libraries that are required by the Caml libraries
used. For instance, assume the Caml part of your program uses the
Unix library. With ocamlc, you should do:
ocamlc -output-obj -o camlcode.o unix.cma other .cmo and .cma files
cc -o myprog C objects and libraries \
camlcode.o -L/usr/local/lib/ocaml -lunix -lcamlrun
With ocamlopt, you should do:
ocamlopt -output-obj -o camlcode.o unix.cmxa other .cmx and .cmxa files
cc -o myprog C objects and libraries \
camlcode.o -L/usr/local/lib/ocaml -lunix -lasmrun
Warning:
On some ports, special options are required on the final
linking phase that links together the object file produced by the
-output-obj option and the remainder of the program. Those options
are shown in the configuration file config/Makefile generated during
compilation of Objective Caml, as the variables BYTECCLINKOPTS
(for object files produced by ocamlc -output-obj) and
NATIVECCLINKOPTS (for object files produced by ocamlopt -output-obj). Currently, the only ports that require special
attention are:
-
Alpha under Digital Unix / Tru64 Unix with gcc:
object files produced by ocamlc -output-obj must be linked with the
gcc options -Wl,-T,12000000 -Wl,-D,14000000.
This is not necessary for object files produced by ocamlopt -output-obj.
- Windows NT: the object file produced by Objective Caml have been
compiled with the /MT flag, and therefore all other object files
linked with it should also be compiled with /MT.
18.8 |
Advanced example with callbacks |
|
This section illustrates the callback facilities described in
section 18.7. We are going to package some Caml functions
in such a way that they can be linked with C code and called from C
just like any C functions. The Caml functions are defined in the
following mod.ml Caml source:
(* File mod.ml -- some ``useful'' Caml functions *)
let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2)
let format_result n = Printf.sprintf "Result is: %d\n" n
(* Export those two functions to C *)
let _ = Callback.register "fib" fib
let _ = Callback.register "format_result" format_result
Here is the C stub code for calling these functions from C:
/* File modwrap.c -- wrappers around the Caml functions */
#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
int fib(int n)
{
static value * fib_closure = NULL;
if (fib_closure == NULL) fib_closure = caml_named_value("fib");
return Int_val(callback(*fib_closure, Val_int(n)));
}
char * format_result(int n)
{
static value * format_result_closure = NULL;
if (format_result_closure == NULL)
format_result_closure = caml_named_value("format_result");
return strdup(String_val(callback(*format_result_closure, Val_int(n))));
/* We copy the C string returned by String_val to the C heap
so that it remains valid after garbage collection. */
}
We now compile the Caml code to a C object file and put it in a C
library along with the stub code in modwrap.c and the Caml runtime system:
ocamlc -custom -output-obj -o modcaml.o mod.ml
ocamlc -c modwrap.c
cp /usr/local/lib/ocaml/libcamlrun.a mod.a
ar r mod.a modcaml.o modwrap.o
(One can also use ocamlopt -output-obj instead of ocamlc -custom -output-obj. In this case, replace libcamlrun.a (the bytecode
runtime library) by libasmrun.a (the native-code runtime library).)
Now, we can use the two fonctions fib and format_result in any C
program, just like regular C functions. Just remember to call
caml_startup once before.
/* File main.c -- a sample client for the Caml functions */
#include <stdio.h>
int main(int argc, char ** argv)
{
int result;
/* Initialize Caml code */
caml_startup(argv);
/* Do some computation */
result = fib(10);
printf("fib(10) = %s\n", format_result(result));
return 0;
}
To build the whole program, just invoke the C compiler as follows:
cc -o prog main.c mod.a -lcurses
(On some machines, you may need to put -ltermcap or
-lcurses -ltermcap instead of -lcurses.)
18.9 |
Advanced topic: custom blocks |
|
Blocks with tag Custom_tag contain both arbitrary user data and a
pointer to a C struct, with type struct custom_operations, that
associates user-provided finalization, comparison, hashing,
serialization and deserialization functions to this block.
18.9.1 |
The struct custom_operations |
|
The struct custom_operations is defined in <caml/custom.h> and
contains the following fields:
-
char *identifier
A zero-terminated character string serving as an identifier for
serialization and deserialization operations.
- void (*finalize)(value v)
The finalize field contains a pointer to a C function that is called
when the block becomes unreachable and is about to be reclaimed.
The block is passed as first argument to the function.
The finalize field can also be NULL to indicate that no
finalization function is associated with the block.
IMPORTANT NOTE: the v parameter of this function is of type value, but
it must not be declared using the CAMLparam macros.
- int (*compare)(value v1, value v2)
The compare field contains a pointer to a C function that is
called whenever two custom blocks are compared using Caml's generic
comparison operators (=, <>, <=, >=, <, > and
compare). The C function should return 0 if the data contained in
the two blocks are structurally equal, a negative integer if the data
from the first block is less than the data from the second block, and
a positive integer if the data from the first block is greater than
the data from the second block.
NOTE: You must use CAMLparam to declare v1 and v2 and CAMLreturn
to return the result.
The compare field can be set to custom_compare_default; this
default comparison function simply raises Failure.
- long (*hash)(value v)
The hash field contains a pointer to a C function that is called
whenever Caml's generic hash operator (see module Hashtbl) is
applied to a custom block. The C function can return an arbitrary
long integer representing the hash value of the data contained in the
given custom block. The hash value must be compatible with the
compare function, in the sense that two structurally equal data
(that is, two custom blocks for which compare returns 0) must have
the same hash value.
NOTE: You must use CAMLparam to declare v and CAMLreturn
to return the result.
The hash field can be set to custom_hash_default, in which case
the custom block is ignored during hash computation.
- void (*serialize)(value v, unsigned long * wsize_32, unsigned long * wsize_64)
The serialize field contains a pointer to a C function that is
called whenever the custom block needs to be serialized (marshaled)
using the Caml functions output_value or Marshal.to_....
For a custom block, those functions first write the identifier of the
block (as given by the identifier field) to the output stream,
then call the user-provided serialize function. That function is
responsible for writing the data contained in the custom block, using
the serialize_... functions defined in <caml/intext.h> and listed
below. The user-provided serialize function must then store in its
wsize_32 and wsize_64 parameters the sizes in bytes of the data
part of the custom block on a 32-bit architecture and on a 64-bit
architecture, respectively.
NOTE: You must use CAMLparam to declare v and CAMLreturn
to return the result.
The serialize field can be set to custom_serialize_default,
in which case the Failure exception is raised when attempting to
serialize the custom block.
- unsigned long (*deserialize)(void * dst)
The deserialize field contains a pointer to a C function that is
called whenever a custom block with identifier identifier needs to
be deserialized (un-marshaled) using the Caml functions input_value
or Marshal.from_.... This user-provided function is responsible for
reading back the data written by the serialize operation, using the
deserialize_... functions defined in <caml/intext.h> and listed
below. It must then rebuild the data part of the custom block
and store it at the pointer given as the dst argument. Finally, it
returns the size in bytes of the data part of the custom block.
This size must be identical to the wsize_32 result of
the serialize operation if the architecture is 32 bits, or
wsize_64 if the architecture is 64 bits.
The deserialize field can be set to custom_deserialize_default
to indicate that deserialization is not supported. In this case,
do not register the struct custom_operations with the deserializer
using register_custom_operations (see below).
18.9.2 |
Allocating custom blocks |
|
Custom blocks must be allocated via the alloc_custom function.
alloc_custom(ops, size, used, max)
returns a fresh custom block, with room for size bytes of user
data, and whose associated operations are given by ops (a
pointer to a struct custom_operations, usually statically allocated
as a C global variable).
The two parameters used and max are used to control the
speed of garbage collection when the finalized object contains
pointers to out-of-heap resources. Generally speaking, the
Caml incremental major collector adjusts its speed relative to the
allocation rate of the program. The faster the program allocates, the
harder the GC works in order to reclaim quickly unreachable blocks
and avoid having large amount of ``floating garbage'' (unreferenced
objects that the GC has not yet collected).
Normally, the allocation rate is measured by counting the in-heap size
of allocated blocks. However, it often happens that finalized
objects contain pointers to out-of-heap memory blocks and other resources
(such as file descriptors, X Windows bitmaps, etc.). For those
blocks, the in-heap size of blocks is not a good measure of the
quantity of resources allocated by the program.
The two arguments used and max give the GC an idea of how
much out-of-heap resources are consumed by the finalized block
being allocated: you give the amount of resources allocated to this
object as parameter used, and the maximum amount that you want
to see in floating garbage as parameter max. The units are
arbitrary: the GC cares only about the ratio used / max.
For instance, if you are allocating a finalized block holding an X
Windows bitmap of w by h pixels, and you'd rather not
have more than 1 mega-pixels of unreclaimed bitmaps, specify
used = w * h and max = 1000000.
Another way to describe the effect of the used and max
parameters is in terms of full GC cycles. If you allocate many custom
blocks with used / max = 1 / N, the GC will then do one
full cycle (examining every object in the heap and calling
finalization functions on those that are unreachable) every N
allocations. For instance, if used = 1 and max = 1000,
the GC will do one full cycle at least every 1000 allocations of
custom blocks.
If your finalized blocks contain no pointers to out-of-heap resources,
or if the previous discussion made little sense to you, just take
used = 0 and max = 1. But if you later find that the
finalization functions are not called ``often enough'', consider
increasing the used / max ratio.
18.9.3 |
Accessing custom blocks |
|
The data part of a custom block v can be
accessed via the pointer Data_custom_val(v). This pointer
has type void * and should be cast to the actual type of the data
stored in the custom block.
The contents of custom blocks are not scanned by the garbage
collector, and must therefore not contain any pointer inside the Caml
heap. In other terms, never store a Caml value in a custom block,
and do not use Field, Store_field nor modify to access the data
part of a custom block. Conversely, any C data structure (not
containing heap pointers) can be stored in a custom block.
18.9.4 |
Writing custom serialization and deserialization functions |
|
The following functions, defined in <caml/intext.h>, are provided to
write and read back the contents of custom blocks in a portable way.
Those functions handle endianness conversions when e.g. data is
written on a little-endian machine and read back on a big-endian machine.
Function |
Action |
serialize_int_1 |
Write a 1-byte integer |
serialize_int_2 |
Write a 2-byte integer |
serialize_int_4 |
Write a 4-byte integer |
serialize_int_8 |
Write a 8-byte integer |
serialize_float_4 |
Write a 4-byte float |
serialize_float_8 |
Write a 8-byte float |
serialize_block_1 |
Write an array of 1-byte quantities |
serialize_block_2 |
Write an array of 2-byte quantities |
serialize_block_4 |
Write an array of 4-byte quantities |
serialize_block_8 |
Write an array of 8-byte quantities |
deserialize_uint_1 |
Read an unsigned 1-byte integer |
deserialize_sint_1 |
Read a signed 1-byte integer |
deserialize_uint_2 |
Read an unsigned 2-byte integer |
deserialize_sint_2 |
Read a signed 2-byte integer |
deserialize_uint_4 |
Read an unsigned 4-byte integer |
deserialize_sint_4 |
Read a signed 4-byte integer |
deserialize_uint_8 |
Read an unsigned 8-byte integer |
deserialize_sint_8 |
Read a signed 8-byte integer |
deserialize_float_4 |
Read a 4-byte float |
deserialize_float_8 |
Read an 8-byte float |
deserialize_block_1 |
Read an array of 1-byte quantities |
deserialize_block_2 |
Read an array of 2-byte quantities |
deserialize_block_4 |
Read an array of 4-byte quantities |
deserialize_block_8 |
Read an array of 8-byte quantities |
deserialize_error |
Signal an error during deserialization;
input_value or Marshal.from_... raise a Failure exception after
cleaning up their internal data structures |
Serialization functions are attached to the custom blocks to which
they apply. Obviously, deserialization functions cannot be attached
this way, since the custom block does not exist yet when
deserialization begins! Thus, the struct custom_operations that
contain deserialization functions must be registered with the
deserializer in advance, using the register_custom_operations
function declared in <caml/custom.h>. Deserialization proceeds by
reading the identifier off the input stream, allocating a custom block
of the size specified in the input stream, searching the registered
struct custom_operation blocks for one with the same identifier, and
calling its deserialize function to fill the data part of the custom block.
Identifiers in struct custom_operations must be chosen carefully,
since they must identify uniquely the data structure for serialization
and deserialization operations. In particular, consider including a
version number in the identifier; this way, the format of the data can
be changed later, yet backward-compatible deserialisation functions
can be provided.
Identifiers starting with _ (an underscore character) are reserved
for the Objective Caml runtime system; do not use them for your custom
data. We recommend to use a URL
(http://mymachine.mydomain.com/mylibrary/version-number)
or a Java-style package name
(com.mydomain.mymachine.mylibrary.version-number)
as identifiers, to minimize the risk of identifier collision.
Custom blocks generalize the finalized blocks that were present in
Objective Caml prior to version 3.00. For backward compatibility, the
format of custom blocks is compatible with that of finalized blocks,
and the alloc_final function is still available to allocate a custom
block with a given finalization function, but default comparison,
hashing and serialization functions. alloc_final(n, f, used, max) returns a fresh custom block of
size n words, with finalization function f. The first
word is reserved for storing the custom operations; the other
n-1 words are available for your data. The two parameters
used and max are used to control the speed of garbage
collection, as described for alloc_custom.
18.10 |
Building mixed C/Caml libraries: ocamlmklib |
|
The ocamlmklib command facilitates the construction of libraries
containing both Caml code and C code, and usable both in static
linking and dynamic linking modes.
Windows:
This command is available only under Cygwin, but not for the
native Win32 port.
MacOS:
This command is not available.
The ocamlmklib command takes three kinds of arguments:
-
Caml source files and object files (.cmo, .cmx, .ml)
comprising the Caml part of the library;
- C object files (.o, .a) comprising the C part of the
library;
- Support libraries for the C part (-llib).
It generates the following outputs:
-
A Caml bytecode library .cma incorporating the .cmo and
.ml Caml files given as arguments, and automatically referencing the
C library generated with the C object files.
- A Caml native-code library .cmxa incorporating the .cmx and
.ml Caml files given as arguments, and automatically referencing the
C library generated with the C object files.
- If dynamic linking is supported on the target platform, a
.so shared library built from the C object files given as arguments,
and automatically referencing the support libraries.
- A C static library .a built from the C object files.
In addition, the following options are recognized:
-
-cclib, -ccopt, -I, -linkall
-
These options are passed as is to ocamlc or ocamlopt.
See the documentation of these commands.
- -pthread, -rpath, -R, -Wl,-rpath, -Wl,-R
-
These options are passed as is to the C compiler. Refer to the
documentation of the C compiler.
- -custom
- Force the construction of a statically linked library
only, even if dynamic linking is supported.
- -failsafe
- Fall back to building a statically linked library
if a problem occurs while building the shared library (e.g. some of
the support libraries are not available as shared libraries).
- -Ldir
- Add dir to the search path for support
libraries (-llib).
- -ocamlc cmd
- Use cmd instead of ocamlc to call
the bytecode compiler.
- -ocamlopt cmd
- Use cmd instead of ocamlopt to call
the native-code compiler.
- -o output
- Set the name of the generated Caml library.
ocamlmklib will generate output.cma and/or output.cmxa.
If not specified, defaults to a.
- -oc outputc
- Set the name of the generated C library.
ocamlmklib will generate liboutputc.so (if shared
libraries are supported) and liboutputc.a.
If not specified, defaults to the output name given with -o.
Example
Consider a Caml interface to the standard libz
C library for reading and writing compressed files. Assume this
library resides in /usr/local/zlib. This interface is
composed of a Caml part zip.cmo/zip.cmx and a C part zipstubs.o
containing the stub code around the libz entry points. The
following command builds the Caml libraries zip.cma and zip.cmxa,
as well as the companion C libraries dllzip.so and libzip.a:
ocamlmklib -o zip zip.cmo zip.cmx zipstubs.o -lz -L/usr/local/zlib
If shared libraries are supported, this performs the following
commands:
ocamlc -a -o zip.cma zip.cmo -dllib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -cclib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
gcc -shared -o dllzip.so zipstubs.o -lz -L/usr/local/zlib
ar rc libzip.a zipstubs.o
If shared libraries are not supported, the following commands are
performed instead:
ocamlc -a -custom -o zip.cma zip.cmo -cclib -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ar rc libzip.a zipstubs.o
Instead of building simultaneously the bytecode library, the
native-code library and the C libraries, ocamlmklib can be called
three times to build each separately. Thus,
ocamlmklib -o zip zip.cmo -lz -L/usr/local/zlib
builds the bytecode library zip.cma, and
ocamlmklib -o zip zip.cmx -lz -L/usr/local/zlib
builds the native-code library zip.cmxa, and
ocamlmklib -o zip zipstubs.o -lz -L/usr/local/zlib
builds the C libraries dllzip.so and libzip.a. Notice that the
support libraries (-lz) and the corresponding options
(-L/usr/local/zlib) must be given on all three invocations of ocamlmklib,
because they are needed at different times depending on whether shared
libraries are supported.