Interacción con bibliotecas dinámicas compiladas con GCC usando ctypes en Python

Introducción

ctypes es una biblioteca de funciones externas incluida en Python. Proporciona tipos de datos compatibles con C y permite invocar funciones desde bibliotecas dinámicas (DLL) o bibliotecas compartidas compiladas en C o C++. Este módulo facilita el desarrrollo de evnolturas para estas bibliotecas directamente en Python.

El código presentado se ejecuta en un sistema Windows con Python 64-bit versión 3.9.12. Para su uso en Linux, consultar entradas anteriores del blog.

1. Estructura básica del código

1.1 Crear archivo header.h
#pragma once

#define ExportDll __declspec(dllexport)

typedef struct {
    int coordenada_x;
    double coordenada_y;
} Punto;

extern "C"
{
    ExportDll void saludo_mundial(void);
    ExportDll char mostrar_caracter(char caracter);
    ExportDll double suma_valores(int valor1, float valor2, double valor3);
    ExportDll char* prueba_cadena(char *cadena);
    ExportDll int procesar_arreglos(int arreglo1[], int tamano1, int arreglo2[][3], int tamano2);
    ExportDll double* calcular_resultados(int *ptr1, float *ptr2, double *ptr3);
    ExportDll void desplegar_coordenada(Punto punto);
}

1.2 Crear archivo implementacion.cpp
#include <iostream>
#include <cstring>
#include "header.h"

void saludo_mundial() {
    std::cout << "Saludo Mundial!" << std::endl;
}

char mostrar_caracter(char caracter) {
   std::cout << caracter << std::endl;
   return caracter;
}

double suma_valores(int valor1, float valor2, double valor3) {
    return valor1 + valor2 + valor3;
}

char* prueba_cadena(char *cadena) {
    static char mensaje[] = "saludo";
    char *puntero;
    int indice;
    puntero = mensaje;
    for (indice = 0; indice < 2; indice++) {
        *(cadena + indice) = mensaje[indice];
    }
    return puntero;
}

double* calcular_resultados(int *ptr1, float *ptr2, double *ptr3) {
    static double resultados[5] = {2000.0, 4.0, 6.8, 34.0, 100.0};
    double *puntero_resultado;
    int i;
    resultados[0] = resultados[0] + *ptr1 + *ptr2 + *ptr3;
    puntero_resultado = resultados;
    return puntero_resultado;
}

int procesar_arreglos(int arreglo1[], int tamano1, int arreglo2[][3], int tamano2) {
    int i;
    for (i = 0; i < tamano1; i++) {
        arreglo1[i] = arreglo1[i] + 20;
    }

    int fila, columna;
    for (fila = 0; fila < tamano2; fila++) {
        for (columna = 0; columna < 3; columna++) {
            arreglo2[fila][columna] = arreglo2[fila][columna] + 200;
        }
    }
    return 0;
}

void desplegar_coordenada(Punto punto) {
    std::cout << "X: " << punto.coordenada_x << ", Y: " << punto.coordenada_y << std::endl;
}
</cstring></iostream>
1.3 Compilar código C++
g++ implementacion.cpp -fPIC -static -shared -o biblioteca.dll

1.4 Crear archivo python_interfaz.py
# -*- coding: UTF-8 -*-
from ctypes import *

# Cargar la biblioteca dinámica
lib = CDLL('./biblioteca.dll')

# Ejecutar función simple
lib.saludo_mundial()

# Configurar tipos para función que recibe y devuelve char
lib.mostrar_caracter.restype = c_char
resultado_caracter = lib.mostrar_caracter(c_char(b'X'))
print("Resultado caracter: ", resultado_caracter)

# Configurar tipos para operación matemática compleja
lib.suma_valores.restype = c_double
lib.suma_valores.argtypes = [c_int, c_float, c_double]
resultado_decimal = lib.suma_valores(c_int(10), c_float(1.5), c_double(0.1))
print("Resultado decimal: ", resultado_decimal)

# Manejo de cadenas de caracteres
lib.prueba_cadena.restype = c_char_p
lib.prueba_cadena.argtypes = [c_char_p]
buffer_cadena = c_char_p(b"t")
resultado = lib.prueba_cadena(buffer_cadena)
print(resultado)
print(buffer_cadena.value)

# Operaciones con punteros
lib.calcular_resultados.restype = POINTER(c_double)
lib.calcular_resultados.argtypes = [POINTER(c_int), POINTER(c_float), POINTER(c_double)]
resultados = lib.calcular_resultados(pointer(c_int(200)), pointer(c_float(0.2)), pointer(c_double(0.02)))

for idx in range(5):
    print(resultados[idx])

# Uso de arreglos unidimensionales y bidimensionales
lib.procesar_arreglos.argtypes = [c_int * 4, c_int, (c_int * 3)*4, c_int]
arreglo_uno = (c_int * 4)(2, 4, 6, 8)
arreglo_dos = ((c_int * 3)*4)((2, 4, 6), (8, 10, 12), (14, 16, 18), (20, 22, 24))
lib.procesar_arreglos(arreglo_uno, 4, arreglo_dos, 4)

for idx in range(4):
    print("Python arreglo 1:", arreglo_uno[idx])

for idx in range(4):
    for jdx in range(3):
        print("Python arreglo 2:", arreglo_dos[idx][jdx])

# Segunda forma usando punteros
lib.procesar_arreglos.argtypes = [POINTER(c_int), c_int, POINTER(c_int), c_int]
arreglo_uno = (c_int * 4)(2, 4, 6, 8)
arreglo_uno_ptr = cast(arreglo_uno, POINTER(c_int))
arreglo_dos = ((c_int * 3)*4)((2, 4, 6), (8, 10, 12), (14, 16, 18), (20, 22, 24))
arreglo_dos_ptr = cast(arreglo_dos, POINTER(c_int))
lib.procesar_arreglos(arreglo_uno_ptr, 4, arreglo_dos_ptr, 4)

for idx in range(4):
    print("Python arreglo 1 (puntero):", arreglo_uno_ptr[idx])

for idx in range(12):
    print("Python arreglo 2 (puntero):", arreglo_dos_ptr[idx])

# Definición y uso de estructuras
class Punto(Structure):
    _fields_ = [
        ('x', c_int),
        ('y', c_double),
    ]

coordenada = Punto(5, 10)
print(coordenada.x, coordenada.y)

lib.desplegar_coordenada(coordenada)
print(coordenada.x, coordenada.y)

1.5 Ejecutar código Python
python python_interfaz.py

Etiquetas: Python ctypes dll GCC C++

Publicado el 9-29 06:51