1

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

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

首先,我已经成功处理了SANE_Status(an enum) 和SANE_Bool(a typedef to 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) 似乎只有在只有一个结果时才会起作用。我不能 len() on devices.contents.contentsor devices.contents.contents.contents。我如何确定结果的数量?SANE 文档指定“如果函数成功执行,它会存储一个指向 *device_list 中指向 SANE_Device 结构的指针的 N​​ULL 终止数组的指针”。建议?

更新 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 中这样的可变长度构造。

4

1 回答 1

7

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

根据SANE 文档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注意和之间的区别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

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

于 2008-12-20T07:49:30.810 回答