Buscar..


Introducción

ctypes es una biblioteca incorporada de python que invoca funciones exportadas desde bibliotecas compiladas nativas.

Nota: Dado que esta biblioteca maneja el código compilado, es relativamente dependiente del sistema operativo.

Uso básico

Digamos que queremos usar la libc ntohl libc .

Primero, debemos cargar libc.so :

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

Entonces, obtenemos el objeto de función:

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

Y ahora, simplemente podemos invocar la función:

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

Lo que hace exactamente lo que esperamos que haga.

Errores comunes

No cargar un archivo

El primer error posible está fallando al cargar la biblioteca. En ese caso, un OSError normalmente se plantea.

Esto se debe a que el archivo no existe (o el sistema operativo no lo puede encontrar):

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

Como puede ver, el error es claro y bastante indicativo.

La segunda razón es que se encuentra el archivo, pero no tiene el formato correcto.

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

En este caso, el archivo es un archivo de script y no un archivo .so . Esto también puede suceder cuando se intenta abrir un archivo .dll en una máquina Linux o un archivo de 64 bits en un intérprete de 32 bits de Python. Como puede ver, en este caso el error es un poco más vago y requiere un poco de investigación.

No acceder a una función

Suponiendo que .so éxito el archivo .so , necesitamos acceder a nuestra función como lo hemos hecho en el primer ejemplo.

Cuando se usa una función que no existe, se AttributeError 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

Objeto de ctypes básico

El objeto más básico es un int:

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

Ahora, obj refiere a una porción de memoria que contiene el valor 12.

Se puede acceder a ese valor directamente, e incluso modificarse:

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

Ya que obj refiere a un trozo de memoria, también podemos averiguar su tamaño y ubicación:

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

arrays de ctypes

Como cualquier buen programador de C sabe, un solo valor no lo llevará tan lejos. ¡Lo que realmente nos hará avanzar son matrices!

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

Esto no es una matriz real, pero está bastante cerca! Creamos una clase que denota una matriz de 16 int s.

Ahora todo lo que tenemos que hacer es inicializarlo:

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

Ahora arr es una matriz real que contiene los números del 0 al 15.

Se puede acceder a ellos como en cualquier lista:

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

Y al igual que cualquier otro objeto ctypes , también tiene un tamaño y una ubicación:

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

Funciones de envoltura para ctypes.

En algunos casos, una función C acepta un puntero de función. Como usuarios ávidos de ctypes , nos gustaría usar esas funciones, e incluso pasar la función python como argumentos.

Vamos a definir una función:

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

Ahora, esa función toma dos argumentos y devuelve un resultado del mismo tipo. Por el bien del ejemplo, asumamos que el tipo es un int.

Como hicimos en el ejemplo de matriz, podemos definir un objeto que denota ese prototipo:

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

Ese prototipo denota una función que devuelve un c_int (el primer argumento), y acepta dos argumentos c_int (los otros argumentos).

Ahora vamos a envolver la función:

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

Los prototipos de funciones tienen un mayor uso: pueden envolver la función ctypes (como libc.ntohl ) y verificar que se usan los argumentos correctos cuando se invoca la función.

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

Uso complejo

lfind todos los ejemplos anteriores en un escenario complejo: utilizando la libc lfind libc .

Para más detalles sobre la función, lea la página del manual . Les insto a que lo lean antes de continuar.

Primero, definiremos los prototipos adecuados:

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

Entonces, vamos a crear las variables:

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

Y ahora definimos la función de comparación:

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

Tenga en cuenta que x , y y son POINTER(c_int) , por lo tanto, debemos desreferenciarlos y tomar sus valores para poder comparar realmente el valor almacenado en la memoria.

Ahora podemos combinar todo juntos:

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

ptr es el puntero vacío devuelto. Si no se encontró la key en arr , el valor sería None , pero en este caso obtuvimos un valor válido.

Ahora podemos convertirlo y acceder al valor:

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

También, podemos ver que ptr apunta al valor correcto dentro de arr :

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


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow