ctypes en Python: usa bibliotecas C

Publicado el: 24/08/2026
Tempo de leitura: 6 minutos
African American man using a laptop in a well-organized library setting with colorful bookshelves.

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código en un monitor que representa acceso a memoria nativa con ctypes.memoryview_at en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ctypes.memoryview_at: memoria nativa sin copias

    Aprende ctypes.memoryview_at en Python para acceder a memoria nativa sin copias, validar punteros y reducir riesgos.

    Ler mais

    Tempo de leitura: 7 minutos
    12/10/2026
    Código Python en pantalla representando EnumDict y enumeraciones avanzadas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    EnumDict: personaliza Enums con validación

    Aprende EnumDict en Python para personalizar EnumType, validar miembros y crear enumeraciones avanzadas con seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/10/2026
    Rack de servidores que representa el balanceo de conexiones con SO_REUSEPORT_LB en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: reparte conexiones entre workers

    Aprende SO_REUSEPORT_LB en Python para distribuir conexiones entre workers con pruebas, portabilidad y cierre ordenado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026
    Código Python asíncrono en un portátil para inspect.markcoroutinefunction
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: detecta wrappers async

    Aprende inspect.markcoroutinefunction en Python para identificar wrappers asíncronos, integrar frameworks y evitar detecciones incorrectas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Código Python para recorrer carpetas y archivos con Path.walk
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: recorre directorios con seguridad

    Aprende Path.walk en Python para recorrer directorios, filtrar archivos, tratar errores y controlar la travesía con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Depuración de un proceso Python en terminal con código
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depura procesos Python en ejecución

    Aprende a conectar pdb a un proceso Python en ejecución, inspeccionar la pila y diagnosticar bloqueos de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026