Qt下异步使用C++调用Python文件

484 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

Qt项目中使用到了C++异步调用Python,这里记录一下。

环境

C++ 14,Python 2.7 ,Qt5.4.2用CMake构建,Win10 64位

CMakeLists.txt:Python部分

# Python环境配置
find_package(Python2.7 COMPONENTS Interpreter Development REQUIRED)
include_directories(${PYTHON_INCLUDE_DIR})

C++ 体系复杂,且构建依赖环境,一定要注意各个环节版本是否一致。特别是系统64/32位,两种环境不能互通。

代码

C++头文件

class MyWindow : public QMainWindow
{
Q_OBJECT
public:
    explicit MyWindow(QWidget *parent = 0);
    //销毁资源
    virtual ~MyWindow();
    //异步调用方法需设置为static
    static QString callPython(QString selectedValue);
public slots:
    void onValueChanged();
    void onComboChanged(int idx);
private:
    //任务监听
    QFutureWatcher<QString> *myWatcher;
    QComboBox *myCombo;
    QString myValue;
}
  • QFutureWatcher<QString>:为任务监听,QString为异步任务返回值,全局初始化是为了方便获取执行结果及销毁资源,不全局申明可在onValueChanged方法中用下面的代码获取任务监听
QFutureWatcher\<QString\> *myWatcher = dynamic_cast<QFutureWatcher\<QString\> *\>(this->sender());

C++ CXX文件:

#include <Python.h>

MyWindow::MyWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MyWindow),
        m_Model(NULL) {
    ui->setupUi(this);
    myCombo = new QComboBox(this);
    //...初始化combobox
    //绑定comboBoxchange事件
    connect(myCombo, SIGNAL(currentIndexChanged(int)), SLOT(onComboChanged(int)));
    myWatcher = new QFutureWatcher<QString>(this);
    //监听任务结束事件
    connect(myWatcher, SIGNAL(finished()), this, SLOT(onValueChanged())); 
...
  }    
  
  MyWindow::~MyWindow() {
        //销毁资源
        myWatcher->cancel();
        myWatcher->waitForFinished();
        delete myWatcher;
    }
  
void MyWindow::onComboChanged(int idx) {
        QString selectedValue = myCombo->currentData().value<QString>();
        QFuture<QString> myFuture = QtConcurrent::run(MyWindow::callPython, selectedValue);
        cacWatcher->setFuture(myFuture);
    }

QString MyWindow::callPython(QString selectedValue){
            QString result = "";
            if(Py_IsInitialized() == 0){
                Py_Initialize();
            }
            QString filename = "py文件路径";
            QFileInfo filepath = QFileInfo(filename);
            QString path = filepath.absolutePath();
            PyObject *sys = PyImport_ImportModule("sys");
            PyObject *syspath = PyObject_GetAttrString(sys, "path");
            PyList_Insert(syspath, 0, PyString_FromFormat(path.toStdString().c_str()));
            filename = filepath.fileName().split(".")[0];
            PyObject *pName = PyString_FromString(filename.toStdString().c_str());
            PyObject *pModule = PyImport_Import(pName);
            if (pModule != NULL) {
                PyObject *pDict = PyModule_GetDict(pModule);
                PyObject *pFunc = PyDict_GetItem(pDict, PyString_FromString("函数名称"));
                if (pFunc != NULL) {
                    PyObject *pyParams = PyTuple_New(2);
                    PyTuple_SetItem(pyParams, 0, Py_BuildValue("s", selectedValue.toStdString().c_str()));

                    PyObject *pythonResult = PyObject_CallObject(pFunc, pyParams);
                    result = QString(PyString_AsString(pythonResult).c_str());

                } else {
                    msgBox.setText("can't find function");
                    msgBox.exec();
                }
            } else {
                msgBox.setText("can't find dir");
                msgBox.exec();
            }
        } catch (std::exception &err) {
            msgBox.setText(err.what());
            msgBox.exec();
        }
        return result;
}

    void MyWindow::onValueChanged() {
        this->myValue = myWatcher->result();
        // ...业务代码
    }

  • QFutureWatcher:为任务监听
  • QFuture:为异步任务,这里使用QtConcurrent来创建异步任务,也有其它模式,具体请参见官方文档。
  • Py_Initialize为初始化python环境,但在2.7时重复初始环境会导致系统崩溃,可使用Py_IsInitialized()判断是否已初始化过环境。
  • Py_DECREF:看说明是释放资源,但是调用函数第二次运行时也会崩溃,所以实际代码中没有使用。
  • Py_Finalize():同上述原因实际没有使用。
  • PyObject_CallObject:第二个参数为Python函数参数,可有多个,使用PyTuple包裹。

Python源码:

import os

def call_python(param):
    helloStr = "Hello in Python "+param;
    return helloStr;

代码运行过程中每第二次调用python程序就会崩溃,一度以为是QFutureWatcher的异步监听没有释放资源导致,修改为同步任务调试才发现是由于Py_Initialize二次初始化及Py_DECREF释放资源导致。去掉相关资源释放后反而正常了。还是要以实际效果为准

参考资料

zhuanlan.zhihu.com/p/149887203

blog.csdn.net/iamqianrenz…