Используете в Python ctypes для передачи / чтения параметра, объявленного как "имя_структуры *** имя_параметра"?
Я пытаюсь использовать библиотеку Python ctypes для доступа к некоторым методам в библиотеке сканирования SANE. Это мой первый опыт работы с 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) кажется, что это будет работать, только если есть один результат. Я не могу лен () на 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 ответ
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
Теперь, как вы бы назвали это из кода C Я просмотрел документацию по 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
[Edit] Я протестировал приведенный выше код, используя очень простой заполнитель для SANE_get_devices
и это работает.