サーチ…


前書き

ctypesは、コンパイルされたネイティブライブラリからエクスポートされた関数を呼び出すPythonビルトインライブラリです。

注:このライブラリはコンパイルされたコードを処理するため、比較的OSに依存します。

基本的な使用法

libcntohl関数を使いたいとしましょう。

まず、 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つの複雑なシナリオに結合しましょう: libclfind関数を使う。

この関数の詳細については、マニュアルページを参照してください 。私はあなたにそれを続ける前にそれを読むことを強く勧めます。

まず、適切なプロトタイプを定義します。

>>> 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

xyPOINTER(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)

また、 ptrarr内の正しい値を指していることが分かります。

>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow