OCamlのライブラリをコンパイルする正しい方法

もしOCamlのライブラリを拾ってきても、原則として添付のconfigure/Makefileは信用してはダメです。自分でコンパイルしましょう。さもないと後々トラブルに見舞われることでしょう。OCamlのライブラリ界隈はそんな感じです。
ここでは、既存のlibnice.aを使うためのOCamlのインターフェースライブラリとしてnice_stub.c, nice.mli, nice.mlが存在するとします。

Windows以外

C側のソースをコンパイルします。nice_stub.cを一応覗いてみて、中に#ifdefが書かれていた場合、必要なものを-Dで渡します。

gcc -isystem `ocamlc -where` -c -O2 nice_stub.c

OCaml側のソースをコンパイルします。OCamlには条件コンパイルなんて無いので楽です。

ocamlc -c nice.mli
ocamlc -c nice.ml
ocamlopt -c nice.ml

ocamlmklibを使って必要なファイルを作ります。

ocamlmklib nice_stub.o nice.cmo nice.cmx -o nice -oc mlnice -lnice

-ocでスタブの名前を変えているのは、そのままですとnice_stub.oからlibnice.aが作られて、既存のlibnice.aと被るからです。
またリンクするライブラリには決してパスを指定してはいけません。パスは-Lで指定してください。
これでdllmlnice.so, libmlnice.a, nice.cma, nice.cmxa, nice.aが作られます。
後はsudo cpするだけです。
dllmlnice.soを/usr/local/lib/ocaml/stublibs(`ocamlc -where`/stublibs)にコピーします。
libmlnice.a, nice.cma, nice.cmxa, nice.aと、nice.cmiを/usr/local/lib/ocaml(`ocamlc -where`)にコピーします。
G'Camlの場合は一旦nice_stub.o以外を消して、oをgに変えたコマンドを繰り返します。C側ヘッダーにはOCamlとG'Camlの違いは無いためnice_stub.oはコンパイルし直す必要はありません。ただしG'Camlはoptコンパイラを作れないのでopt関係は省きます。gcamlmklibがnice.cmxが無いぞと文句をいいますがバイトコードで使うために必要なファイルは作られますので無視してOKです。

gcamlc -c nice.mli
gcamlc -c nice.ml
gcamlmklib nice_stub.o nice.cmo -o nice -oc mlnice -lnice

dllmlnice.soを/usr/local/lib/gcaml/stublibs(`gcamlc -where`/stublibs)にコピーします。
nice.cmaと、nice.cmiを/usr/local/lib/gcaml(`gcamlc -where`)にコピーします。

OSXの場合でlibnice.aではなくlibnice.dylibの場合、リンク時にcyclicなんとか言われたら、-dylib_fileを追加します。-dylib_fileはダイナミックリンクするファイル名を明示するオプションです。同じファイル名をコロンで区切って2回繰り返します。*1

ocamlmklib nice_stub.o nice.cmo nice.cmx -o nice -oc mlnice -lnice -cclib "-dylib_file /fullpath/libnice.dylib:/fullpath/libnice.dylib"

ただし、-dylib_fileを使う必要があるものや、SDLのようなmain乗っ取り型ライブラリは、当然ocamlrunに影響を及ぼすことができないためバイトコードでは使えません……ocamlmktopしたらいけるかな?

Windows

Windows以外ですと、作者様が添付してくれているMakefileでも問題ないことは多いと思います。Windowsが問題なのです。おそらくこのページは、WindowsでのOCamlの正しいライブラリの作り方を解説した唯一のページになることでしょう……。

OCaml側のコンパイルは普通に行えます。

ocamlc -c nice.mli
ocamlc -c nice.ml
ocamlopt -c nice.ml

C側のコンパイルは……misc.hを見ていただければわかりますが、.dllか否かで条件コンパイル切られているのはWindowsの時のみです。従ってWindowsでは、.dllを作る時と、.aを作る時で、.cをコンパイルし直す必要があります。

#if defined(_WIN32) && defined(CAML_DLL)

幸いにして!?Windows版のOCamlにはocamlmklibが付いてこないため、ライブラリの構築は全て手動で行うことになります。これはビルドプロセスへの介入など厄介なことをしなくて済むようにとの配慮に違いありません。
とりあえず.cma/.cmxaを作るためにC側の実体が存在している必要は無いため、先に.cma/.cmxaを作っときましょう。

ocamlc -a -o nice.cma nice.cmo -dllib -lmlnice -cclib -lmlnice -cclib -lnice
ocamlopt -a -o nice.cmxa nice.cmx -cclib -lmlnice -cclib -lnice

user32.dllやgdi32.dllその他のAPIが使用されている場合は、-cclib -luser32や-cclib -lgdi32も必要に応じて指定します。
-dllibではスタブのみを指定します。libnice.aはdllmlnice.dllにスタティックリンクしてしまえばいいですし、もしnice.dllとして提供されている場合でもdllmlnice.dllがロード(インポートライブラリ or LoadLibrary)するように作れば-dllibに指定する必要はありません。むしろ指定してはダメです。後述しますが、-dllibにはflexlinkでリンクしたOCamlバイトコード用のスタブのみを指定しないと不具合が出ます。
さてC側です。
まず.aのほうを作りましょう。MinGW版を使います。VC++版をお使いの方は適当に読み替えてください。

