CTypes: Pointer für eine bestimmte Struktur erstellen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Steffo
User
Beiträge: 45
Registriert: Sonntag 24. Mai 2009, 19:38

Hallo,
eine C-Funktion, die ich aufrufen möchte, erwartet einen Pointer auf eine bestimmte Struktur:

Code: Alles auswählen

libusb_device **list;
ssize_t cnt = libusb_get_device_list(NULL, &list);
Wie setze ich das in Python um?
Muss ich einen Pointer auf die Struktur übergeben?
Wie mache ich einen Pointer auf einen Pointer?

Danke im Voraus!

L. G.
Steffo
BlackJack

@Steffo: In welchem Kontext? Man muss bei C ja statische Typen unterscheiden, also einen Typ „Pointer auf Pointer auf irgendeinen C-Datentyp”, und den Programmablauf wo man einen Pointer auf einen Wert von einem Typ „Pointer auf irgendeinen C-Datentyp” erzeugen kann. Wobei in beiden Fällen „irgendein C-Datentyp” natürlich auch ein Pointer auf irgendetwas sein darf. Pointer*typen* erzeugt man mit `ctypes.POINTER` und Pointer-Werte mit `ctypes.pointer()` und `ctypes.byref()`. Letzteres ist für Argumente in Funktionsaufrufen gedacht und etwas effizienter als dort `pointer()` zu verwenden.

Die `libusb_device` Struktur ist laut Dokumentation opaque, dass heisst der Benutzer der Bibliothek verwendet ausschliesslich Zeiger auf diese Struktur ohne deren Aufbau und Inhalt zu kennen. Also kann man Zeiger auf diesen Typ in C als `void *` beziehungsweise in Python als `ctypes.c_void_p` handhaben (den `ctypes`-Präfix spare ich mir ab jetzt): ``DeviceType = c_void_p``. Einen Pointer-Typ darauf erstellt man mit `POINTER`. Und um dann tatsächlich einen Wert von diesem Typ zu erzeugen, muss man den Typ aufrufen: ``device_list = POINTER(DeviceType)()``. Einen Pointer auf diesen Wert kann man dann mit `byref()` an eine mit `ctypes` gewrappte C-Funktion übergeben.

Wenn man `libusb_get_device_list()` in Python wrappen möchte, so dass man eine Liste mit `Device`-Objekten zurück bekommt, muss man sich allerdings noch mit der Speicherverwaltung herum schlagen um keine Speicherlecks zu haben oder Abstürze weil man versucht auf Daten zuzugreifen, die gar nicht mehr existieren.

Das Kontextargument der Funktion würde ich auch nutzen, denn man bekommt den Kontext ja von `usblib_init()` frei Haus geliefert und es würde Sinn machen da eine Klasse drumherum zu stricken, die ein Contextmanager ist, damit `libusb_exit()` garantiert aufgerufen wird. Das `Context`-Objekt bekommt dann den Wrapper für die `libusb_get_device_list()`-Funktion als Methode. Dann kann man das so benutzen:

Code: Alles auswählen

from my_usblib import Context

# ...

def main():
    with Context() as context:
        context.debug = 3
        devices = context.get_device_list()
        print devices
Oder als komplettes minimales (*hust*) Beispiel:

Code: Alles auswählen

#!/usr/bin/env python
from ctypes import byref, c_int, c_size_t, c_void_p, CDLL, POINTER
from ctypes.util import find_library

USB_LIB = CDLL(find_library('usb-1.0'))

DeviceType = c_void_p
ContextType = c_void_p

_init = USB_LIB.libusb_init
_init.argtypes = [POINTER(ContextType)]
_init.restype = c_int

_exit = USB_LIB.libusb_exit
_exit.argtypes = [ContextType]
_exit.restype = None

_set_debug = USB_LIB.libusb_set_debug
_set_debug.argtypes = [ContextType, c_int]
_set_debug.restype = None

_get_device_list = USB_LIB.libusb_get_device_list
_get_device_list.argtypes = [ContextType, DeviceType]
_get_device_list.restype = c_size_t

_free_device_list = USB_LIB.libusb_free_device_list
_free_device_list.argtypes = [POINTER(DeviceType), c_int]
_free_device_list.restype = None

_ref_device = USB_LIB.libusb_ref_device
_ref_device.argtypes = [DeviceType]
_ref_device.restype = DeviceType

