Skip to main content
 首页 » 编程设计

python之使用 Python 的 ctypes 传递/读取声明为 "struct_name *** param_name"的参数

2025年05月04日14wayfarer

我正在尝试使用 Python 的 ctypes 库访问扫描库中的一些方法 SANE .这是我第一次使用 ctypes,也是我一年多来第一次不得不处理 C 数据类型,所以这里有一个公平的学习曲线,但我认为即使没有这个特定的声明也会很麻烦:

extern SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only); 

首先,我已经成功处理了 SANE_Status(枚举)和 SANE_Bool(c_int 的类型定义)。那些都很简单。另一方面,第一个参数让我很伤心。我一开始就不熟悉“***”符号,到目前为止,我的示踪子弹只产生了垃圾数据。如何格式化此函数的输入,以便我可以读回我的 Python 结构对象列表?作为引用,被引用的 C 结构是:

typedef struct 
{ 
    SANE_String_Const name; /* unique device name */ 
    SANE_String_Const vendor;   /* device vendor string */ 
    SANE_String_Const model;    /* device model name */ 
    SANE_String_Const type; /* device type (e.g., "flatbed scanner") */ 
 } 
SANE_Device; 

其中 SANE_String_Const 定义为 c_char_p。

我的这个对象的 Python/ctypes 版本是:

class SANE_Device(Structure): 
    _fields_ = [ 
        ("name", c_char_p), 
        ("vendor", c_char_p), 
        ("model", c_char_p), 
        ("type", c_char_p)] 

关于我应该传递什么以便从中获得预期行为(结构对象列表)的建议?感谢所有回复。

更新 1:

使用以下内容,我能够检索到正确的 SANE_Device Python 结构:

devices = pointer(pointer(pointer(SANE_Device()))) 
status = libsane.sane_get_devices(devices, c_int(0)) 
print status, devices, devices.contents.contents.contents.name 

但是,1) 讨厌和 2) 这似乎只有在只有一个结果时才有效。我不能在 devices.contents.contents 或 devices.contents.contents.contents 上使用 len()。我如何确定结果的数量? SANE 文档指定“如果函数成功执行,它会存储一个指针,该指针指向指向 SANE_Device 结构的 NULL 终止指针数组,位于 *device_list 中”。有什么建议吗?

更新 2:

我能够传递一个包含十项的数组,然后使用以下方法访问第一个元素:

devices = pointer(pointer(pointer((SANE_Device * 10)()))) 
status = libsane.sane_get_devices(devices, c_int(0)) 
print status, devices, devices.contents.contents.contents[0].name 

但是,十显然是一个任意数字,我无法确定实际结果的数量。在仅连接一个设备时尝试访问 devices.contents.contents.contents[1].name 会导致段错误。必须有一种适当的方法来处理 ctypes 中的可变长度结构。

请您参考如下方法:

const SANE_Device *** 是一个三级指针:它是一个指向常量 SANE_Device 的指针的指针。您可以使用程序 cdecl破译复杂的 C/C++ 类型定义。

根据SANE documentation ,如果成功,SANE_get_devices() 将存储一个指向以 NULL 结尾的指向 SANE 设备的指针列表的指针。因此,调用它的正确方法是声明一个类型为 const SANE_Device ** 的变量(即指向常量 `SANE_Device 的指针),并传入该指针的地址:

const SANE_Device **device_list; 
SANE_get_devices(&device_list, local_only);  // check return value 
// Now, device_list[0] points to the first device, 
// device_list[1] points to the second device, etc. 
// Once you hit a NULL pointer, that's the end of the list: 
int num_devices = 0; 
while(device_list[num_devices] != NULL) 
    num_devices++; 
// num_devices now stores the total number of devices 

现在,这就是您从 C 代码中调用它的方式。我浏览了关于 ctypes 的文档,看来您想使用 byref 函数通过引用传递参数,并且您传递的值应该是指向 SANE_Device 的指针.请注意 pointer 和 POINTER 之间的区别:前者创建一个指向 instance 的指针,而后者创建一个指向 type 的指针。因此,我猜测以下代码会起作用:

// SANE_Device declared as you had it 
devices = POINTER(POINTER(SANE_Device))()  // devices is a NULL pointer to a pointer to a SANE_Device 
status = libsane.sane_get_devices(byref(devices), c_int(0)) 
if status != successful:   // replace this by whatever success is 
    print error 
else: 
    num_devices = 0 
    // Convert NULL-terminated C list into Python list 
    device_list = [] 
    while devices[num_devices]: 
        device_list.append(devices[num_devices].contents)  // use .contents here since each entry in the C list is itself a pointer 
        num_devices += 1 
    print device_list 

[编辑] 我已经使用一个非常简单的 SANE_get_devices 占位符测试了上面的代码,它有效。