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 yPOINTER(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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow