如何在Python中使用C的malloc分配的内存(ctypes)
我猜下面的代码应该能正常工作,但实际上并没有。有人能告诉我怎么使用这种指针吗?当然,这只是一个示例代码。我需要解决的问题是使用一个来自外部库的指针(这个库有自己的内存分配器'library_allocete_memory',它内部使用的是malloc)。我需要从Python代码中把数据复制到那块内存里。
import ctypes
libc = ctypes.cdll.msvcrt
data = b'test data'
size = len(data)
ptr = libc.malloc(size)
ptr = ctypes.cast(ptr, ctypes.c_char_p)
# ptr = ctypes.create_string_buffer(size) # this is working, but I cannot allocate memory this way in my case
ctypes.memmove(ptr, data, size) # access violation is thrown here
4 个回答
在你当前的例子中,libc.malloc.restype = ctypes.c_void_p
是必要的,这样可以确保返回的类型是指针大小的,而不是默认的 c_int
大小(32位)。在64位操作系统上,如果不这样做,返回的64位指针会被截断成32位。libc.malloc.argtypes = ctypes.c_size_t,
也是个好主意。最好总是为用 ctypes
调用的函数声明 .argtypes
和 .restype
。
工作示例:
import ctypes as ct
libc = ct.CDLL('msvcrt')
libc.malloc.argtypes = ct.c_size_t,
libc.malloc.restype = ct.c_void_p
libc.free.argtypes = ct.c_void_p,
libc.free.restype = None
data = b'test data'
size = len(data)
ptr = libc.malloc(size)
ct.memmove(ptr, data, size)
print(ct.string_at(ptr, size))
libc.free(ptr)
还要注意,msvcrt
是C语言运行时的动态链接库,但通常不是应用程序链接的那个。通过C语言运行时分配的内存必须由同一个运行时释放(释放内存)。正如提问者提到的:
我需要解决的问题是使用来自外部库的指针(它有自己的分配器 'library_allocete_memory'(原文如此),内部使用 malloc)。
确保只访问那个“来自外部库的指针”,并将其发送回那个库以释放它('library_free_memory'??)。
对于回调,要小心不要使用 ctypes.c_char_p
或 ctypes.c_wchar_p
作为指针类型。这些是特殊的 ctypes
类型,会将值转换为Python的 bytes
或 str
对象,原始的指针地址会丢失。应该使用 ctypes.c_void_p
(根据写入的数据,你可能需要进行类型转换)或者 ctypes.POINTER
类型,这样指针就可以通过 ctypes.memmove
或其他写入指针的方法使用。
谢谢大家!缺少的只是通过restype方法设置malloc或webui_malloc的返回类型:
libc.malloc.restype = ct.c_void_p
这里有两个可能导致未定义行为的地方:
根据[Python.Docs]: ctypes - 加载动态链接库(强调是我自己的):
注意:通过
cdll.msvcrt
访问标准C库将使用一个过时的库版本,这可能与Python使用的版本不兼容。如果可能,使用原生的Python功能,或者导入并使用msvcrt模块。[SO]: 从Python通过ctypes调用的C函数返回错误值(@CristiFati的回答)。这是使用CTypes(调用函数)时常见的陷阱。
这里有一段在(稍微不同的场景中)有效的代码。
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
from ctypes.util import find_msvcrt
def main(*argv):
data = b"test data"
size = len(data)
ptr = None
use_msvcrt = bool(argv) # If an argument (any value) is given
if use_msvcrt:
print(f"UCRT (found by CTypes): {find_msvcrt()}")
size += 1 # + 1 for the NUL terminating char (whoever uses the buffer might rely on it)
procexp = 0 # Enable this to inspect the (current) Python process (loaded .dlls) via Process Explorer tool
if procexp:
input("Press <Enter> to load UCRT...")
libc = cts.cdll.msvcrt
if procexp:
input("UCRT loaded, press <Enter> move forward...")
malloc = libc.malloc
malloc.argtypes = (cts.c_size_t,)
malloc.restype = cts.POINTER(cts.c_char) # Rigorously should be cts.c_void_p, but choosing this form for convenience
free = libc.free
free.argtypes = (cts.c_void_p,)
free.restype = None
ptr = libc.malloc(size)
else:
ptr = cts.create_string_buffer(size)
if ptr is None:
print("Uninitialized pointer")
return -1
print(f"Ptr: {ptr} ({type(ptr)})")
cts.memmove(ptr, data, size)
if use_msvcrt:
if (not ptr):
print("NULL pointer")
return -2
print(f"Ptr data: {b''.join(ptr[i] for i in range(size - 1))}")
free(ptr)
else:
print(f"Ptr data: {bytes(ptr)}")
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q078205030]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Ptr: <ctypes.c_char_Array_9 object at 0x00000239067AB6C0> (<class 'ctypes.c_char_Array_9'>) Ptr data: b'test data' Done. [prompt]> [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py dummyarg Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 UCRT (found by CTypes): None Ptr: <ctypes.LP_c_char object at 0x000002143B88B6C0> (<class 'ctypes.LP_c_char'>) Ptr data: b'test data' Done.
注意事项:
这通常 没有处理#1.。在我的场景中有效,因为malloc和free是从同一个UCRT实例(cts.cdll.msvcrt)加载的。而且,前面提到的UCRT实例与主实例(由进程自动加载)是相同的,但这只是巧合,不能安全依赖。
我没有使用WebUI,不知道是否可以将释放内存的回调传递给它(这样就可以安全地传递当前的free函数),或者甚至通知它不要释放指针,而是使用(推荐的)cts.create_string_buffer方法。可能还值得查看: