PythonからOCamlにアクセスするための低レベルインターフェースを作ってる
Python Developers Festaに参加しました。OCamlでPythonの拡張モジュールを作っているという話をしていたら、何人かの人に興味を持ってもらって、色々と意見やアドバイスをいただきました。その時点ではPython/C APIとOCaml/C Interfaceの橋渡しをCで実装していたのですが、ctypesでやってみるのはどうかという話があったので早速やってみました。
OCaml/C Interfaceの全機能の実装はまだ行っていませんが,ソースコードをBitbucketで公開しました。
https://bitbucket.org/likr/otypes
otypes
OCaml/C InterfaceのC関数部分はctypesから呼び出せるので問題はないのですが、他にも数々のマクロが定義されているのでそれらをctypesで地道に実装しています。otypes.pyの一部を抜粋して以下に掲載します。
#!/usr/bin/env python import ctypes import sys class MLDLL(object): def __init__(self, name): self.lib = ctypes.CDLL(name) ArgArray = ctypes.c_char_p * (len(sys.argv) + 1) args = ArgArray() for i,arg in enumerate(sys.argv): args[i] = ctypes.c_char_p(arg) args[len(sys.argv)] = None self.lib.caml_startup(args) self.lib.caml_named_value.restype = ctypes.POINTER(ctypes.c_long) def caml_named_value(self, name): return self.lib.caml_named_value(ctypes.c_char_p(name)).contents def val_long(self, x): return (x << 1) + 1 def long_val(self, x): return x.value >> 1 def val_int(self, x): return self.val_long(x) def int_val(self, x): return self.long_val(x)
OCamlプログラムのビルド
以下のようなOCamlプログラムを用意してsample.mlとします。CやPythonなど外部からアクセスしたい値をCallback.registerで設定します。
let f x = x + 3 let sort_list list = List.sort (fun a b -> b - a) list let sort_array array = Array.iter (fun x -> Printf.printf "%d " x) array;print_newline (); Array.sort (fun a b -> b - a) array let _ = Callback.register "x" 6; Callback.register "y" 7.; Callback.register "f" f; Callback.register "sort_list" sort_list; Callback.register "sort_array" sort_array; Callback.register "tuple" ('a','b'); Callback.register "max3" (fun (a,b,c) -> max a (max b c))
ビルドには-output-objオプションを使ってCからリンク可能なsoファイルを作成します。
ocamlopt -c sample.ml ocamlopt -output-obj -o sample.so sample.cmx
OCamlMakefileを使う場合は以下のようにMakefileを記述します。
SOURCES = sample.ml RESULT = sample.so OCAMLLDFLAGS = -output-obj all: native-code include OCamlMakefile
OCamlプログラムのビルド(Mac)
当方のMac環境では上記の方法ではctypesからのロード時に以下のようなエラーが発生して動かすことができませんでした。
importing mllibrary Traceback (most recent call last): File "./test.py", line 7, in <module> lib = MLDLL('sample.so') File "/Users/likr/workspace/otypes/otypes.py", line 10, in __init__ self.lib = ctypes.CDLL(name) File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ctypes/__init__.py", line 353, in __init__ self._handle = _dlopen(self._name, mode) OSError: dlopen(sample.so, 6): Symbol not found: _caml_atom_table Referenced from: /Users/likr/workspace/otypes/test/sample.so Expected in: flat namespace in /Users/likr/workspace/otypes/test/sample.so
試行錯誤してみたところ、ccオプションを指定してCコンパイラのオプションを外してやることで正しく動くようになりました。原因がしっかりとわかっていないので要調査です。
ocamlopt -cc="gcc" -output-obj -o sample.so sample.cmx
Makefileの場合はこうなります。
make OCAMLLDFLAGS='-output-obj -cc "gcc"'
サンプル
otypesを使ってPythonからsample.mlの値にアクセスしてみます。
#!/usr/bin/env python # -*- coding: utf-8 -*- import otypes from otypes import MLDLL # ライブラリのロード print 'importing mllibrary' lib = MLDLL('sample.so') print # int値の取得 x = lib.caml_named_value('x') print lib.int_val(x) print # int -> int 関数の呼び出し f = lib.caml_named_value('f') res = lib.caml_callback(f, x) print lib.int_val(res) print # float値の取得 y = lib.caml_named_value('y') print lib.double_val(y) print # float値の作成 d = lib.caml_alloc(1, otypes.DOUBLE_TAG) lib.store_double_val(d, 0.3) print lib.double_val(d) print # int * int の取得 ab = lib.caml_named_value('tuple') print chr(lib.int_val(lib.field(ab, 0))) print chr(lib.int_val(lib.field(ab, 1))) print # int * int * int -> int 関数の呼び出し max3 = lib.caml_named_value('max3') arg = lib.caml_alloc(3, 0) lib.store_field(arg, 0, lib.val_int(3)) lib.store_field(arg, 1, lib.val_int(8)) lib.store_field(arg, 2, lib.val_int(5)) ret = lib.caml_callback(max3, arg) print lib.int_val(ret) print # Arrayのソート l = [1,4,2,35,64,3,12,14,46,57] sort_array = lib.caml_named_value('sort_array') ml_array = lib.caml_alloc(len(l), 0) for i,item in enumerate(l): lib.store_field(ml_array, i, lib.val_int(item)) lib.caml_callback(sort_array, ml_array) for i in range(len(l)): print lib.int_val(lib.field(ml_array, i)) print # Listのソート sort_list = lib.caml_named_value('sort_list') def cons(hd, tl): l = lib.caml_alloc(2, 1) lib.store_field(l, 0, hd) lib.store_field(l, 1, tl) return l tl = lib.val_emptylist() tl = cons(lib.val_int(57), tl) tl = cons(lib.val_int(46), tl) l = cons(lib.val_int(58), tl) l = lib.caml_callback(sort_list, l) print lib.int_val(lib.field(l, 0)) l = lib.field(l, 1) print lib.int_val(lib.field(l, 0)) l = lib.field(l, 1) print lib.int_val(lib.field(l, 0))