2017-05-05 53 views
9

Łączę się z biblioteką zewnętrzną za pomocą ctypes. Ta biblioteka zwraca mi binarny bufor. Interfejs wygląda następująco:Jaki jest najskuteczniejszy sposób kopiowania zewnętrznego dostarczonego bufora do bajtów

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

Biblioteka eksportuje również dealokator tak, że mogę zwolnić bufor, gdy jestem z nim zrobić, ale ten aspekt nie przedstawia żadnych problemów do mnie, więc nie sądzę, musimy na pokrycie tego.

W moim kodzie cctses reprezentuję argument buf jako c_void_p. Chciałbym skopiować ten bufor do obiektu bajtów tak wydajnie, jak to tylko możliwe.

W tej chwili mam:

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

gdzie buf jest c_void_p i len jest c_int.

To, jak rozumiem, wykonuje dwie kopie. Raz do obiektu bytearray, a następnie ponownie do obiektu bajtów.

Jak mogę to zrobić za pomocą tylko jednej kopii?

Moje obecne wysiłki koncentrują się na Pythonie 2, ale w odpowiednim czasie będę musiał to również wspierać w Pythonie 3.

+3

Na Pythonie 3, powinieneś być w stanie usunąć wywołanie 'bytearray'. – user2357112

+1

Dlaczego używasz 'c_void_p' z rzutowaniem zamiast po prostu' buf = POINTER (c_char) '? Następnie 'getBuff (byref (buf), byref (len))' i 'dane = buf [: len.value]'. – eryksun

+0

@eryksun: Huh. Możesz wyciąć wskaźnik ctypes? Wiadomości do mnie. – user2357112

Odpowiedz

5

Podobno można wyciąć wskaźnik ctypes. Nie działają typy c_void_p, c_char_p lub c_wchar_p, ale POINTER. Dla POINTER(c_char), krojenie go daje bytes:

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

Dzięki eryksun do wniesienia że w górę. Ponadto, nie jest jasne, dlaczego buf jest c_void_p zamiast już być POINTER(c_char). (Dla POINTER(c_char) kod byłby tylko buf[:len.value].)


Dla uzyskania bytes z ogólnym przedmiocie obsługującym protokół buforowego memoryview(...).tobytes() obejmuje mniejszą ilość kopii niż bytes(bytearray(...)):

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

to jest Kompatybilny z Python 2 i Python 3.


Należy pamiętać, że buf tutaj musi być wskaźnikiem do bufora, a nie wskaźnikiem do wskaźnika do bufora. getBuff przyjmuje wskaźnik do wskaźnika (więc prawdopodobnie byref(buf)).

+0

Dzięki. Tak, podwójny wskaźnik jest taki, że biblioteka może zwrócić wskaźnik do osoby dzwoniącej. Ale w moim kodzie cpy kod jest wskaźnikiem do bufora. –

+0

@eryksun: Przyjrzałem się i wow, 'cast' faktycznie jest wywołaniem FFI zamiast zwykłej wbudowanej lub funkcji Pythona, i to jest rzeczywiście zadziwiająco powolne. Przez mikrosekundę na wywołanie w środowisku testowałem to tylko dla samego połączenia "cast", w porównaniu do około 67 nanosekund dla krojenia wskaźnika i około 285 nanosekund dla 'memoryview (x) .tobytes()' (z 10- tablica testowa elementów). – user2357112

+0

@eryksun: Czy miałeś na myśli 'ctypes.POINTER (ctypes.c_char)', czy istnieje pewne dziwactwo API, które oznacza, że ​​powinniśmy faktycznie używać 'ctypes.POINTER (ctypes.c_char_p)'? – user2357112