PythonでのFFI

Pythonでは、FFI(Foreign Function Interface)としてctypesモジュールを使います

DLL

読み込むDLLのCソース dll.c:

// gcc -fPIC -shared -o dll.so dll.c
typedef struct {
  int age;
} Foo;

Foo newFoo(int age) {
  Foo f;
  f.age = age;
  return f;
}

int getAge(Foo* foo) {
  return foo->age;
}

void callFoo(Foo* foo, void (*func)(Foo*)) {
  func(foo);
}

typedef Foo NewFunc(int);
NewFunc* getNew() {
  return &newFoo;
}

(とりあえず、いろいろやってます)。これをdll.soとしてスクリプトと同じディレクトリに置いてます。
余談ですけど、以下のと最後の関数は同じ(→C言語での複雑な型表記を理解する方法 - ラシウラ)ですが、自重してあります:

Foo (*getNew())(int) {
  return &newFoo;
}

Python

dll.soにアクセスするスクリプト

# load dll
import os
import ctypes

this_file = os.path.abspath(__file__)
dllfile = os.path.join(os.path.dirname(this_file), "dll.so")
dll = ctypes.CDLL(dllfile)


# struct
# typedef struct { int age; } Foo
class Foo(ctypes.Structure):
    _fields_ = [("age", ctypes.c_int)]

# call func
# Foo newFoo(int age)
# dll.newFoo.argtypes = [ctypes.c_int] # standard type is not required
dll.newFoo.restype = Foo
foo = dll.newFoo(10)
print foo.age

# pointer
# int getAge(Foo* foo)
dll.getAge.argtypes = [ctypes.POINTER(Foo)]
dll.getAge.restype = ctypes.c_int
# obj to pointer
print dll.getAge(ctypes.pointer(foo))

# func type
# void (*callback)(Foo*)
callbackType = ctypes.CFUNCTYPE(None, ctypes.POINTER(Foo))
# void callFoo(Foo* foo, void (*callback)(Foo*))
dll.callFoo.argtypes = [ctypes.POINTER(Foo), callbackType]
# callbacked func
def callback(pFoo):
    # pointer to obj
    foo = pFoo.contents
    print "foo.age: %d" % foo.age
    pass
# func obj
dll.callFoo(ctypes.pointer(foo), callbackType(callback))


# use returned c function
# typedef Foo (*PNewFunc)(int);
newfuncType = ctypes.CFUNCTYPE(Foo, ctypes.c_int)
# PNewFunc getNew()
dll.getNew.restype = newfuncType
newfunc = dll.getNew()
print newfunc
print newfunc.restype
bar = newfunc(20)
print bar.age

基本は、dllをロードして、使うCの関数にargtypesとrestypeをセットして呼び出すという感じです。標準でない型やポインタや関数は作ってやる必要があります。関数型はポインタ型になってます。

その他

野良DLLを読み込ませるには、上記のように絶対PATHを指定するか、LD_LIBRARY_PATHをpythonに渡すかする必要があります。

インストールされたDLLはファイル名を見つけるユーティリティがあるようです。

import ctypes.util
mfile = ctypes.util.find_library("m")

"lib"のあるなし、".so"や".dylib"、".dll"などの拡張子を補完するようです。

触れわすれたけど、配列は、型から*で作れます。

int5array = ctypes.c_int * 5
arr = int5array(1, 2, 3, 4, 5)
print arr[3] # => 4