Usando ctypes de Python para pasar / leer un parámetro declarado como “struct_name *** param_name”?
Pregunta
Estoy tratando de utilizar la biblioteca de Python ctypes acceder a algunos métodos en la biblioteca de exploración SANE . Esta es mi primera experiencia con ctypes y la primera vez que he tenido que tratar con tipos de datos C en más de un año por lo que hay una curva de aprendizaje justo aquí, pero creo que incluso sin que esta declaración particular sería problemático:
extern SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only);
En primer lugar, he tratado con éxito con SANE_Status
(una enumeración) y SANE_Bool
(un typedef a c_int
). Esos eran a la vez simple. Ese primer parámetro, por el contrario, es lo que hizo todo tipo de dolor. Estoy familiarizado con la notación "***
" para empezar, y mis balas trazadoras hasta ahora han dado nada más que datos de la basura. ¿Cómo formateo la entrada a esta función tal que pueda leer de nuevo una lista de mis Python estructura-objetos? Como referencia, la estructura C que se hace referencia es:
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;
Donde SANE_String_Const
se define como un c_char_p
.
Mi Python / ctypes versión de este objeto es:
class SANE_Device(Structure):
_fields_ = [
("name", c_char_p),
("vendor", c_char_p),
("model", c_char_p),
("type", c_char_p)]
Las sugerencias sobre lo que debería pasar en tal forma que puedo conseguir el comportamiento esperado (una lista de estructura-objetos) de todo esto? Todas las respuestas apreciadas.
Actualización 1:
El uso de los siguientes, yo era capaz de recuperar una estructura SANE_Device Python correcta:
devices = pointer(pointer(pointer(SANE_Device())))
status = libsane.sane_get_devices(devices, c_int(0))
print status, devices, devices.contents.contents.contents.name
Sin embargo, 1) asco y 2) que parece que sólo funcionaría si hay un solo resultado. No puedo len () en devices.contents.contents
o devices.contents.contents.contents
. ¿Cómo voy a determinar el número de resultados? Los documentos especifican que SANE "Si la función se ejecuta con éxito, almacena un puntero a una matriz terminada en nulo de punteros a estructuras SANE_Device en device_list *". Sugerencias?
Actualización 2:
I era capaz de pasar una matriz de diez artículo y después acceder al primer elemento usando:
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
Sin embargo, diez es, obviamente, un número arbitrario y no tengo ninguna manera de determinar el número real de resultados. Intentar acceder a devices.contents.contents.contents[1].name
cuando sólo un dispositivo está conectado provoca una violación de segmento. Tiene que haber una manera apropiada de tratar con construcciones de longitud variable como estos en ctypes.
Solución
A const SANE_Device ***
es un puntero de tres niveles: es un puntero a un puntero a un puntero a una SANE_Device constante. Puede utilizar el programa cdecl descifrar complicados de tipo C / C ++ definiciones.
De acuerdo con la documentación SANE , SANE_get_devices()
almacenará un puntero a NULL lista terminada en su sano juicio de punteros a los dispositivos en caso de éxito. Por lo tanto, la forma correcta de llamarlo es declarar una variable de tipo const SANE_Device **
(es decir, un puntero a un puntero a una constante `SANE_Device), y pasar en la dirección de ese puntero:
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
Ahora bien, esta es la forma en que podríamos llamar desde el código C. He rozado la documentación sobre ctypes, y parece que desea utilizar la función byref
para pasar el argumento por referencia, y que el valor que pasa debería ser un puntero a un puntero a un SANE_Device. Tenga en cuenta la distinción entre pointer
y POINTER
: el primero crea un puntero a un ejemplo , mientras que el segundo crea un puntero a un type . Por lo tanto, supongo que el siguiente código funcionará:
// 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
[Editar] He probado el código anterior utilizando un marcador de posición muy simple para SANE_get_devices
, y funciona.