如何在Python中使用C的malloc分配的内存(ctypes)

1 投票
4 回答
84 浏览
提问于 2025-04-13 02:12

我猜下面的代码应该能正常工作,但实际上并没有。有人能告诉我怎么使用这种指针吗?当然,这只是一个示例代码。我需要解决的问题是使用一个来自外部库的指针(这个库有自己的内存分配器'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 个回答

0

在你当前的例子中,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_pctypes.c_wchar_p 作为指针类型。这些是特殊的 ctypes 类型,会将值转换为Python的 bytesstr 对象,原始的指针地址会丢失。应该使用 ctypes.c_void_p(根据写入的数据,你可能需要进行类型转换)或者 ctypes.POINTER 类型,这样指针就可以通过 ctypes.memmove 或其他写入指针的方法使用。

0

谢谢大家!缺少的只是通过restype方法设置malloc或webui_malloc的返回类型:

libc.malloc.restype = ct.c_void_p
0

这里有两个可能导致未定义行为的地方:

  1. 根据[Python.Docs]: ctypes - 加载动态链接库强调是我自己的):

    注意:通过cdll.msvcrt访问标准C库将使用一个过时的库版本,这可能与Python使用的版本不兼容。如果可能,使用原生的Python功能,或者导入并使用msvcrt模块。

    还提到[Python.Docs]: msvcrt - 来自MS VC++运行时的有用例程

  2. [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.

注意事项

撰写回答