2017-05-05 3 views
9

Je m'interface avec une bibliothèque externe en utilisant des ctypes. Cette bibliothèque me renvoie un tampon binaire. L'interface ressemble à ceci:Quel est le moyen le plus efficace pour copier un tampon fourni en externe en octets?

int getBuff(unsigned char **buf, int *len); 

La bibliothèque exporte aussi deallocator pour que je puisse libérer le tampon quand je suis fait avec elle, mais cet aspect me pose pas de problème, donc je ne pense pas que nous avons besoin pour le couvrir. Dans mon code ctypes, je représente l'argument buf comme c_void_p. Je voudrais copier ce tampon dans un objet octets aussi efficacement que possible.

Au moment j'ai:

data = bytes(bytearray(ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte*len.value))[0])) 

buf est c_void_p et len est c_int. Si je comprends bien, cela fait deux copies. Une fois à l'objet bytearray, puis de nouveau à l'objet bytes.

Comment puis-je faire cela avec une seule copie? Mes efforts actuels se sont concentrés sur Python 2, mais en temps voulu, je devrai également prendre en charge Python 3 pour Python 3.

+3

Sur Python 3, vous devriez pouvoir supprimer l'appel 'bytearray'. – user2357112

+1

Pourquoi utilisez-vous 'c_void_p' avec un cast au lieu de simplement' buf = POINTER (c_char) '? Ensuite 'getBuff (parref (buf), byref (len))' et 'data = buf [: len.value]'. – eryksun

+0

@eryksun: Huh. Vous pouvez découper un pointeur ctypes? Nouvelles à moi. – user2357112

Répondre

5

Apparemment, vous pouvez découper un pointeur ctypes. Pas c_void_p, c_char_p, ou c_wchar_p, mais POINTER types fonctionnent. Pour une POINTER(c_char), tranchage il vous donne bytes:

data = ctypes.POINTER(ctypes.c_char).from_buffer(buf)[:len.value] 

Merci à eryksun avoir soulevé cette question. En outre, il n'est pas clair pourquoi buf est un c_void_p au lieu d'être déjà POINTER(c_char). (Pour une POINTER(c_char), le code serait juste buf[:len.value].)


Pour obtenir bytes à partir d'un objet général qui prend en charge le protocole de la mémoire tampon, memoryview(...).tobytes() implique une moins copie de bytes(bytearray(...)):

data = memoryview(ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte*len.value))[0]).tobytes() 

Ceci est compatible avec Python 2 et Python 3.


Gardez à l'esprit que le buf ici doit être un pointeur vers le tampon, pas un pointeur vers un pointeur vers le tampon. getBuff prend un pointeur vers un pointeur (donc probablement byref(buf)).

+0

Merci. Oui, le pointeur double est de sorte que la bibliothèque peut renvoyer le pointeur vers l'appelant. Mais dans mon code ctypes, buf est le pointeur vers le tampon. –

+0

@eryksun: J'ai jeté un oeil, et wow, 'cast' est en fait un appel FFI au lieu d'une fonction intégrée ou Python, et il est étonnamment lent. Plus d'une microseconde par appel sur l'environnement, je l'ai testé uniquement pour l'appel 'cast', comparé à environ 67 nanosecondes pour le découpage du pointeur et environ 285 nanosecondes pour' memoryview (x) .tobytes() '(avec un 10- tableau de test d'éléments). – user2357112

+0

@eryksun: Voulez-vous dire 'ctypes.POINTER (ctypes.c_char)', ou y a-t-il un peu d'étrangeté API qui signifie que nous devrions utiliser 'ctypes.POINTER (ctypes.c_char_p)'? – user2357112