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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow