Python Language
ctypes
Zoeken…
Invoering
ctypes
is een ingebouwde python-bibliotheek die geëxporteerde functies uit native gecompileerde bibliotheken oproept.
Opmerking: aangezien deze bibliotheek gecompileerde code verwerkt, is deze relatief afhankelijk van het besturingssysteem.
Basis gebruik
Laten we zeggen dat we de ntohl
functie van libc
willen gebruiken.
Eerst moeten we libc.so
laden:
>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>
Vervolgens krijgen we het functieobject:
>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>
En nu kunnen we eenvoudig de functie aanroepen:
>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'
Dat doet precies wat we ervan verwachten.
Veel voorkomende valkuilen
Geen bestand laden
De eerste mogelijke fout kan de bibliotheek niet laden. In dat geval wordt meestal een OSError opgeworpen.
Dit komt omdat het bestand niet bestaat (of niet kan worden gevonden door het besturingssysteem):
>>> 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
Zoals u kunt zien, is de fout duidelijk en vrij indicatief.
De tweede reden is dat het bestand is gevonden, maar niet de juiste indeling heeft.
>>> 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 dit geval is het bestand een scriptbestand en geen .so
bestand. Dit kan ook gebeuren wanneer u probeert een .dll
bestand te openen op een Linux-machine of een 64bit-bestand op een 32bit python-interpreter. Zoals je kunt zien, is de fout in dit geval een beetje vager en moet je wat rondgraven.
Geen toegang tot een functie
Ervan uitgaande dat we het .so
bestand met succes hebben geladen, moeten we onze functie openen zoals we in het eerste voorbeeld hebben gedaan.
Wanneer een niet-bestaande functie wordt gebruikt, wordt een AttributeError
opgewekt:
>>> 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
Basic ctypes object
Het meest elementaire object is een int:
>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)
Nu verwijst obj
naar een stuk geheugen met de waarde 12.
Die waarde is direct toegankelijk en kan zelfs worden gewijzigd:
>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)
Omdat obj
verwijst naar een deel van het geheugen, kunnen we ook de grootte en locatie ervan achterhalen:
>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'
ctypes arrays
Zoals elke goede C-programmeur weet, zal een enkele waarde u niet zover brengen. Wat ons echt op gang zal brengen zijn arrays!
>>> c_int * 16
<class '__main__.c_long_Array_16'>
Dit is geen echte reeks, maar het is behoorlijk verdomd dichtbij! We hebben een klasse gemaakt die een array van 16 int
s aangeeft.
Nu hoeven we het alleen nog te initialiseren:
>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>
Nu is arr
een feitelijke array die de getallen van 0 tot 15 bevat.
Ze zijn net als elke lijst toegankelijk:
>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20
En net als elk ander ctypes
object, heeft het ook een grootte en een locatie:
>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'
Inpakfuncties voor ctypes
In sommige gevallen accepteert een C-functie een functie-aanwijzer. Als fervente gebruikers van ctypes
willen we die functies graag gebruiken en zelfs de python-functie als argumenten doorgeven.
Laten we een functie definiëren:
>>> def max(x, y):
return x if x >= y else y
Nu neemt die functie twee argumenten en retourneert een resultaat van hetzelfde type. Laten we voor het voorbeeld aannemen dat type een int is.
Zoals we deden in het array-voorbeeld, kunnen we een object definiëren dat dat prototype aangeeft:
>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>
Dat prototype geeft een functie aan die een c_int
(het eerste argument) retourneert en twee c_int
argumenten accepteert (de andere argumenten).
Laten we nu de functie inpakken:
>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>
Functieprototypes hebben meer gebruik: ze kunnen de ctypes
functie (zoals libc.ntohl
) libc.ntohl
en controleren of de juiste argumenten worden gebruikt bij het aanroepen van de functie.
>>> 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)
Complex gebruik
Laten we alle bovenstaande voorbeelden combineren in één complex scenario: de lfind
functie van libc
lfind
.
Lees de man-pagina voor meer informatie over de functie. Ik verzoek u dringend om het te lezen voordat u verder gaat.
Eerst zullen we de juiste prototypes definiëren:
>>> 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)
Laten we vervolgens de variabelen maken:
>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)
En nu definiëren we de vergelijkingsfunctie:
>>> def compar(x, y):
return x.contents.value - y.contents.value
Merk op dat x
en y
POINTER(c_int)
, dus moeten we ze POINTER(c_int)
en hun waarden nemen om de in het geheugen opgeslagen waarde daadwerkelijk te vergelijken.
Nu kunnen we alles combineren:
>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))
ptr
is de geretourneerde nietige aanwijzer. Als de key
niet in arr
werd gevonden, zou de waarde None
, maar in dit geval hebben we een geldige waarde.
Nu kunnen we het converteren en toegang krijgen tot de waarde:
>>> cast(ptr, POINTER(c_int)).contents
c_long(12)
We kunnen ook zien dat ptr
naar de juiste waarde binnen arr
wijst:
>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True