2017-05-09 5 views
1

Angenommen, ich die folgende Funktion in einer externen Bibliothek habe:Ist es möglich, sowohl Bytes als auch Bytearray-Objekte mit ctypes effizient an eine externe Bibliothek zu übergeben?

void foo(const unsigned char *buf, const int len); 

ich diese Funktion nutzen können anrufen möchte von meinem Python-Code, ctypes verwenden, ohne eine Kopie des Puffers zu machen. Der Puffer könnte ziemlich groß sein, so dass das Vermeiden einer Kopie offensichtliche Leistungsvorteile hat. Und für den Komfort des Verbrauchers meines Codes würde ich gerne in der Lage sein, diesen Puffer entweder als bytes oder bytearray zu liefern.

Im Moment erkläre ich buf als ctypes.POINTER(ctypes.c_char) in meiner argtypes Deklaration.

lib.foo.argtypes = [ctypes.POINTER(ctypes.c_char), ctypes.c_int] 
buf = bytes(...) 
lib.foo(buf, len(buf)) 

Dies funktioniert gut, und ich kann ein bytes Objekt übergeben. Wenn ich jedoch ein bytearray Objekt übergeben dann begegne ich den folgenden Fehler:

ctypes.ArgumentError: argument 1: : wrong type

Gibt es für mich einen Weg zu ermöglichen bytearray vorzugsweise mit bytes austauschbar übergeben, werden?

+0

@eryksun Danke für die Antwort. Ich habe keine wirklichen Sorgen darüber, wie dies erreicht wird. Ich möchte nur, dass der externe Code ein 'const unsigned char * 'erhält, ohne den Inhalt des Puffers zu kopieren. Wenn möglich. –

Antwort

1

Sie können eine Unterklasse des Zeigertyps erstellen, die from_param überschreibt, um eine bytearray anzupassen. Zum Beispiel:

class Pchar(ctypes.POINTER(ctypes.c_char)): 
    _type_ = ctypes.c_char 
    @classmethod 
    def from_param(cls, param, array_t=ctypes.c_char * 0): 
     if isinstance(param, bytearray): 
      param = array_t.from_buffer(param) 
     return super(Pchar, cls).from_param(param) 

lib.foo.argtypes = [Pchar, ctypes.c_int] 

Die c_char Array, das für die bytearray erstellt werden nur an dem internen Puffer des Objekts über Python-Puffer-Protokoll erhalten benötigt. Die Array-Größe spielt keine Rolle, daher können wir die Erstellung einer Array-Unterklasse für jede mögliche Länge von bytearray vermeiden. Verwenden Sie einfach einen Array-Typ Länge 0, der in der Argumentliste from_param zwischengespeichert wird.

+0

Danke nochmal Eryk, ich schulde dir wirklich ein Bier! –

+0

* in der from_param Argumentliste * zwischengespeichert. Das hat mich interessiert. Die in 'ctypes._CData' definierte Klassenmethode hat kein' array_t' Argument, oder? Es hat nur ein Argument, was Sie hier param nennen. Sie haben jedoch ein benanntes Argument 'array_t' hinzugefügt, das von keinem Aufrufer bereitgestellt wird. Sie tun das, damit der Standardwert bei Laden der Module ausgewertet wird, was eine möglicherweise zeitaufwändige Maßnahme darstellt. Auf diese Weise bezahlen Sie nur einmal diese Kosten. Ist das korrekt? Ich bin noch nie auf diese Technik gestoßen! –

+1

@DavidHeffernan, habe ich die Argumentliste verwendet, um die Suche etwas zu optimieren. Ich hätte ein Klassenattribut verwenden können, das etwas langsamer ist. Ich könnte mich auch einfach darauf verlassen, wie ctypes den Typ speichert, der mit 'ctypes.c_char * 0' erstellt wurde. Für letztere würde sie immer noch den Byte-Code ausführen, die 'sq_repeat'-Funktion aufrufen und schließlich' PyCArrayType_from_ctype' aufrufen, die den Cachetyp basierend auf dem Tupel-Schlüssel '(c_char, 0)' zurückgibt. Das mag sehr viel erscheinen, aber es ist relativ billig im Vergleich zu den Kosten, ein neues Typ-Objekt zu erstellen, weshalb ctypes Typ-Caches hat. – eryksun

Verwandte Themen