【问题标题】:How to avoid Segmentation fault when calling Python scripts in Qt projectQt项目中调用Python脚本时如何避免Segmentation fault
【发布时间】:2019-08-19 01:09:30
【问题描述】:

我正在尝试在 C++ 下调用 Python 中的函数。

这是我的尝试:

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();
}
}

问题是如果 python 脚本有任何错误import,我的 C++ 程序就会崩溃。在这种情况下,我怀疑我尝试使用无效版本的 PyQt4。

这是模块:(exportarXLS.py)

#!/usr/bin/python3

from PyQt4 import QtCore, QtGui, QtSql

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

现在我的问题:

现在我开始探索开发用于加载 python 插件的函数的步骤,我想知道如果有人想添加错误的脚本import,我将如何避免崩溃

我也尝试将有问题的行包含在 try/catch 块中,但它不起作用。

编辑:

我忘了说它只发生在我的 Qt 项目中 如果我尝试从中运行我的功能,我会在加载模块时遇到错误,但它不会崩溃。 我已经修改了标题

【问题讨论】:

  • 无法为此重现崩溃。如果模块无法加载,我不会崩溃,我只会得到一个 NULL。管道胶带解决方法在这里并没有真正的帮助,因为它很可能是一个环境问题,它触发了您需要找到根源的问题。您可以尝试使用环境变量 PYTHONVERBOSE 来查看通过 C++ 加载和直接从 python3 加载之间的区别,看看是否有区别。我会给你一个“如果发生 SEGV”的选项,但你需要确定它的原因。

标签: python c++ pyqt


【解决方案1】:

鸭带解决方案:

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

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

我认为这种解决方案应该只在调试时使用

【讨论】:

    【解决方案2】:

    最好通过找出 SEGV 的根本原因来解决问题,因为您的应用程序的状态可能会因触发它而受到严重破坏。

    如果您想尝试以半结构化方式捕获 SEGV,那么您可以使用示例代码之类的东西,它使用了sigsetjmpsiglongjmp

    #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();
    }
    

    【讨论】:

    • 对不起。我编辑了我的问题,因为它仅在我在 Qt 项目下运行该函数时崩溃。在你的代码下运行,如果一切正常,我得到 1,如果有任何错误,我得到 0,但永远不会崩溃。但只有当我像一个孤立的项目一样运行该代码时(我已经在 CodeBlocks 下尝试过),但如果我将你的代码调整为我的 Qt 项目下的函数,它也会崩溃。对不起,因为我忘了说我在 Qt 项目下,看起来很重要
    • 这极大地改变了事情。据我了解,PyQt 将它自己的 Qt 库副本嵌入到自身内部。您还拥有应用程序中的 Qt 库的副本。这不太可能奏效。如果直接运行应用程序,请检查命令行的输出(application.app/Contents/MacOS/application 直接运行它)。
    • 我建议从PyQt 切换到PySide2,它应该使用与系统上相同的Qt。这应该避免崩溃问题
    【解决方案3】:

    这个问题听起来很熟悉,我什至可以用你的代码重现它:

    如果您多次调用 myFunction(),则会发生多次导入模块。根据文档,这可能会导致问题:

    "如果某些扩展程序的初始化例程可能无法正常工作 被多次调用;如果应用程序调用,这可能会发生 Py_Initialize() 和 Py_Finalize() 不止一次。” https://docs.python.org/2/c-api/init.html

    因此,如果您的应用中出现这种情况,解决方法是只初始化 Python 解释器一次:

    #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;
    }
    

    编辑:“我什至可以重现它”意味着我可以通过导入 numpy 让它在不同的行上崩溃。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多