Python Language
ctypes
Szukaj…
Wprowadzenie
ctypes
to wbudowana biblioteka Pythona, która wywołuje eksportowane funkcje z natywnych bibliotek skompilowanych.
Uwaga: Ponieważ ta biblioteka obsługuje skompilowany kod, jest względnie zależna od systemu operacyjnego.
Podstawowe użycie
Powiedzmy, że chcemy użyć funkcji ntohl
libc
.
Najpierw musimy załadować libc.so
:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Następnie otrzymujemy obiekt funkcji:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
A teraz możemy po prostu wywołać funkcję:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Co robi dokładnie to, czego się spodziewamy.
Częste pułapki
Nie można załadować pliku
Pierwszym możliwym błędem jest nie załadowanie biblioteki. W takim przypadku zwykle pojawia się błąd OSError.
Jest to spowodowane tym, że plik nie istnieje (lub nie może go znaleźć system operacyjny):
>>> cdll.LoadLibrary("foobar.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: foobar.so: cannot open shared object file: No such file or directory
Jak widać, błąd jest wyraźny i dość indykatywny.
Drugim powodem jest to, że plik został znaleziony, ale nie ma poprawnego formatu.
>>> cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/i386-linux-gnu/libc.so: invalid ELF header
W takim przypadku plik jest plikiem skryptu, a nie plikiem .so
. Może się to również zdarzyć podczas próby otwarcia pliku .dll
na komputerze z systemem Linux lub pliku 64-bitowego na 32-bitowym interprecie Pythona. Jak widać, w tym przypadku błąd jest nieco bardziej niejasny i wymaga trochę pogrzebania.
Brak dostępu do funkcji
Zakładając, że pomyślnie załadowaliśmy plik .so
, musimy uzyskać dostęp do naszej funkcji, tak jak zrobiliśmy to w pierwszym przykładzie.
Gdy używana jest nieistniejąca funkcja, zgłaszany jest błąd AttributeError
:
>>> libc.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/i386-linux-gnu/libc.so.6: undefined symbol: foo
Podstawowy obiekt ctypes
Najbardziej podstawowym obiektem jest int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Teraz obj
odnosi się do fragmentu pamięci zawierającego wartość 12.
Dostęp do tej wartości można uzyskać bezpośrednio, a nawet zmodyfikować:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Ponieważ obj
odnosi się do fragmentu pamięci, możemy również dowiedzieć się, jaki jest jego rozmiar i położenie:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
tablice ctypes
Jak każdy dobry programista C wie, jedna wartość nie zaprowadzi cię tak daleko. To, co nas naprawdę uruchomi, to tablice!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
To nie jest rzeczywista tablica, ale jest cholernie blisko! Stworzyliśmy klasę, która oznacza tablicę 16 int
.
Teraz wszystko, co musimy zrobić, to zainicjować:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Teraz arr
jest rzeczywistą tablicą zawierającą liczby od 0 do 15.
Można uzyskać do nich dostęp jak z dowolnej listy:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
I podobnie jak każdy inny obiekt typu ctypes
, ma również rozmiar i lokalizację:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Funkcje owijania dla typów
W niektórych przypadkach funkcja C akceptuje wskaźnik funkcji. Jako ctypes
użytkownicy ctypes
chcielibyśmy używać tych funkcji, a nawet przekazywać funkcję python jako argumenty.
Zdefiniujmy funkcję:
>>> def max(x, y):
return x if x >= y else y
Teraz ta funkcja pobiera dwa argumenty i zwraca wynik tego samego typu. Dla przykładu załóżmy, że typ jest liczbą całkowitą.
Podobnie jak w przykładzie z tablicą, możemy zdefiniować obiekt oznaczający ten prototyp:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Ten prototyp oznacza funkcję, która zwraca c_int
(pierwszy argument) i akceptuje dwa argumenty c_int
(pozostałe argumenty).
Teraz zawińmy funkcję:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Prototypy funkcji mają na więcej użycia: Można je owinąć ctypes
funkcji (jak libc.ntohl
) i sprawdzić, czy poprawne argumenty są używane podczas wywoływania funkcji.
>>> libc.ntohl() # garbage in - garbage out
>>> CFUNCTYPE(c_int, c_int)(libc.ntohl)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: this function takes at least 1 argument (0 given)
Złożone użycie
Połączmy wszystkie powyższe przykłady w jeden złożony scenariusz: używając funkcji lfind
libc
.
Aby uzyskać więcej informacji na temat funkcji, przeczytaj stronę podręcznika man . Zachęcam do przeczytania go przed kontynuowaniem.
Najpierw zdefiniujemy odpowiednie prototypy:
>>> compar_proto = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>> lfind_proto = CFUNCTYPE(c_void_p, c_void_p, c_void_p, POINTER(c_uint), c_uint, compar_proto)
Następnie utwórzmy zmienne:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
A teraz definiujemy funkcję porównania:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Zauważmy, że x
i y
są POINTER(c_int)
, więc musimy je wziąć dereference i ich wartości w celu rzeczywiście porównać wartość przechowywaną w pamięci.
Teraz możemy połączyć wszystko razem:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
jest zwróconym pustym wskaźnikiem. Jeśli key
nie został znaleziony w arr
, wartością byłby None
, ale w tym przypadku otrzymaliśmy prawidłową wartość.
Teraz możemy go przekonwertować i uzyskać dostęp do wartości:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
Widzimy również, że ptr
wskazuje prawidłową wartość wewnątrz arr
:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True