2014年3月16日日曜日

[OCaml]FFIでhello.world!

OCamlからFFI(Foreign Function Interface)を使い、Cで書いた hello, world! を出力する関数を呼び出す。

準備

ctypesパッケージが必要

opamでインストールできる。
$ opam install ctypes

libffiが必要

$ dpkg --list | grep libffi
ii  libffi-dev:i38 3.0.10-3     i386         Foreign Function Interface library
ii  libffi5:i386   3.0.10-3     i386         Foreign Function Interface library

私のシステム(Debian Wheezy)にはもうインストールされていた。

ソース

hello.h

void print_hello(void);

hello.c

#include <stdio.h>
#include "hello.h"

void print_hello(void) {
  printf("hello, world!\n");
}


hello.ml

open Ctypes
open Foreign

let print_hello =
  foreign "print_hello" (void @-> returning void)


hello.mli

val print_hello : unit -> unit

main.ml

open Hello

let () =
  print_hello ();


コンパイル

共有ライブラリの作成

$ gcc -c -fPIC -Wall hello.c
$ gcc -shared -o libhello.so hello.o


OCamlソースのコンパイル

$ ocamlfind ocamlopt -linkpkg hello.mli
$ ocamlfind ocamlopt -linkpkg -package ctypes,ctypes.foreign hello.ml
$ ocamlfind ocamlopt -cclib libhello.so -linkpkg -g -thread -package ctypes.foreign -package core -o main hello.cmx


実行

$ LD_LIBRARY_PATH=. ./main
hello, world!

動いた!

まとめ

簡単そうだったけど、試行錯誤したので、メモを残しておこう。

静的ライブラリは使えない(使えなかった)

最初は静的ライブラリが使えると思っていた。
いろいろ試していたのだけど、
Fatal error: exception Dl.DL_error("〜/main.native: undefined symbol: print_hello")
というエラーメッセージを見て、動的ライブラリでないと駄目だということに気が付いた。

リンカへのライブラリ指定

hello.cを置いたディレクトリの下に、ocamlソース用のディレクトリを作成して、corebuildコマンドでビルドしていた。
$ corebuild -pkg ctypes.foreign -lflags -cclib,-L../..,-cclib,libhello.so main.native
実行すると、
$ ./main.native
main.native: error while loading shared libraries: ../../libhello.so: cannot open shared object file: No such file or directory
となってしまい、../../libhello.soを参照してしまう。
カレントディレクトリにlibhello.soを置いて、corebuildを実行してみたが、余計なファイルがあると、以下のようなエラーが発生し、ビルドできない。
$ corebuild -pkg ctypes.foreign -lflags -cclib,libhello.so main.native
SANITIZE: a total of 1 file that should probably not be in your source tree
  has been found. A script shell file
  "/home/satoshi/workspace/real_world_ocaml/ffi/ocaml/_build/sanitize.sh" is
  being created. Check this script and run it to remove unwanted files or use
  other options (such as defining hygiene exceptions or using the -no-hygiene
  option).
IMPORTANT: I cannot work with leftover compiled files.
ERROR: Leftover object files:
  File libhello.so in . has suffix .so
Exiting due to hygiene violations.
あきらめて、ビルドにはocamlfindコマンドを使うことにした。

シグネチャの作成

corebuildを使って、以下のようなコマンドを実行すると、mlファイルからシグネチャを作ることができる。
$ corebuild -pkg ctypes.foreign hello.inferred.mli
モジュールの公開メソッドが多いとき、自分で一から書くよりは楽ちん。

参考

Real World OCamlがオンラインで公開されている。