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 &lt;&lt; "En C++ se llamo unvalor()" &lt;&lt; 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;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;amp;modDef);
}

int main()
{
 PyImport_AppendInittab("arkmslib", &amp;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.

9 Comentarios en “C++: Cargar y ejecutar Python (Scripting)

  1. 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

  2. Hola, he estado intentando abrir una ventana de Tkinter, lo hace todo bien solo que no abre esta, que puedo usar para que sirva??

  3. Hola, intentaba seguir esta demostración para un proyecto, pero no logro pasar de las primeras cosa, como los includes.
    ¿Podrías ayudarme?

  4. 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.

  5. 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++

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.