【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

CFunctionTypeから関数ポインタを取り出す

Pythonでは関数のアドレスを指定して呼び出せます。以下の例は適当なアドレスなので、呼び出してもエラーになります。

>>> from ctypes import *
>>> f = CFUNCTYPE(None)(0x1234)
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: exception: access violation writing 0x00001234

逆に、fから0x1234を取り出すやり方がわかりません。

【追記】c_void_p(C言語での void *)にキャストすると取り出せます。

>>> def getaddr(x): return cast(x, c_void_p).value
...
>>> hex(getaddr(f))
'0x1234'

関数を作って通すという方法もあります。
【追記】以前はこれしか知らなかったため、こちらだけを記事に書いていました。キャストに比べてオーバーヘッドが多い方法です。

>>> getaddr = CFUNCTYPE(c_void_p, c_void_p)(lambda p: p)
>>> hex(getaddr(f))
'0x1234'

DLLからインポートした関数のアドレスや、Pythonから作ったサンクのアドレスも取り出せます。

>>> putchar = cdll.msvcrt.putchar
>>> hex(getaddr(putchar))
'0x77beef74'
>>> hex(getaddr(getaddr))
'0xa40fc0'

取り出したアドレスを直接指定すると、ちゃんと呼び出せることが確認できます。

>>> CFUNCTYPE(c_int, c_int)(0x77beef74)(65)
A65

上の例のputcharとgetaddrは、_objectsにデータを持っているようです。fにはありません。

>>> f._objects
>>> putchar._objects
{'0': <CDLL 'msvcrt', handle 77bc0000 at c7a7b0>}
>>> getaddr._objects
{'0': <_ctypes.CThunkObject object at 0x00C7FF38>}

CDLLやCThunkObjectはC言語で実装されているため、Pythonからポインタを取り出すことはできないようです。仮に取り出せたとしても種類によって内部のオブジェクトが異なるため、統一的にアドレスを取り出すにはgetaddrのような関数を使うしかなさそうです。