Ricerca…


introduzione

ctypes è una libreria incorporata in Python che richiama le funzioni esportate da librerie compilate native.

Nota: poiché questa libreria gestisce il codice compilato, è relativamente dipendente dal SO.

Utilizzo di base

Diciamo che vogliamo usare la funzione ntohl libc .

Per prima cosa, dobbiamo caricare libc.so :

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

Quindi, otteniamo l'oggetto funzione:

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

E ora, possiamo semplicemente invocare la funzione:

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

Che fa esattamente quello che ci aspettiamo che faccia.

Insidie ​​comuni

Impossibile caricare un file

Il primo possibile errore non riesce a caricare la libreria. In tal caso viene sollevato di solito un OSError.

Questo perché il file non esiste (o non può essere trovato dal sistema operativo):

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

Come puoi vedere, l'errore è chiaro e piuttosto indicativo.

La seconda ragione è che il file è stato trovato, ma non è del formato corretto.

>>> 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 questo caso, il file è un file di script e non un file .so . Ciò potrebbe anche accadere quando si tenta di aprire un file .dll su una macchina Linux o un file 64bit su un interprete python a 32 bit. Come puoi vedere, in questo caso l'errore è un po 'più vago e richiede alcuni scavi.

Impossibile accedere a una funzione

Supponendo di aver caricato correttamente il file .so , dobbiamo quindi accedere alla nostra funzione come abbiamo fatto nel primo esempio.

Quando viene utilizzata una funzione non esistente, viene sollevato un 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

Oggetto ctypes di base

L'oggetto più elementare è un int:

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

Ora, obj riferisce a un blocco di memoria contenente il valore 12.

È possibile accedere direttamente a questo valore e persino modificarlo:

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

Poiché obj riferisce a un blocco di memoria, possiamo anche scoprire la sua dimensione e posizione:

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

array di tipi

Come ogni buon programmatore C sa, un singolo valore non ti porterà lontano. Ciò che ci farà davvero andare avanti sono gli array!

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

Questo non è un array reale, ma è dannatamente vicino! Abbiamo creato una classe che denota un array di 16 int s.

Ora tutto ciò che dobbiamo fare è inizializzarlo:

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

Ora arr è un array reale che contiene i numeri da 0 a 15.

Possono essere consultati come qualsiasi elenco:

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

E proprio come qualsiasi altro oggetto ctypes , ha anche una dimensione e una posizione:

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

Funzioni di avvolgimento per i tipi

In alcuni casi, una funzione C accetta un puntatore a funzione. Come utenti accaniti di ctypes , vorremmo usare quelle funzioni e persino passare la funzione python come argomenti.

Definiamo una funzione:

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

Ora, quella funzione accetta due argomenti e restituisce un risultato dello stesso tipo. Per l'esempio, supponiamo che type sia un int.

Come abbiamo fatto sull'esempio dell'array, possiamo definire un oggetto che denota quel prototipo:

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

Quel prototipo denota una funzione che restituisce un c_int (il primo argomento) e accetta due argomenti c_int (gli altri argomenti).

Ora avvolgiamo la funzione:

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

Prototipi di funzione hanno in più l'utilizzo: possono avvolgere ctypes funzione (come libc.ntohl ) e verificare che gli argomenti corretti vengono utilizzati quando si richiama la funzione.

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

Utilizzo complesso

Combiniamo tutti gli esempi sopra in uno scenario complesso: usando la funzione lfind libc .

Per maggiori dettagli sulla funzione, leggi la pagina man . Vi esorto a leggerlo prima di andare avanti.

Innanzitutto, definiremo i prototipi corretti:

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

Quindi, creiamo le variabili:

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

E ora definiamo la funzione di confronto:

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

Si noti che x y sono POINTER(c_int) , quindi è necessario dereferenziarli e prendere i loro valori per confrontare effettivamente il valore memorizzato nella memoria.

Ora possiamo combinare tutto insieme:

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

ptr è il puntatore vuoto restituito. Se la key non è stata trovata in arr , il valore sarebbe None , ma in questo caso abbiamo ottenuto un valore valido.

Ora possiamo convertirlo e accedere al valore:

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

Inoltre, possiamo vedere che ptr punta al valore corretto all'interno di arr :

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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow