Search code examples
pythonc++python-c-api

python works in c++ in debug mode, but not in exe file


I am working on a Python code embedding in c++ helloworld program, necessary additional include/library directories are properly set up.

When I use Local Windows Debugger, it shows "Hello World" correctly. But if I double click project.exe, it says project.exe has stopped working.

Does anyone know what kind of configurations or steps to make so that project.exe shows "Hello World" when double clicked??

Code goes like the following:

main.cpp

#include <iostream>
#include <Python.h> 
#include <string.h>
#include <stdlib.h>
using namespace std;
int main() 
{ 
    Py_Initialize(); 
    PyRun_SimpleString("import sys");   
    PyRun_SimpleString("sys.path.append('./')");
    PyObject *pModule = PyImport_ImportModule("helloworld");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "printHello");
    PyEval_CallObject(pFunc, NULL);
    Py_Finalize();
    return 0;
}

helloworld.py

def printHello():
   print("Hello World!")

Solution

  • Shot in the dark:

    • You're not checking the return values for your calls, specially the ones returning pointers
    • running with the debugger isn't using the same directory as the "exe-clicking" method

    you should check the return value of pModule. My guess is that since you're in a different directory, the import fails, so the PyImport_ImportModule function returns NULL (python raises exceptions, but not in this context as this is a C API with its limitations)

    This is fragile (and possibly useless):

    sys.path.append('./')
    

    You don't know what the current directory is. It would be better to make it relative to the current executable, or configurable with an argument or an environment variable. You could make it relative to the current executable, see Finding current executable's path without /proc/self/exe or Get path of executable

    Now, when you try to use this null pointer, the program crashes. Start with this:

    PyObject *pModule = PyImport_ImportModule("helloworld");
    if (pModule == NULL)
    {
       std::cout << "could not import module\n";
       exit(1);
    }
    

    (same goes for the attribute fetch: always protect your calls, or better: wrap them in a C++ method that throws exceptions).

    #include <string>
    #include <stdexcept>
    
    PyObject *safe_PyImport_ImportModule(const std::string &module_name)
    {
       PyObject *pModule = PyImport_ImportModule(module_name.c_str());
       if (pModule == NULL) // c++11 purists would put "nullptr"
       {
           std::cout << "cannot import " << module_name << '\n';
           throw std::runtime_error("Import error: "+module_name);
       }
       return pModule;
    }