Python Language
ctypes
サーチ…
前書き
ctypes
は、コンパイルされたネイティブライブラリからエクスポートされた関数を呼び出すPythonビルトインライブラリです。
注:このライブラリはコンパイルされたコードを処理するため、比較的OSに依存します。
基本的な使用法
libc
のntohl
関数を使いたいとしましょう。
まず、 libc.so
をロードlibc.so
必要があります。
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
次に、関数オブジェクトを取得します。
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
そして今、単に関数を呼び出すことができます:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
私たちが期待していることとまったく同じです。
共通の落とし穴
ファイルの読み込みに失敗しました
最初に考えられるエラーは、ライブラリをロードできません。その場合、通常はOSErrorが発生します。
これは、ファイルが存在しないか、またはOSによって検出されないためです。
>>> cdll.LoadLibrary("foobar.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: foobar.so: cannot open shared object file: No such file or directory
あなたが見ることができるように、エラーは明らかであり、かなり指示的です。
2番目の理由は、ファイルが見つかりましたが、正しい形式ではありません。
>>> cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/i386-linux-gnu/libc.so: invalid ELF header
この場合、ファイルはスクリプトファイルであり、 .so
ファイルではありません。これは、Linuxマシンで.dll
ファイルを開くときや、32ビットのPythonインタプリタで64ビットファイルを開くときにも起こります。ご覧のように、この場合、エラーはもう少し曖昧で、いくらか掘り下げる必要があります。
関数へのアクセスに失敗しました
.so
ファイルを正常に読み込んだとすると、最初の例のように関数にアクセスする必要があります。
存在しない関数が使用されると、 AttributeError
が発生します。
>>> libc.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/i386-linux-gnu/libc.so.6: undefined symbol: foo
基本ctypesオブジェクト
最も基本的なオブジェクトはintです:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
今、 obj
は値12を含むメモリのチャンクを参照します。
その値に直接アクセスしたり、変更したりすることもできます。
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
obj
はメモリの塊を指すので、サイズと場所も調べることができます:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
ctypes配列
優れたCプログラマが知っているように、単一の価値はあなたをそれほど遠ざけません。本当に私たちが行くのは配列です!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
これは実際の配列ではありませんが、かなり近いです!私たちは、16 int
の配列を表すクラスを作成しました。
これで初期化するだけです。
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
現在、 arr
は0から15までの数字を含む実際の配列です。
彼らはどんなリストと同様にアクセスすることができます:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
また、他のctypes
オブジェクトと同様に、サイズと場所も持っています:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
ctypesのラッピング関数
場合によっては、C関数は関数ポインタを受け取ります。熱心なctypes
ユーザーとして、私たちはこれらの関数を使用したいし、Python関数を引数として渡すことさえしたい。
関数を定義しましょう:
>>> def max(x, y):
return x if x >= y else y
この関数は2つの引数をとり、同じ型の結果を返します。この例のために、typeがintであるとしましょう。
配列の例と同様に、そのプロトタイプを示すオブジェクトを定義することができます:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
このプロトタイプは、 c_int
(最初の引数)を返す関数を示し、2つのc_int
引数(他の引数)をc_int
ます。
今度は関数をラップしましょう:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
関数プロトタイプには、さらに多くの用途があります。関数は( libc.ntohl
ように) ctypes
関数をラップし、関数を呼び出すときに正しい引数が使用されていることを確認できます。
>>> libc.ntohl() # garbage in - garbage out
>>> CFUNCTYPE(c_int, c_int)(libc.ntohl)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: this function takes at least 1 argument (0 given)
複雑な使用法
上記のすべての例を1つの複雑なシナリオに結合しましょう: libc
のlfind
関数を使う。
この関数の詳細については、マニュアルページを参照してください 。私はあなたにそれを続ける前にそれを読むことを強く勧めます。
まず、適切なプロトタイプを定義します。
>>> compar_proto = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>> lfind_proto = CFUNCTYPE(c_void_p, c_void_p, c_void_p, POINTER(c_uint), c_uint, compar_proto)
次に、変数を作成しましょう:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
次に、比較関数を定義します。
>>> def compar(x, y):
return x.contents.value - y.contents.value
x
とy
はPOINTER(c_int)
、実際にメモリに格納されている値を比較するには、それらを逆参照して値を取る必要があることに注意してください。
これですべてを組み合わせることができます:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
は返されたvoidポインタです。 arr
key
が見つからなかった場合、値はNone
になりますが、この場合は有効な値が得られます。
これで変換して値にアクセスできます:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
また、 ptr
がarr
内の正しい値を指していることが分かります。
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True