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释放资源导致。去掉相关资源释放后反而正常了。还是要以实际效果为准
参考资料
https://zhuanlan.zhihu.com/p/149887203
https://blog.csdn.net/iamqianrenzhan/article/details/86516440