Используете в Python ctypes для передачи / чтения параметра, объявленного как "имя_структуры *** имя_параметра"? - PullRequest
1 голос
/ 20 декабря 2008

Я пытаюсь использовать библиотеку 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:

Используя следующее, я смог получить правильную структуру Python SANE_Device:

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 () на devices.contents.contents или devices.contents.contents.contents. Как мне определить количество результатов? Документы SANE указывают, что «Если функция выполняется успешно, она сохраняет указатель на массив указателей с окончанием NULL на структуры SANE_Device в * 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.

1 Ответ

7 голосов
/ 20 декабря 2008

A const 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

Так вот, как бы вы назвали это из кода Си. Я просмотрел документацию по ctypes, и кажется, что вы хотите использовать функцию byref для передачи аргумента по ссылке, и что передаваемое вами значение должно быть POINTER для POINTER для SANE_Device. Обратите внимание на различие между 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, и он работает.

...