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