_unref_device = USB_LIB.libusb_unref_device
_unref_device.argtypes = [DeviceType]
_unref_device.restype = None


class USBError(Exception):
    """USB error exception.
    
    :TODO: `from_error_number()` method.
    :TODO: Meaningful message texts.
    :TODO: Exception hierarchy to be able to distinguish in ``except`` clauses.
    """


class Device(object):
    def __init__(self, handle):
        self._as_parameter_ = _ref_device(handle)

    _unref_device = _unref_device
    
    def __del__(self):
        _unref_device(self)


class Context(object):
    def __init__(self):
        handle = ContextType()
        error_code = _init(byref(handle))
        if error_code != 0:
            raise USBError(error_code)
        self._as_parameter_ = handle
    
    def __enter__(self):
        return self
    
    def __exit__(self, *_args):
        self.exit()
    
    def _set_debug(self, level):
        _set_debug(self, level)
    
    debug = property(fset=_set_debug)
    
    def exit(self):
        _exit(self)

    def get_device_list(self):
        device_list = POINTER(DeviceType)()
        count = _get_device_list(self, byref(device_list))
        if count < 0:
            raise USBError(count)
        result = [Device(device_list[i]) for i in xrange(count)]
        _free_device_list(device_list, True)
        return result


def main():
    with Context() as context:
        context.debug = 3
        devices = context.get_device_list()
        print devices


if __name__ == '__main__':
    main()
Bei `Device` sieht man eine der wenigen Situationen, wo man eine `__del__()`-Methode sinnvoll einsetzen kann: Für die Freigabe von Ressourcen von denen die Python-Laufzeitumgebung nichts weiss. Man muss dann allerdings darauf achten, dass man mit diesen Objekten keine zyklischen Referenzen erzeugt.
Steffo
User
Beiträge: 45
Registriert: Sonntag 24. Mai 2009, 19:38

WAHNSINN!!! :shock:
THX, BlackJack!!! :D
Steffo
User
Beiträge: 45
Registriert: Sonntag 24. Mai 2009, 19:38

Hallo BlackJack,
du scheinst dich ja mit libusb auszukennen.
Irgendwie kriege ich es nicht hin, auf den ConfigurationDescriptor zuzugreifen.

Code: Alles auswählen

class ConfigDescriptor(Structure):
    _fields_ = [("bLength", c_uint8),
                ("bDescriptorType", c_uint8),
                ("wTotalLength", c_uint16),
                ("bNumInterfaces", c_uint8),
                ("bConfigurationValue", c_uint8),
                ("iConfiguration", c_uint8),
                ("bmAttributes", c_uint8),
                ("bMaxPower", c_uint8),
                ("interface", POINTER(InterfaceDescriptor)),
                ("extra", POINTER(c_ubyte)),
                ("extra_length", c_int)]

    def __init__(self, dev):
        config_p = POINTER(ConfigDescriptor)(self)
        error_code = _get_active_config_descriptor(dev, byref(config_p))
        
        if error_code != 0:
            raise USBError(error_code) # Hier wird die Exception ausgelöst: __main__.USBError: LIBUSB_ERROR_NOT_FOUND

    def __exit__(self):
        _free_config_descriptor(byref(self))

_get_active_config_descriptor = USB_LIB.libusb_get_active_config_descriptor
_get_active_config_descriptor.argTypes = [DeviceType, POINTER(POINTER(ConfigDescriptor))]
_get_active_config_descriptor.restype = c_int

def main():
    with Context() as context:
        context.debug = 3
        devices = context.get_device_list()
        for dev in devices:
            print "busNr:", dev.get_bus_number(), "portNr:", dev.get_port_number(), "dev address:", dev.get_device_address()
            desc = DeviceDescriptor(dev)
            print "VID:", hex(desc.idVendor), "PID:", hex(desc.idProduct), "number configs:", desc.bNumConfigurations
            config = ConfigDescriptor(dev)
            print "bLength:", config.bLength
Das Problem ist, dass ich nicht weiß, wie ich überhaupt eine gültige DeviceConfig setze, was Voraussetzung ist, um _get_active_config_descriptor() aufzurufen. Ich brauche gültige Indizes oder Values. Aber wie finde ich die heraus?

Danke im Voraus!

L. G.
Steffo
Antworten