Sök…


Introduktion

ctypes är ett inbyggt ctypes som åberopar exporterade funktioner från ursprungliga kompilerade bibliotek.

Obs: Eftersom detta bibliotek hanterar kompilerad kod är det relativt OS-beroende.

Grundläggande användning

Låt oss säga att vi vill använda libc : s ntohl funktion.

Först måste vi ladda libc.so :

>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>

Sedan får vi funktionsobjektet:

>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>

Och nu kan vi helt enkelt åberopa funktionen:

>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'

Vilket gör exakt vad vi förväntar oss att det ska göra.

Vanliga fallgropar

Det går inte att ladda en fil

Det första möjliga felet går inte att ladda biblioteket. I så fall höjs vanligtvis en OSError.

Detta beror antingen på att filen inte finns (eller inte hittas av operativsystemet):

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

Som ni ser är felet tydligt och ganska vägledande.

Det andra skälet är att filen hittas, men inte har rätt format.

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

I det här fallet är filen en skriptfil och inte en .so fil. Detta kan också hända när du försöker öppna en .dll fil på en Linux-maskin eller en 64bit-fil på en 32bit-python-tolk. Som du ser är felet i detta fall lite vagare och kräver lite grävning.

Misslyckas med att komma åt en funktion

Förutsatt att vi lyckades ladda .so filen, måste vi då komma åt vår funktion som vi gjort i det första exemplet.

När en icke-befintlig funktion används höjs en 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

Grundläggande ctypobjekt

Det mest grundläggande objektet är en int:

>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)

obj hänvisar nu till en bit av minnet som innehåller värdet 12.

Det värdet kan nås direkt och ändras till och med:

>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)

Eftersom obj hänvisar till en bit av minnet, kan vi också ta reda på det är storlek och plats:

>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'

ctypuppsättningar

Som alla bra C-programmerare vet kommer ett enda värde inte att få dig så långt. Det som verkligen får oss att gå är matriser!

>>> c_int * 16
<class '__main__.c_long_Array_16'>

Detta är inte ett faktiskt array, men det är ganska darn nära! Vi skapade en klass som betecknar en matris med 16 int .

Allt vi behöver göra är att initialisera det:

>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>

Nu är arr en faktisk matris som innehåller siffrorna från 0 till 15.

De kan nås precis som vilken lista som helst:

>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20

Och precis som alla andra ctypes har det också en storlek och en plats:

>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'

Inpackningsfunktioner för ctyper

I vissa fall accepterar en C-funktion en funktionspekare. Som avid- ctypes vi använda dessa funktioner och till och med överföra pythonfunktion som argument.

Låt oss definiera en funktion:

>>> def max(x, y):
        return x if x >= y else y

Nu tar den funktionen två argument och returnerar ett resultat av samma typ. Låt oss anta att exemplet är ett int.

Som vi gjorde i arrayexemplet kan vi definiera ett objekt som anger den här prototypen:

>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>

Den prototypen anger en funktion som returnerar en c_int (det första argumentet) och accepterar två c_int argument (de andra argumenten).

Låt oss nu ta bort funktionen:

>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>

Funktionsprototyper har mer användning: De kan linda in ctypes (som libc.ntohl ) och verifiera att rätt argument används när man åberopar funktionen.

>>> 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)

Komplex användning

Låt oss kombinera alla exemplen ovan i ett komplext scenario: med hjälp av libc : s lfind funktion.

För mera information om funktionen, läs man-sidan . Jag uppmanar dig att läsa det innan du fortsätter.

Först definierar vi rätt prototyper:

>>> 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)

Låt oss sedan skapa variablerna:

>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)

Och nu definierar vi jämförelsefunktionen:

>>> def compar(x, y):
        return x.contents.value - y.contents.value

Lägg märke till att x och y är POINTER(c_int) , så vi måste återvända dem och ta deras värden för att faktiskt jämföra värdet som lagras i minnet.

Nu kan vi kombinera allt tillsammans:

>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))

ptr är den returnerade void-pekaren. Om key inte hittades i arr , skulle värdet vara None , men i det här fallet fick vi ett giltigt värde.

Nu kan vi konvertera det och få tillgång till värdet:

>>> cast(ptr, POINTER(c_int)).contents
c_long(12)

Vi kan också se att ptr pekar på rätt värde inuti arr :

>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow