exodehm
exodehm

Reputation: 581

How to avoid Segmentation fault when calling Python scripts in Qt project

I'm trying to call a function in Python under C++.

This is my attempt:

void myFuncion()
{
    PyObject* fExportar = nullptr;
    PyObject* modulo = nullptr;
    PyObject* pName = nullptr;
    const char *scriptDirectoryName = "path/of/my/pyfile";
    Py_Initialize();
    PyObject *sysPath = PySys_GetObject("path");
    PyObject *path = PyUnicode_FromString(scriptDirectoryName);
    int result = PyList_Insert(sysPath, 0, path);
    if (result == 0 )//0 if ok, -1 if error
    {
        pName = PyUnicode_FromString("exportarXLS");//exportarXLS.py
        modulo = PyImport_Import(pName);
        Py_DECREF(path);
        if (modulo)
        {           
                fExportar = PyObject_GetAttrString(modulo, "exportar");//it crahs here
                Py_DECREF(modulo);                
                if (fExportar)
                {
                     //call the function
                }
            }        
    }
    else
    {
        PyErr_Print();
    }
    Py_Finalize();
}
}

The problem is that my C++ program crashes if the python script has any wrong import. In this case, I suspect that I trying to use an invalid version of PyQt4.

This is the module: (exportarXLS.py)

#!/usr/bin/python3

from PyQt4 import QtCore, QtGui, QtSql

def exportar():
    print ("hello, I am probing")

Now my question:

Now I am beggining to probe the steps for develop a function for load python plugings, and I wonder how would I avoid the crash if someone want to add a script with wrong import

I have try to enclose the troubled line in a try/catch block too, but it doesn't work.

EDIT:

I forgot to say that it's only happens into my Qt Project If I try to run my function out of this I can get errors loading modules but it doesn't crash. I've edited the tittle

Upvotes: 0

Views: 492

Answers (3)

NicoLazz
NicoLazz

Reputation: 164

This problem sounds familiar and I even can sort of reproduce it with your code:

If you call myFunction() more than once, what happens is that you import your modules more than once. According to the Docs this might cause issues:

"Some extensions may not work properly if their initialization routine is called more than once; this can happen if an application calls Py_Initialize() and Py_Finalize() more than once." https://docs.python.org/2/c-api/init.html

So if that is the case in your app, a workaround is to initialize the Python Interpreter only once:

#include <iostream>
#include <Python/Python.h>

void myFuncion()
{
    PyObject* fExportar = nullptr;
    PyObject* modulo = nullptr;
    PyObject* pName = nullptr;
    const char *scriptDirectoryName = "path/of/my/pyfile";

    PyObject *sysPath = PySys_GetObject("path");
    PyObject *path = PyUnicode_FromString(scriptDirectoryName);
    int result = PyList_Insert(sysPath, 0, path);
    if (result == 0 )//0 if ok, -1 if error
    {
        pName = PyUnicode_FromString("exportarXLS");//exportarXLS.py
        modulo = PyImport_Import(pName);
        Py_DECREF(path);
        if (modulo)
        {           
                fExportar = PyObject_GetAttrString(modulo, "exportar");//it crahs here
                Py_DECREF(modulo);                
                if (fExportar)
                {
                     //call the function
                }
            }        
    }
    else
    {
        PyErr_Print();
    }

}

int main(int argc, const char * argv[]) {

    Py_Initialize();

    myFuncion();
    myFuncion();

    // what ever

    Py_Finalize();

    return 0;
}

EDIT: "I even can sort of reproduce it" meaning I can get it to crash, on a different line, though, by importing numpy.

Upvotes: 1

Anya Shenanigans
Anya Shenanigans

Reputation: 94614

It would be best to deal with the problem by finding out the underlying cause of the SEGV as the state of your application could be seriously damaged by it being triggered.

If you want to attempt to catch the SEGV in a semi-structured manner then you can use something like the sample code, which uses sigsetjmp and siglongjmp:

#include <python3.7m/Python.h> // That's my python
#include <setjmp.h>
#include <signal.h>

static sigjmp_buf env;

static void
catch_segv(int func)
{
    siglongjmp(env, 1);
}

int myFunction()
{
    PyObject* fExportar = nullptr;
    PyObject* modulo = nullptr;
    PyObject* pName = nullptr;
    const char *scriptDirectoryName = "."; // NOTE: I changed the path for me
    Py_InitializeEx(1); // NOTE: skip signal handlers being registered - for embedding
    PyObject *sysPath = PySys_GetObject("path");
    PyObject *path = PyUnicode_FromString(scriptDirectoryName);
    int result = PyList_Insert(sysPath, 0, path);
    if (result == 0 )//0 if ok, -1 if error
    {
        pName = PyUnicode_FromString("beep");//exportarXLS.py
        modulo = PyImport_Import(pName);
        Py_DECREF(path);
        if (modulo)
        {
            // redirect segv handler here:
            sig_t old = signal(SIGSEGV, catch_segv);
            // record an environment to return to with siglongjmp
            if (sigsetjmp(env, 1)) { // returns 0 on setting up, 1 when called with siglongjmp(env, 1)
                // handler called
                Py_Finalize();
                signal(SIGSEGV, old); // restore old handler
                return 1; // return to caller
            } else {
                // this triggers a segv (for the test)
                (reinterpret_cast<sig_t>(0))(1);
                fExportar = PyObject_GetAttrString(modulo, "beep");//it crahs here
                Py_DECREF(modulo);
                if (fExportar)
                {
                     //call the function
                }
            }
            signal(SIGSEGV, old); // restore old handler
        }
    }
    else
    {
        PyErr_Print();
    }
    Py_Finalize();
    return 0; // return success.
}

int main(int argc, char **argv)
{
    return myFunction();
}

Upvotes: 1

Martin Morterol
Martin Morterol

Reputation: 2870

Duck tape solution :

void signal_handler(int signal)
{
  std::cout << "Usefull information" std::endl; 
  exit(1);
}
...
std::signal(SIGSEGV, signal_handler);

Doc : https://en.cppreference.com/w/cpp/utility/program/signal

I think this kind of solution should be used only in debug

Upvotes: 1

Related Questions