Publicado en 22 de diciembre, 2017
C++: Cargar y ejecutar Python (Scripting)
C++ es un lenguaje de programación muy poderoso, pero uno de sus ventajas es que una vez compilado, si necesitas hacer ciertos cambios se requiere de utilizar el código fuente, por lo que si necesitamos que un usuario pueda hacer ciertos cambios sin darle el código fuente, podemos darle la oportunidad de generar scripting, en este articulo mencionare como cargar y ejecutar código de Python desde C++ y la comunicación en ambos sentidos. En videojuegos es una manera tradicional de permitir al usuario genere sus propios mods, entre otros usos.
Configuración básica:
- Instalar Python (En este ejemplo yo utilize 3.6) de preferencia en «C:/Python36».
- Crear un proyecto de consola en C++.
- Compilar el «hola mundo» para que genera la carpeta de .exe.
- Copiar «python36_d.dll» donde esta el .exe.
- Agregar al proyecto include de «C:\Python36\include» y Lib a «C:\Python36\Lib».
Paso 1: Verificar la configuración básica:
Primero escribimos un pequeño código para verificar que este todo en su lugar:
#include <iostream> #include <Windows.h> #include <Python.h> int main() { //Inicilizamos inteprete Py_Initialize(); //Prueba de compilaciom PyRun_SimpleString("print('Ya estas usando Python :D')"); //Terminamos inteprete Py_Finalize(); system("Pause"); return 0; }
Si compila, ya estamos listos, si hay un error los pasos de configuración básica, si tienes un problema no dudes en comentar.
Paso 2: Cargar archivo python y ejecutarlo.
Primero cargaremos un archivo python y lo ejecutaremos, esto es más que suficiente si no necesitamos intercambio entre C++ y Python.
Código de Archivo de EjemploPython.py:
print('Ejecutando desde un archivo de python') print('Hola mundo desde Python')
Codigo en C++:
int main() { Py_Initialize(); char ArchivoPython[] = "EjemploPython.py"; FILE* file; //Leemos archivo file = _Py_fopen(ArchivoPython, "r"); //Ejecutamos PyRun_SimpleFile(file, ArchivoPython); Py_Finalize(); system("Pause"); return 0; }
Cargamos el archivo llamado «EjemploPython.py» y lo ejecutamos, esto ya nos va permitir que podamos hacer cambios directamente en el archivo de python sin necesidad de compilar de nuevo nuestro exe.
Paso 3: Llamar funciones de Python desde C++
Una de las utilidades de cargar python es que podemos definir funciones y llamarlas desde C++, esto igual nos permitiría cambiar que acción harían sin necesidad de compilar nuestro exe, esto lo vi en acción en un snake donde desde C++ se llama una función de Python que tenia que regresar 1, 2, 3 o 4 en que dirección se movería la serpiente, así los alumnos no podían cambiar como funciona el juego y se dedicaran 100% a la IA del snake.
Código en python «EjemploPython2.py» (Este archivo debe estar junto al exe)
def getInteger(): print('Funcion de getInteger llamado') c = ((1*2)+1)/3 return c
Aquí creamos un archivo que tiene definido una función llamado ‘getInteger’ que solo regresa un 1. En C++ como ya queremos llamar funciones de python, estos deben cargarse mediante variables del tipo PyObject que pueden ser funciones, variables o archivos. El tener funciones de python desde C++ se le conoce como modulo:
PyObject* Archivo = PyUnicode_FromString("EjemploPython2"); //Aqui no necesita extension PyObject* Modulo = PyImport_Import(Archivo); if (Modulo) //Se pudo cargar? { //Obtenemos referencia a la función llamada 'getInteger' PyObject* Func_getInteger = PyObject_GetAttrString(Modulo, "getInteger"); if (Func_getInteger && PyCallable_Check(Func_getInteger)) //Se pudo cargar la función? { //Llamamos y obtenemos resultado PyObject* resultado = PyObject_CallObject(Func_getInteger, NULL); //Imprimimos en C++ std::cout << "C++: getInteger regreso = " << PyLong_AsLong(resultado) << std::endl; } else { std::cout << "Error: No existe la función getInteger" << std::endl; } } else { std::cout << "Error: modulo no se pudo cargar" << std::endl; }
Paso 4: Llamar funciones de C++ desde Python (Mods y Scripting)
El ultimo paso y el que de mi parte es el que más utilidad tiene es poder llamar funciones de C++ desde el lado de Python, esto permite que se puedan crear Mods o permitir scripting siempre limitado por lo que tu definas del lado de C++. Esto en personal lo eh usado para crear mis niveles y ver cambios de más rápidos sin necesidad de compilar el exe.
Codigo en Python en EjemploPython3.py:
import arkmslib print('En Python: ') valor = arkmslib.unvalor() print('En Python:arkmslib.unvalor() regreso: ', valor) arkmslib.imprimir(valor*10)
El código en el lado de C++ primero debemos definir las funciones que serán llamados desde python, estos requieren recibir 2 parámetros ‘(PyObject* self, PyObject* args)’ donde self indica la misma función y args son los argumentos que se reciben desde Python (sin importar cuantos sean, solo va 1 vez).
Luego debemos crear una estructura que permitirá ser el puente entre lo que se tendrá que escribir en python y que representa en C++, esto antes de iniciar python indicarle que existe una nueva librería llamada ‘arkmslib’ y tiene 2 funciones, la ‘unvalor()’ y ‘imprimir’.
Por ultimo ya solo necesitamos cargar el archivo de Python y ejecutarlo.
static PyObject* arkmslib_unvalor(PyObject* self, PyObject* args) { std::cout << "En C++ se llamo unvalor()" << std::endl; return PyLong_FromLong(51); } //Declaro la funcion show static PyObject* arkmslib_imprimir(PyObject* self, PyObject* args) { PyObject *a; //Ayuda a parsear parametros de Python a C++ //args, "", numero de argumentos minimo, numero de argumentos maximo if (PyArg_UnpackTuple(args, "", 1, 1, &amp;a)) { std::cout << "C++ imprimir(" << PyLong_AsLong(a) << ")" << std::endl; } return PyLong_FromLong(0); } //Definición de metodos------------------------------------ static struct PyMethodDef metodos[] = { //Nombre, funcion en C, METH_VARARGS, Comentario { "unvalor", arkmslib_unvalor, METH_VARARGS, "Regresa un numero" }, { "imprimir", arkmslib_imprimir, METH_VARARGS, "Imprime un numero" }, { NULL, NULL, 0, NULL } //Terminamos de definir }; static struct PyModuleDef modDef = { //Definimos que la libreria 'arkmslib' tiene las funciones definidas arriba PyModuleDef_HEAD_INIT, "arkmslib", NULL, -1, metodos, NULL, NULL, NULL, NULL }; static PyObject* Iniciar_arkmslib(void) { return PyModule_Create(&amp;modDef); } int main() { PyImport_AppendInittab("arkmslib", &amp;Iniciar_arkmslib); //ESTO VA ANTES DE: Py_Initialize(); Py_Initialize(); const char pFile[] = "EjemploPython3.py"; FILE* fp = _Py_fopen(pFile, "r"); //Ejecutamos PyRun_AnyFile(fp, pFile); Py_Finalize(); system("Pause"); return 0; }
Con esto termino este tutorial de como agregar Python a un proyecto en C++, si hay errores o dudas puedes comentar, esto me ayuda a mejorar este articulo.
Hola,
Gracias por tus aportaciones.
He instalado Pyton 3.7 como dices. Uso Borland C++ Builder 6 y he añadido el include y el Lib pero al compilar me da el error siguiente:
[C++ Error] pyport.h(6): E2209 Unable to open include file ‘inttypes.h’
Además de otros errores que creo que vienen debido a que no encuentra este archivo.
¿Alguna idea para solucionarlo?
Gracias
Hola Rodolfo, Borland C++ usa otro tipo de compilador a mi ejemplo, pero debería poderse solucionar agregando ‘#include‘
Hola, he estado intentando abrir una ventana de Tkinter, lo hace todo bien solo que no abre esta, que puedo usar para que sirva??
Hola, intentaba seguir esta demostración para un proyecto, pero no logro pasar de las primeras cosa, como los includes.
¿Podrías ayudarme?
Buen día,
me genera el siguiente error en los 3 imp, initialize, py_run y finalize
mi proyecto ya tiene las carpetas mencionadas y el .dll que se llama python36_d.dll.
error referencia:
«generacde.cpp:(.text+0x10): undefined reference to `__imp_Py_Initialize»
El error undefined reference se refiere que falta agregar las librerías, los lib, es decir el ultimo paso.
Hola,
he conseguido llamar a un modulo complejo de python desde C++, el problema que veo es que si el Python da una excepción el C++ me da un segmentation fault. Hay alguna manera de controlar que python termine con éxito?
Gracias por adelantado.
De lado de C++ puedes usar un
try {
// Llama a python
}
catch (…) { // los 3 puntos es para que sin importar el tipo de error, lo atrape
// Python fallo, pero ya no detiene a C++
}
De ahí, revisa que está ocurriendo en Python para arreglar el problema, pero con esto no debería detener a C++
Excelente idea, muchas gracias!!