El módulo ctypes permite cargar bibliotecas compartidas y llamar funciones C directamente desde Python. Proporciona tipos compatibles con C, punteros, estructuras, unions, arrays, callbacks y acceso a símbolos exportados por DLL, archivos .so y .dylib.
Este poder implica un riesgo elevado. ctypes trabaja con memoria nativa y evita varias garantías de seguridad de Python. Una firma incorrecta, un puntero inválido, una calling convention equivocada o un callback recolectado pueden corromper datos, revelar memoria, provocar access violations o cerrar el proceso.
Cuándo usar ctypes
Usa ctypes cuando existe una biblioteca C estable, no hay un binding mantenido y la API requerida puede describirse con precisión. Para APIs grandes, complejas o críticas, considera CFFI, Cython, pybind11 o una extensión compilada dedicada.
Antes de escribir el wrapper, confirma ABI, arquitectura, calling convention, tamaño de tipos, ownership de memoria, thread safety y contrato de errores.
Encontrar y cargar una biblioteca
from ctypes import CDLL
from ctypes.util import find_library
nombre = find_library("m")
if not nombre:
raise RuntimeError("No se encontró la biblioteca matemática")
libm = CDLL(nombre)
find_library() depende de la plataforma. Un wrapper distribuible suele funcionar mejor con nombres y rutas probados por sistema operativo que aceptando una ruta arbitraria del usuario.
Cargar una biblioteca nativa ejecuta código dentro del proceso. No pases un archivo subido o una ruta no confiable directamente a CDLL(). Valida origen, versión, integridad, permisos y directorios de búsqueda.
Calling conventions
CDLL usa la convención C estándar. En Windows, WinDLL usa stdcall y OleDLL interpreta retornos como HRESULT. Elegir la convención incorrecta puede corromper la pila.
Consulta headers y documentación del proveedor. No deduzcas la convención por el nombre del archivo o función.
Define argtypes y restype
Las funciones extranjeras se consideran como si devolvieran c_int cuando no se define restype. Ese default puede truncar punteros, enteros de 64 bits, timestamps y tamaños.
from ctypes import CDLL, c_double
from ctypes.util import find_library
libm = CDLL(find_library("m"))
cos = libm.cos
cos.argtypes = [c_double]
cos.restype = c_double
print(cos(0.0))
Define prototipos antes de la primera llamada. argtypes valida y convierte argumentos; restype interpreta correctamente el retorno.
Tipos compatibles con C
El módulo incluye c_int, c_uint, c_long, c_size_t, c_float, c_double, c_char_p, c_wchar_p, c_void_p y enteros de ancho fijo. El tamaño de C long cambia entre plataformas.
Para protocolos binarios con formato explícito, compara con struct en Python. struct trabaja con layouts de bytes definidos; ctypes.Structure suele seguir la ABI nativa.
Strings y buffers mutables
c_char_p representa un puntero a string terminado en NUL. No debe usarse cuando la función C escribirá en memoria. Usa un buffer mutable.
from ctypes import create_string_buffer
buffer = create_string_buffer(256)
# funcion_c(buffer, len(buffer))
print(buffer.value)
Confirma si el tamaño incluye el NUL final y cómo la API informa truncamiento. Nunca declares un tamaño mayor que la asignación real.
Punteros y byref()
byref() pasa un objeto por referencia con poco overhead. pointer() crea un objeto puntero reutilizable.
from ctypes import c_int, byref
resultado = c_int()
# estado = biblioteca.calcular(10, byref(resultado))
# print(estado, resultado.value)
ctypes detecta acceso a puntero NULL, pero no puede validar una dirección no nula incorrecta. Indexar fuera de un array puede leer o sobrescribir memoria arbitraria.
Estructuras, alineación y layout
from ctypes import Structure, c_int
class Punto(Structure):
_fields_ = [
("x", c_int),
("y", c_int),
]
El layout depende de la ABI. Verifica sizeof(), offsets, alineación y byte order contra el header y un programa C de referencia. No ajustes _pack_, _align_ o _layout_ por prueba y error.
Bit fields y unions dependen del compilador. La documentación recomienda pasar estructuras o unions con bit fields por puntero, no por valor.
Ownership de memoria
Una función C puede devolver memoria estática, un puntero prestado, una referencia ligada a otro objeto o un buffer nuevo que debe liberarse. Un wrapper seguro documenta ownership y expone la función correcta de liberación.
No liberes memoria con un allocator distinto. En Windows, runtimes C diferentes pueden usar heaps incompatibles.
from ctypes import c_void_p
lib.crear_buffer.restype = c_void_p
lib.liberar_buffer.argtypes = [c_void_p]
puntero = lib.crear_buffer()
if not puntero:
raise MemoryError("Falló la asignación nativa")
try:
pass # Usar solo dentro de límites documentados
finally:
lib.liberar_buffer(puntero)
errno y errores del sistema
Carga la biblioteca con use_errno=True cuando la API informa fallos mediante errno. Lee la copia thread-local de ctypes inmediatamente después.
import os
from ctypes import CDLL, get_errno
lib = CDLL("libejemplo.so", use_errno=True)
resultado = lib.operacion()
if resultado == -1:
codigo = get_errno()
raise OSError(codigo, os.strerror(codigo))
El próximo artículo de este lote profundiza en las constantes de errno.
Centralizar validación con errcheck
def comprobar(resultado, funcion, argumentos):
if resultado == 0:
codigo = get_errno()
raise OSError(codigo, os.strerror(codigo))
return resultado
lib.operacion.errcheck = comprobar
El contrato de éxito depende de la API. Algunas funciones devuelven cero en éxito; otras, cero o NULL en fallo; otras usan valores negativos.
Callbacks de C a Python
CFUNCTYPE y WINFUNCTYPE crean punteros C respaldados por callables de Python.
from ctypes import CFUNCTYPE, c_int
COMPARAR = CFUNCTYPE(c_int, c_int, c_int)
@COMPARAR
def comparar(a, b):
return (a > b) - (a < b)
Mantén una referencia fuerte mientras el código nativo pueda llamar el callback. Si se recolecta, una invocación posterior puede cerrar el intérprete.
No permitas que excepciones escapen del callback. Captúralas, guarda un estado controlado y devuelve un valor permitido por el contrato C.
Threads y GIL
Las llamadas por CDLL normalmente liberan el GIL. Eso no vuelve thread-safe la biblioteca. Sincroniza el estado según su documentación.
En builds free-threaded, accesos simultáneos a la misma dirección mediante diferentes punteros pueden requerir threading.Lock.
Segmentation faults e aislamiento
Un segmentation fault no es una excepción normal. Activa faulthandler, ejecuta tests riesgosos en subprocesses y usa AddressSanitizer, Valgrind o debugger nativo.
Para bibliotecas inestables, aísla el binding en un worker. Un crash termina solo el worker y no el servicio principal.
Validar tamaños y rangos
Antes de entrar en C, verifica longitudes, rangos, cantidades de elementos, relaciones entre parámetros y supuestos de punteros. No dependas del truncamiento silencioso. Confirma que los valores caben en el tipo C declarado.
Evitar cast() innecesario
cast() reinterpreta la misma dirección como otro tipo. No convierte datos, no corrige alineación y no valida tamaño. Úsalo solo cuando el contrato nativo lo exige y la vida del objeto original está garantizada.
Tests multiplataforma
Prueba cada sistema operativo, arquitectura y versión de biblioteca soportada. Verifica símbolos, tamaños, calling convention, offsets, alineación, Unicode y errores.
inspect en Python ayuda a validar la capa Python, pero no demuestra la ABI nativa. Los tests contra headers y código C de referencia siguen siendo obligatorios.
Seguridad de la cadena de suministro
Las bibliotecas nativas ejecutan con los privilegios del proceso. Fija versiones, valida hashes, usa fuentes confiables y restringe directorios de búsqueda. Variables como LD_LIBRARY_PATH o la ruta de DLL de Windows pueden alterar el binario cargado.
Para verificar integridad, consulta hashlib en Python.
Errores comunes
Los fallos frecuentes son omitir argtypes, aceptar el restype default, pasar strings inmutables como buffers, elegir calling convention incorrecta, perder referencias de callbacks, liberar con otro allocator, indexar punteros sin longitud y cargar DLL desde rutas no confiables.
Conclusión
ctypes permite crear bindings nativos sin compilar una extensión, pero exige disciplina similar a C. Define prototipos, valida ABI y tamaños, documenta ownership, conserva callbacks y trata los crashes como posibilidad real.
Consulta la documentación oficial de ctypes y la documentación oficial sobre extensión e integración con C.







