Python Language
ctypes
Suche…
Einführung
ctypes
ist eine in Python integrierte Bibliothek, die exportierte Funktionen aus nativen kompilierten Bibliotheken aufruft.
Hinweis: Da diese Bibliothek kompilierten Code verarbeitet, ist sie relativ vom Betriebssystem abhängig.
Grundlegende Verwendung
Nehmen wir an, wir wollen die ntohl
Funktion von libc
ntohl
.
Zuerst müssen wir libc.so
laden:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Dann erhalten wir das Funktionsobjekt:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
Und jetzt können wir einfach die Funktion aufrufen:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Was genau das tut, was wir erwarten.
Häufige Fehler
Laden einer Datei fehlgeschlagen
Der erste mögliche Fehler ist das Laden der Bibliothek. In diesem Fall wird normalerweise ein OSError ausgelöst.
Dies liegt entweder daran, dass die Datei nicht existiert (oder vom Betriebssystem nicht gefunden wird):
>>> 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
Wie Sie sehen können, ist der Fehler klar und bezeichnend.
Der zweite Grund ist, dass die Datei gefunden wurde, aber nicht das richtige Format hat.
>>> 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
In diesem Fall ist die Datei eine Skriptdatei und keine .so
Datei. Dies kann auch passieren, wenn Sie versuchen, eine .dll
Datei auf einem Linux-Computer oder eine 64-Bit-Datei auf einem 32-Bit-Python-Interpreter zu öffnen. Wie Sie sehen, ist der Fehler in diesem Fall etwas ungenauer und erfordert einige Eingrabungen.
Fehler beim Zugriff auf eine Funktion
Wenn wir die .so
Datei erfolgreich geladen .so
, müssen wir wie im ersten Beispiel auf unsere Funktion zugreifen.
Wenn eine nicht vorhandene Funktion verwendet wird, wird ein AttributeError
ausgelöst:
>>> 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
Basisobjekt für ctypes
Das grundlegendste Objekt ist ein int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Nun bezieht sich obj
auf einen Speicherblock, der den Wert 12 enthält.
Dieser Wert kann direkt abgerufen und sogar geändert werden:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Da sich obj
auf einen Speicherplatz bezieht, können wir auch die Größe und den Speicherort ermitteln:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
ctypes Arrays
Wie jeder gute C-Programmierer weiß, wird ein einzelner Wert Sie nicht so weit bringen. Was uns wirklich in Fahrt bringen wird, sind Arrays!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
Dies ist kein tatsächliches Array, aber es ist verdammt nah! Wir haben eine Klasse erstellt, die ein Array von 16 int
s kennzeichnet.
Jetzt müssen wir es nur noch initialisieren:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Jetzt ist arr
ein tatsächliches Array, das die Zahlen von 0 bis 15 enthält.
Sie können wie jede Liste aufgerufen werden:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
Und wie jedes andere ctypes
Objekt hat es auch eine Größe und einen Ort:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Wrapping-Funktionen für ctypes
In einigen Fällen akzeptiert eine C-Funktion einen Funktionszeiger. Als avid ctypes
Benutzer möchten wir diese Funktionen verwenden und sogar die Python-Funktion als Argumente übergeben.
Definieren wir eine Funktion:
>>> def max(x, y):
return x if x >= y else y
Nun nimmt diese Funktion zwei Argumente an und gibt ein Ergebnis desselben Typs zurück. Nehmen wir im Beispiel an, dass type ein int ist.
Wie beim Array-Beispiel können wir ein Objekt definieren, das diesen Prototyp kennzeichnet:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Dieser Prototyp bezeichnet eine Funktion, die ein c_int
(das erste Argument) c_int
und zwei c_int
Argumente (die anderen Argumente) akzeptiert.
Lassen Sie uns nun die Funktion umschließen:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Funktionsprototypen haben mehr Verwendung: Sie können ctypes
function (wie libc.ntohl
) libc.ntohl
und überprüfen, ob beim Aufrufen der Funktion die richtigen Argumente verwendet werden.
>>> 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)
Komplexe Verwendung
Lassen Sie uns alle obigen Beispiele in einem komplexen Szenario kombinieren: Verwenden Sie die lfind
Funktion von libc
.
Weitere Informationen zu dieser Funktion finden Sie in der Manpage . Ich bitte Sie dringend, es zu lesen, bevor Sie fortfahren.
Zuerst definieren wir die richtigen Prototypen:
>>> 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)
Dann lassen Sie uns die Variablen erstellen:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
Und jetzt definieren wir die Vergleichsfunktion:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Beachten Sie, dass x
und y
POINTER(c_int)
sind POINTER(c_int)
müssen wir sie dereferenzieren und ihre Werte verwenden, um den im Speicher gespeicherten Wert vergleichen zu können.
Jetzt können wir alles miteinander kombinieren:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
ist der zurückgegebene Leerzeiger. Wenn der key
nicht in arr
gefunden wurde, arr
der Wert None
, aber in diesem Fall haben wir einen gültigen Wert erhalten.
Jetzt können wir es konvertieren und auf den Wert zugreifen:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
Wir können auch sehen, dass ptr
auf den korrekten Wert innerhalb von arr
:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True