我正在尝试使用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
的typedef)。那些都很简单。另一方面,第一个参数引起了各种各样的悲痛。我不熟悉开头的“***
”符号,到目前为止,我的追踪子弹只能产生垃圾数据。如何格式化此函数的输入,以便我可以读回我的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)yuck和2)似乎只有在有一个结果时它才会起作用。我不能devices.contents.contents
或devices.contents.contents.contents
上的len()。我如何确定结果的数量? SANE文档指定“如果函数成功执行,它将指向指向* device_list中的SANE_Device结构的NULL终止指针数组的指针”。建议
更新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
然而,10显然是一个任意数字,我无法确定实际的结果数。当仅连接一个设备时尝试访问devices.contents.contents.contents[1].name
会导致分段错误。在ctypes中必须有一种正确的方法来处理像这样的可变长度构造。
答案 0 :(得分:7)
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
和POINTER
之间的区别:前者创建指向实例的指针,而后者创建指向类型的指针。因此,我猜测以下代码将起作用:
// 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
的一个非常简单的占位符测试了上面的代码,它可以正常工作。