如何使用ctypes.create_string_buffer将其作为char *参数传递给C函数?

3

我有一个带有函数的C/C++ dll:

int get_camera_info(char * description, char * serial_number, char * manufacturer)

此函数接受参数并修改它们,以便您可以打印它们。我正在尝试使用ctypes从Python中使用此DLL。我猜我需要使用ctypes.create_string_buffer创建一个空缓冲区,以便我有一个可变对象。但是,我该如何做到将其作为参数传递,然后检索数据呢?我需要使用cast(buffer_object, c_char_p_object)吗?这与一开始创建和传递c_char_p对象有什么不同吗?我不太了解C/C++,但也许我应该使用ctypes.POINTER
我的问题是,在将c_char_p类型对象作为参数传递后,我会收到错误:OSError: exception: access violation reading 0x00000000
如果您需要帮助理解我的问题,请随时询问。或者,如果您认为我最好使用像cppyy这样的东西,那么我也需要帮助。

该函数接收参数并对其进行修改,但不会修改指针。这是不可能的。被修改的是指针所指向的内容,而指针的实际值仍保持不变。 - PaulMcKenzie
我想我需要使用ctypes.create_string_buffer -- 缓冲区大小是否正确? DLL信任您有足够的空间来复制到缓冲区中的任何字符串数据。 - PaulMcKenzie
就像我说的,我对C/C++几乎一无所知。但是我知道(在C++代码中),如果我传递该参数,然后尝试打印它,它会给我所需的相机信息。我相信你是正确的,它不会修改指针(只是它所指向的内容),但你明白我想做什么吗? - Eddie Random
我这样定义它的大小:create_string_buffer(256),因为在原始的C++代码中,它会有这样一行代码:char description[256] = "\0"; - Eddie Random
那么问题就是一个Python问题,而我不是Python专家。尝试像这样做:string_buffer = create_string_buffer(256),创建3个这样的缓冲区,并将它们作为参数传递给函数。 - PaulMcKenzie
1个回答

4

以下是基本信息。虽然在这种情况下不是必需的,但正确设置.argtypes.restype有助于ctypes正确处理参数并检测错误传递的参数。

与传递数组到C函数类似,create_string_buffer返回一个c_char_Array_array_size对象,但它被编组为指向其第一个元素(c_char_p)的指针,以便与.argtypes分配相匹配。

如果没有.argtypes分配,传递不正确的东西(例如intcreate_unicode_buffer()数组)将崩溃或产生错误结果。但正确定义它会捕捉这些错误并引发异常。

test.c

#include <string.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API int get_camera_info(char * description, char * serial_number, char * manufacturer)
{
    strcpy(description, "Description");
    strcpy(serial_number, "12345678");
    strcpy(manufacturer, "Manufacturer");
    return 1;
}

test.py

from ctypes import *

MAX_STR = 256  # docs better say how big the buffers are required to be.

# int get_camera_info(char * description, char * serial_number, char * manufacturer)

dll = CDLL('./test')
dll.get_camera_info.argtypes = c_char_p,c_char_p,c_char_p
dll.get_camera_info.restype = c_int

desc = create_string_buffer(MAX_STR)
sn = create_string_buffer(MAX_STR)
mfg = create_string_buffer(MAX_STR)

ret = dll.get_camera_info(desc,sn,mfg)
print(desc.value.decode(),sn.value.decode(),mfg.value.decode())

输出:

Description 12345678 Manufacturer

请注意,.value 返回缓冲区开头的字节字符串,直到第一个空字节为止(不包括空字节)。.raw 将输出一个包含缓冲区每个字节的字节字符串。.decode() 使用UTF-8作为默认编码将字节字符串转换为Unicode字符串。

谢谢,我认为我的代码与此完全相同,除了 .decode() 之外,当相机连接时它不会打印任何东西(也许在那时我使用的是 ctypes.c_char_p 而不是 create_string_buffer,所以它不会改变其值)。我现在无法访问相机,因此它会崩溃并显示 OSError: exception: access violation reading 0x00000000。我认为现在代码应该是正确的,所以当我能够连接相机时,我将验证并确认这是正确的答案。 - Eddie Random
还有一件事。如果参数是short *,那么argtype会是ctypes.POINTER(ctypes.c_short)吗?我传递的参数如何可变? - Eddie Random
1
如果它只是一个指向一个 short 的指针,你可以创建一个实例 x = ctypes.c_short(),然后将其作为 ctypes.byref(x) 传递。如果你需要一个数组,使用 (ctypes.c_short * array_size)()。将 ctypes 类型乘以大小会创建一个数组类型,并且 "调用" 它会创建一个数组实例。不要使用 byref 传递它,否则它就是一个 "short 数组的指针" 而不是 "short 的指针"。 - Mark Tolonen
而argtype会是ctypes.POINTER吗,就像我想的那样还是别的什么?我已经有了(ctypes.c_short * array_size)()来创建实例,然后再传递它。这个周一我连接相机时会验证。 - Eddie Random
1
@EddieRandom ctypes.POINTER(ctypes.c_short) - Mark Tolonen
谢谢!我真的卡住了。 - Eddie Random

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接