@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.