Python Language
ctypes
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