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

Pythonでポインタ経由のメモリアクセス

Pythonmalloc()したメモリへのアクセスを試しました。戻り値を POINTER(c_ubyte) へキャストするとアクセスできます。

※ 例は32bit Windows限定のコードです。UNIX系ではlibcのロード方法が少し違います。

from ctypes import *

malloc = cdll.msvcrt.malloc
malloc.restype = POINTER(c_ubyte)
free   = cdll.msvcrt.free
printf = cdll.msvcrt.printf

p = malloc(4)
p[0] = ord("a")
p[1] = ord("b")
p[2] = ord("c")
p[3] = 0
printf(p)
free(p)

スライス

POINTERはスライスできません。スライスできるようにするには、サイズを指定したARRAYをPOINTERで包みます。POINTERからARRAYを取り出すため、dereferenceで[0]とします。これらを行うラッパーを用意すれば便利です。

from ctypes import *

def malloc(size):
    malloc = cdll.msvcrt.malloc
    malloc.restype = POINTER(ARRAY(c_ubyte, size))
    return malloc(size)[0]

free   = cdll.msvcrt.free
printf = cdll.msvcrt.printf

p = malloc(4)
p[:] = [ord("a"), ord("b"), ord("c"), 0]
printf(p)
free(p)

ARRAYをPOINTERで包まないと戻り値そのものが配列と見なされ、おかしなことになります。

>>> from ctypes import *
>>> f = CFUNCTYPE(c_int)(lambda: 0x12345678)
>>> f.restype = ARRAY(c_ubyte, 4)
>>> map(lambda x: hex(x), f())
['0x78', '0x56', '0x34', '0x12']

JIT

malloc()の代わりにVirtualAlloc()を使えば、JIT用のバッファに直接マシン語を埋め込むことができます。

def VirtualAlloc(address, size, allocationType, protect):
    VirtualAlloc = windll.kernel32.VirtualAlloc
    VirtualAlloc.restype = POINTER(ARRAY(c_ubyte, size))
    VirtualAlloc.argtype = [c_void_p, c_size_t, c_int, c_int]
    return VirtualAlloc(address, size, allocationType, protect)[0]

ARRAYからポインタを取り出す方法は以下を参照してください。

UNIX系ではmmap()を使用します。以下を参照してください。