Utilisation des ctypes de Python pour passer / lecture d'un paramètre déclaré comme « struct_name *** param_name »?

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

Question

Je suis en train d'utiliser la bibliothèque de ctypes de Python pour accéder à certaines méthodes dans la bibliothèque de balayage SANE. Ceci est ma première expérience avec ctypes et la première fois que je l'ai eu à traiter C types de données dans plus d'un an donc il y a une courbe d'apprentissage juste ici, mais je pense que même sans que cette déclaration particulière serait gênant:

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

Tout d'abord, je l'ai traité avec succès SANE_Status (un ENUM) et SANE_Bool (un typedef à c_int). C'était à la fois simple. Ce premier paramètre, d'autre part, me cause toutes sortes de douleur. Je ne suis pas familier avec la notation « *** » pour commencer et mes balles traçantes jusqu'à présent ont abouti à rien de plus que les données de déchets. Comment formater l'entrée à cette fonction telle que je peux relire une liste de mes objets de structure-Python? Pour référence, la structure C référencée est:

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 est défini comme un c_char_p.

Mon Python / ctypes version de cet objet est:

class SANE_Device(Structure):
    _fields_ = [
        ("name", c_char_p),
        ("vendor", c_char_p),
        ("model", c_char_p),
        ("type", c_char_p)]

Les suggestions sur ce que je dois passer dans ce que je peux obtenir le comportement attendu (une liste des structures-objets) de ce? Toutes les réponses ont apprécié.

Mise à jour 1:

En utilisant ce qui suit, j'ai pu récupérer une structure correcte SANE_Device Python:

devices = pointer(pointer(pointer(SANE_Device())))
status = libsane.sane_get_devices(devices, c_int(0))
print status, devices, devices.contents.contents.contents.name

Cependant, 1) beurk et 2) qui semble comme il ne fonctionnerait que s'il y a un seul résultat. Je ne peux pas len () sur devices.contents.contents ou devices.contents.contents.contents. Comment puis-je déterminer le nombre de résultats? Les SANE docs précisent que « Si la fonction exécute avec succès, il stocke un pointeur vers un tableau à terminaison NULL de pointeurs vers des structures SANE_Device en * device_list ». Suggestions?

Mise à jour 2:

I été en mesure de transmettre un tableau dix-élément, puis accéder au premier élément en utilisant:

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

Cependant, dix est évidemment un nombre arbitraire et je n'ai aucun moyen de déterminer le nombre réel de résultats. Essayer d'accéder à devices.contents.contents.contents[1].name lorsqu'un seul dispositif est relié provoque une erreur de segmentation. Il doit y avoir une bonne façon de traiter avec des constructions de longueur variable comme celles-ci dans ctypes.

Était-ce utile?

La solution

A const SANE_Device *** est un pointeur à trois niveaux: il est un pointeur vers un pointeur vers un pointeur vers une SANE_Device constante. Vous pouvez utiliser le programme cdecl pour déchiffrer le type complexe C / C ++ définitions.

Selon le documentation SANE , SANE_get_devices() enregistre un pointeur vers un NULL liste de pointeurs vers des mots terminée Sane appareils en cas de succès. Ainsi, la meilleure façon de l'appeler consiste à déclarer une variable de type const SANE_Device ** (à savoir un pointeur vers un pointeur sur une `SANE_Device constante), et de passer à l'adresse de ce pointeur:

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

Maintenant, voici comment vous l'appeler à partir du code C. J'ai effleuré la documentation sur ctypes, et il semble que vous voulez utiliser la fonction byref pour passer l'argument par référence, et que la valeur que vous passez doit être un pointeur à un pointeur vers un SANE_Device. Notez la distinction entre pointer et POINTER: le premier crée un pointeur sur un par exemple , alors que celui-ci crée un pointeur vers une type . Ainsi, je devine le code suivant fonctionnera:

// 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

[Modifier] Je l'ai testé le code ci-dessus en utilisant un espace réservé très simple pour SANE_get_devices, et il fonctionne.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top