FIELD NOTES: 書を持って街へ出よう

合同会社フィールドワークス プログラマ兼代表のブログ

OCamlでWindows用DLLを作成する方法

同一のコードから複数のプラットフォーム用の共有ライブラリ(Unix系OSなら.so,Mac OS Xなら.dylib,Windowsなら*.DLL)を作りたい場合,どのプログラミング言語を選ぶべきでしょうか?

まず思い浮かぶのがC/C++だと思いますが,もっと「モダン」なプログラミング言語は使えないものでしょうか?

「モダン」なプログラミング言語とは

私の考えるモダンなプログラミング言語の要件は以下のとおりです。

  • オブジェクト指向または関数プログラミングを取り入れていること。
  • リスト,辞書などのデータ構造が言語仕様に組み込まれていること。
  • GCが搭載されていること。
  • segmentation fault が原理的に発生しないこと。

2年ほど前にこれらの要件満たし,かつネイティブ・オブジェクトを生成可能なプログラミング言語を探したところ, Lisp/Scheme系の言語と関数プログラミング言語の中からいくつか見つけることができました。

弊社ではこの中からOCamlを選択し,PDF帳票開発ツール Field Reportsを開発しました。 ネイティブなライブラリが必要だった理由は,Python, Ruby, PHP, Perlなど多くのLL言語から利用可能な拡張ライブラリを作るためでした。

OCamlで各LL言語用の拡張ライブラリを構築する手順については過去に何回か書いてきましたが, Unix系のOSでの話が中心でした。

Windowsでの構築手順を書いていなかったので,今回はOCamlのコードからDLLを作成するまでの手順を説明したいと思います。


らくだ / 31o5

用意するもの

Windows用OCaml処理系には,MinGWでビルドしたものとVisual C++でビルドしたものがあります。 今回は,MinGWでビルドしたバイナリを選択しました。

MinGW版のOCamlを使用するので,当然MinGW開発環境も必要になります。 ただ,標準のMinGW環境をベースに不足しているライブラリやコマンドを足していくのは結構手間が掛かります。 裏技的ですが,Ruby用の開発環境DevKitをベースにすると, 最初から必要な物が揃っているのでいくらか楽ができます。

ラッパー・プログラムの記述

OCamlがネイティブオブジェクトを出力できるといっても直接呼び出すことはできません。 薄いラッパーの層が必要になります。

以下の説明では,OCamlで記述したFooモジュールからビルドしたライブラリ foo.cmxa が既に存在しているものとし, Fooモジュールの中のbar関数を外部に公開するものとします。

OCamlコールバック関数の登録

まず,外部に公開するOCaml関数をコールバック関数として登録します。 以下のようなOCamlプログラムを作成して,foo_register.ml という名前で保存します。

let _ =
  Callback.register "bar" Foo.bar;

OCamlコールバック関数を呼び出すC関数

次に,OCamlのコールバック関数を呼び出すC関数を記述します。 これを foo_callback.c という名前で保存します。

#include <caml/callback.h>
void bar()
{
    static value * f = NULL;
    if (f == NULL) {
        f = caml_named_value("bar");
    }
    caml_callback(*f, Val_unit);
}

ビルド手順

(1) foo_register.mlのコンパイル

最初に foo_register.ml をコンパイルします。 -output-obj オプションを付けることにより,C形式のオブジェクトが出力されます。 この時,Fooモジュールから参照しているOCamlライブラリを含めると, ライブラリのコードも一つのオブジェクトファイルにまとめることができます。

$ ocamlopt -cclib -fPIC -output-obj -o foo_register.o -I <ライブラリ・パス> \
    <OCamlライブラリ> foo.cmxa foo_register.ml

(2) foo_callback.c のコンパイル

次にCソースをコンパイルします。

$ ocamlc -ccopt -I <インクルード・パス>  -c foo_callback.c

(3) DLLの作成

最後に(1), (2)とその他必要なライブラリ をすべてリンクしてDLLとしてまとめます。

$ flexlink -L<ライブラリパス> -maindll -show-imports -chain mingw \
      -o foo.dll foo_register.o foo_callback.o -lm -lstr -lunix -lasmrun

-lasmrunは必須です。 -lstr-lunixは,Strライブラリ,Unixライブラリを使用している場合に必要になります。 その他,-lmのようなライブラリを必要に応じてリンクします。

(4) インポートライブラリの作成

インポートライブラリがあると,暗黙的リンクが可能になります。 必要に応じてインポートライブラリを作成します。

$ dlltool -k -l libreports.a -d libreports.dll -d libreports.def

まとめ

OCamlでDLLを作成する手順が確立できました。 これで,Linux, Mac OS X, Windows用の共有ライブラリが作成できるようになりました。

残課題として以下があります。

64ビット版DLLがビルドできなかった。

64ビット版MinGWも存在するようなので,今後挑戦したいと思います。

Visual Studio からは,dlltoolで作成したインポートライブラリを利用できなかった。

今回は明示的リンクで回避しましたが,使用するAPIが多くなると大変かもしれません。 Visual C++版のバイナリを使用すれば問題なかったかもしれませんが, 特に必要を感じていないので試す予定はありません。

flexlinkでリンクする時にオブジェクトモジュールが大きくなるとエラーが発生する

flexlinkのバグなのかどうは判然としませんが,3.11ではflexlinkが必須になっていました を参考にパッチをあててエラーが出ないようにしてしまいました。 今のところ問題は出ていません。

参照サイト