Использование ctypes Python для передачи / чтения параметра, объявленного как “struct_name *** param_name”?

StackOverflow https://stackoverflow.com/questions/383010

Вопрос

Я пытаюсь использовать библиотеку ctypes Python для доступа к некоторым методам в библиотеке сканирования ВМЕНЯЕМЫЙ.Это мой первый опыт работы с ctypes и первый раз, когда мне пришлось иметь дело с типами данных C более чем за год, так что здесь есть достаточная кривая обучения, но я думаю, что даже без этого это конкретное объявление было бы проблематичным:

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

Прежде всего, я успешно разобрался с SANE_Status (перечисление) и SANE_Bool (typedef для 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 указано, что "Если функция выполняется успешно, она сохраняет указатель на завершающийся нулем массив указателей на структуры 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.

Это было полезно?

Решение

A const SANE_Device *** является трехуровневым указателем:это указатель на указатель на указатель на константу SANE_Device.Вы можете воспользоваться программой cdecl для расшифровки сложных определений типов C / C ++.

В соответствии с РАЗУМНАЯ документация, SANE_get_devices() сохранит указатель на завершающийся нулем список указателей на НОРМАЛЬНЫЕ устройства в случае успеха.Таким образом, правильный способ вызвать его - объявить переменную типа 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:первый создает указатель на экземпляр, тогда как последний создает указатель на Тип.Таким образом, я предполагаю, что следующий код будет работать:

// 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, и это работает.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top