SWIG: donde C y Python se dan la mano
Cómo crear un módulo de Python a partir de uno en C: python wrappers para C.Me encanta Python. La sencillez de uso y la potencia que te ofrece el lenguaje dejan mella en cualquiera. Tiene una gran cantidad de bibliotecas para hacer casi cualquier cosa. Pero claro, no todo está hecho en Python. Hay mucho código escrito en C, particularmente bibliotecas, drivers, etc. que no es accesible desde Python por cuestiones evidentes: ¡está hecho en C! Por otro lado, es posible que tengas un proyecto en el que haya partes que son especialmente críticas en cuanto al tiempo de ejecución, y quizá te planteés hacerlas en C. Pero... ¿es posible combinarlas con el resto del código, hecho en Python? Pues sí. Gracias a SWIG
Ingredientes
- swig, por supuesto
- el módulo a exportar
- paciencia (a veces mucha...)
Preparándonos
Como puedes ver por el ingrediente número tres, es posible que exportar un módulo de C a Python se convierta en una verdadera odisea, sobre todo al comenzar. Pero, como todo en la vida, con paciencia es posible. ¡No te quemes! Otra cosa a contemplar sobre Swig es que no sólo sirve para portar interfaces de C a Python, sino que lo puedes hacer a un montón de lenguajes: Tcl, Perl, Guile, Ruby, Java, PHP... (aquí está la lista completa). Y por si fuera poco, tiene algo de soporte para C++, aunque este no está completo (si quieres portar cosas de C++ a Python, yo mejor probaría con Boost.Python). Bien, con eso en mente podemos empezar. Swig crea un wrapper (o envoltorio) de las interfaces que queremos tener disponibles para Python. Luego, hemos de especificar de alguna forma qué queremos exportar: qué funciones, qué estructuras, etc. Para eso, tenemos dos opciones. En primer lugar, podemos modificar los ficheros de cabecera (en donde se supone deben estar los prototipos de las funciones y esas cosas) y añadir instrucciones de compilación condicional que le digan a swig qué cosas debe hacer. Esté método no se aconseja por varias razones, entre ellas, es necesario modificar el código fuente original y eso queremos evitarlo (hazme caso, QUIERES evitarlo :-p). El segundo método es crear un fichero en el que se haga explícito lo que queremos envolver para Python. Suele tener la extensión .i, por convenio. Este fichero, que estará escrito prácticamente en ANSI C, contiene además las directivas para swig. Pero para muestra, un botón: veamos un ejemplo.Primer ejemplo
Supongamos que tenemos un módulo sencillo que tiene algunas funciones matemáticas. Su cabecera es la siguiente:/* ejemplo.h */ /* Modulo de ejemplo: operaciones */ #ifndef __EJEMPLO_H__ #define __EJEMPLO_H__ typedef struct { float real; float imag; }Complex; /* Prototipos */ extern int potencia(int, int); extern int factorial(int); extern Complex* sumaCompleja(Complex*, Complex*, Complex*); extern void error_msg(char*); #endif /* __EJEMPLO_H__ */No tiene nada especial: una estructura y cuatro funciones, que están implementadas en ejemplo.c. Para exportarlo todo a Python, podríamos modificar el código añadiendo esto después del #define __EJEMPLO_H__:
#ifdef SWIG %module ejemplo %{ #include "ejemplo.h" %}Con esto, ya tendríamos las modificaciones necesarias. Pero como esta es la manera que se desaconseja, veamos la otra. Creamos un fichero nuevo que se llame ejemplo.i y metemos dentro lo siguiente:
%module ejemplo %{ #include "ejemplo.h" %} typedef struct { float real; float imag; }Complex; /* Prototipos */ extern int potencia(int, int); extern int factorial(int); extern Complex* sumaCompleja(Complex*, Complex*, Complex*); extern void error_msg(char*);La primera línea, %module ejemplo especifica el nombre del módulo y es obligatorio ponerlo, a menos que lo indiques por la línea de comandos. El bloque compuesto por '%{' y '%}' es opcional. Todo lo que haya dentro, se copiará tal cual en el fichero nuevo que swig va a crear y del cual es buen momento para hablar. Cuando swig parsee este fichero, generará uno nuevo, llamado algo_wrap.c por defecto (aunque le puedes decir el nombre que quieras), donde algo es el nombre del fichero original y que contendrá todo el código en C necesario para crear el módulo de Python, es efectivamente el wrapper que queremos. Por eso, es necesario que el wrapper sepa cosas de nuestras funciones, como por ejemplo, donde están los prototipos... El resto del código es exactamente igual al que tiene la cabecera. Con esto, le decimos qué queremos que contenga el wrapper. Si eliminamos la función potencia() de esta lista, no la veremos en el módulo de Python. Así podemos elegir que exportar y que no.
Compilando
Bueno, es hora de terminar con esto. Sólo nos queda generar el wrapper y compilarlo todo. Para generar el wrapper, usamos swig evidentemente:$ ls ejemplo.c ejemplo.h ejemplo.i $ swig -python ejemplo.i $ ls ejemplo.c ejemplo.h ejemplo.i ejemplo.py ejemplo_wrap.c $La opción -python es para indicarle que el wrapper lo queremos para Python. Podríamos haber puesto otras opciones, como por ejemplo -o wrapper.c, para indicarle el nombre del fichero, pero no es necesario, como ves ha creado uno llamado ejemplo_wrap.c. También hay otro nuevo, ejemplo.py, que es el módulo final que cargaremos y que a su vez cargará el que está en C. Ahora podemos compilarlo todo:
$gcc -c ejemplo.c $gcc -c ejemplo_wrap.c -I /usr/include/python2.4/ $gcc -shared ejemplo.o ejemplo_wrap.o -o _ejemplo.so $ls ejemplo.c ejemplo.h ejemplo.i ejemplo.o ejemplo.py _ejemplo.so ejemplo_wrap.c ejemplo_wrap.o $Cuando compilamos el wrapper, le especificamos la ruta de las cabeceras de Python que necesita (para este caso, las de Python2.4). Y cuando lo enlazamos, le especificamos que sea -shared. Y el nombre objeto final, ha de ser '_'+nombre del módulo+'.so'. ¡Y ya está! Ahora podemos probarlo.
$python Python 2.4.4 (#2, Apr 5 2007, 20:11:18) [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import ejemplo >>> ejemplo.potencia(2, 10) 1024 >>> ejemplo.Complex() <ejemplo.Complex; proxy of <Swig Object of type 'Complex *' at 0x81a06c0> > >>>Como puedes ver, se pueden usar sin ningún problema. Quizá te sea útil saber las conversiones que se hacen (en este caso, la estructura Complex se convierte en una clase que es un proxy de esa estructura). Lo puedes ver en la documentación.
Referencias
[ show comments ]
blog comments powered by Disqus