Usando ctypes de Python para pasar / leer un parámetro declarado como “struct_name *** param_name”?

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

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.

¿Fue útil?

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top