gcc -c -O2 -isystem "C:\Program Files\Objective Caml\lib" nice_stub.c
ar q libmlnice.a nice_stub.o

-where?改行コードのせいでそんなものは使えません。フルパス手書きです。
最後に、dllmlnice.dllを作るのですが……まずコンパイル。-DCAML_DLLを指定します。

gcc -c -O2 -isystem "C:\Program Files\Objective Caml\lib" -DCAML_DLL nice_stub.c

後は.dllをリンクするだけなのですが……http://alain.frisch.fr/flexdll.htmlからflexlinkをダウンロードしてきてください。ファイルは全部/mingw/binに入れておけばOKです。OCamlのスタブは、このflexlinkを使用してリンクしなければなりません。ちなみにFlexDLLの日本語記事が読めるのはYak!さんの日記だけ!(最初ここだけと書こうとして検索したら見つけてしまいましたorz)

flexlink -show-imports -noentry -chain mingw -o dllmlnice.dll nice_stub.o \
  -L/mingw/lib \
  -defaultlib /mingw/lib/libnice.a \
  -defaultlib "C:\Program Files\Objective Caml\lib\ocamlrun.a"

UNIX系OSの.soは、持っているシンボルをプロセス内のモジュール全てに公開します。シンボル名だけで.so中の関数にアクセスできるわけです。Windowsの.dllは、シンボルは.dll毎に独立しています。flexlinkでリンクした.dllは、.soのように振舞う、というわけです。*2ここでリンクするライブラリの指定に-lではなく-defaultlibを使う理由は、-lですとocamlrun.aにあるシンボルまで公開されてしまうからです。
flexlinkを使わずにgcc -sharedやdlltoolでも、スタブ1個だけなら動作しますが、複数になると変な動きをします。flexlinkを使えばOKです。
ただし……それはあくまでocamlrun.exeから見ての話。flexlinkを使っても、スタブ間のシンボルの解決まではしてくれないのです。ここで-show-importsが重要です。例えば次のように表示されたとしましょう。

** Imported symbols for descriptor object:
_caml_ba_alloc_dims

_caml_ba_alloc_dimsはbigarray.h/dllbigarray.dllにある関数です。
いかにも自動で_caml_ba_alloc_dimsを解決してくれるようなメッセージですが、解決してくれないです。_caml_ba_alloc_dimsを呼び出そうとするとアクセス違反で落ちます。
というわけでインポートライブラリを作ります。.defファイルが必要です。次の内容をdllbigarray.defとして保存してください。一般的には、objdumpや、Borlandのtdumpを使えば.dllが公開しているシンボルの一覧を得られますので、それを加工すればいいです。impdefで直接.defを吐かせてもいいですが、どの道アンダーラインの有無など識別子の修飾は弄る必要があります。

EXPORTS
    caml_ba_alloc
    caml_ba_alloc_dims
    caml_ba_blit
    caml_ba_create
    caml_ba_dim
    caml_ba_fill
    caml_ba_get_1
    caml_ba_get_2
    caml_ba_get_3
    caml_ba_get_generic
    caml_ba_init
    caml_ba_kind
    caml_ba_layout
    caml_ba_map_file
    caml_ba_map_file_bytecode
    caml_ba_num_dims
    caml_ba_reshape
    caml_ba_set_1
    caml_ba_set_2
    caml_ba_set_3
    caml_ba_set_generic
    caml_ba_slice
    caml_ba_sub

インポートライブラリの作成は古典的にdlltool。

dlltool -l dllbigarray.a -D dllbigarray.dll -d dllbigarray.def

作成したdllbigarray.aをflexlinkに渡せばOK。

flexlink -show-imports -noentry -chain mingw -o dllmlnice.dll nice_stub.o \
  -L/mingw/lib \
  -defaultlib /mingw/lib/libnice.a \
  -defaultlib dllbigarray.a \
  -defaultlib "C:\Program Files\Objective Caml\lib\ocamlrun.a"

インポートライブラリ作るの面倒だし別にコード重複してもいいよってlibbigarray.aを静的リンクしても、うまく動いてくれませんでした。なんかあるのでしょう。
このような手順を、"** Imported symbols for descriptor object:"が表示されなくなるまで繰り返せば、dllmlnice.dllは完成です。
スタブをC:\Program Files\Objective Caml\lib\stublibsに、それ以外をC:\Program Files\Objective Caml\libにコピーすれば、めでたしめでたしとなります。
G'Camlの場合も、oをgに変えてopt抜きでと特に変わったことはないです。ocamlrun.aがgcamlrun.aになる点に注意ください。

*1:コロンってOS9以前のパス区切り記号だったような……?

*2:各所ではまるでWindowsの.dllがまるでプラグイン用途に使えないように書かれていますが、それは違います。GetProcAddressがシンボル名とモジュールハンドルの両方を要求するだけですね。WindowsではCコンパイラだけで相当数ありますが、ランタイムの関数名が衝突したりしないのもこの.dllの仕様のおかげです。逆に言えば.so怖過ぎる。gccに並び立つコンパイラが出てきたら世界は崩壊するんじゃないでしょうか。さて、Windowsでシンボル名だけで関数を特定したいような場合でも、ロードしたdllを片っ端からGetProcAddressして回ればいいわけですから、flexlinkやedllのようなものを導入してしまうのはどうかと思います。