ctypesを使う際の(個人的な)ポイント

  • 38
    Like
  • 0
    Comment
More than 1 year has passed since last update.

ctypesはPythonから動的ライブラリを呼ぶための標準ライブラリ。最近初めて使った際に得た教訓/ハマったポイントをメモしておく。

restypeとargtypesは必ず指定する

動的ライブラリの関数を呼び出す時には、Cと互換性のある型を指定する必要がある。
(ref.) http://docs.python.jp/2/library/ctypes.html#ctypes-fundamental-data-types

この型の指定は、関数呼び出し時に指定するか、restype/argtypesにより戻り値/引数の型を呼び出し前にあらかじめ指定しておくか、のどちらかにより行う。

型の指定
from ctypes import *

# 実行時に指定
ret = test.func2(c_char_p("test"), c_char_p("test2"), c_char_p("test3"))

# restype/argtypesにより指定
test.func2.restype = c_char_p
test.func2.argtypes = (c_char_p, c_char_p, c_char_p)
ret = test.func2("test", "test2", "test3")

実行時に指定するのは呼び出しが多くなると面倒になってくるため、ライブラリのインターフェースを定義したモジュールを作るのが良いと考えている。Cのヘッダファイルと同じ感じか。

interface.py
from ctypes import *

test.func1.restype = c_int
test.func2.argtypes = (None)

test.func2.restype = c_char_p
test.func2.argtypes = (c_char_p, c_char_p, c_char_p)

構造体のポインタ型の指定

構造体はPythonの世界ではctypes.Structureを継承したクラスで表す。
構造体のメンバは、__fields__フィールドにタプルの配列で指定する。タプルは(<フィールド名>, <型>)という形式でなければならない。

構造体と対応するクラス
from ctypes import *

class JSON_T(Structure):
    __fields__ = [('type', c_int), ('refcount', c_size_t)]

この構造体のポインタ型を指定したい場合はctypes.POINTER関数を使う。
(ref.)http://docs.python.jp/2/library/ctypes.html#ctypes.POINTER

構造体のポインタ型
from ctypes import *

class JSON_T(Structure):
    __fields__ = [('type', c_int), ('refcount', c_size_t)]

test.func3.restype = c_char_p
test.func3.argtypes = (c_void_p, c_char_p, c_char_p, c_char_p, POINTER(JSON_T))

ポインタのポインタ型の指定

ctypes.c_char_pctypes.c_void_pなど元々ポインタを表している型については、ctypes.POINTER関数を使えばよい。

ポインタのポインタ型(元々ポインタを表す型)
from ctypes import *

test.func4.restype = c_char_p
test.func4.argtypes = (c_void_p, c_char_p, c_char_p, c_char_p, POINTER(c_void_p), POINTER(c_char_p))

これらを使用する際には、ctypes.c_char_p/ctypes.c_void_pで指定した変数を、ctypes.byref関数に与えて参照を取得する必要がある。

ポインタのポインタ型の引数を扱う
from ctypes import *

test.func4.restype = c_char_p
test.func4.argtypes = (c_void_p, c_char_p, c_char_p, c_char_p, POINTER(c_void_p), POINTER(c_char_p))

# arg1は既に定義済みとする
arg2 = 'str2'
arg3 = 'str3'
arg4 = 'str4'
void_p_p = c_void_p(None)
char_p_p = c_char_p(None)
# 以下だとポインタのポインタにNULLが代入されるためダメ
# void_p_p = None
# char_p_p = None

test.func4(arg1, arg2, arg3, arg4, byref(void_p_p), byref(char_p_p))

(注) ctypes.c_intなどプリミティブ型を表している型については今回使用していないので分からない。ctypes.POINTER関数を二重に使うのか?