|
工作中遇到qtc++调用我的python代码,并且想要一键打包,这里我根据参考的以及个人实践的结果来简单实现一下。环境:windows系统,QTCreater4.5,python3.8(anaconda虚拟环境)1.简单QT调用python程序1.创建QT工程中间省略3个步骤图。创建完成后,如图。首先提示各位从python过来的同仁,QT中有时候对项目“重新构建”,项目并不真正的重新构建,如果这样的话,我们需要在工程文件夹下找到对应的构建后的项目,即比较长的这个(对应的是debug模式下的编译构建),删除掉,再点击重新构建。2.配置python环境使用QT调用python需要加载Python.h头文件,我们在Headers/mainwindow.h里面引入Python.h。但原始配置是找不到Python.h的,所以首先我们需要将安装好的python路径配置到QT的配置文件(.pro)中。 打开(项目名.pro)文件,按照如下格式填写。这里我将一个python环境的DLLs,include,Lib,libs和python3.dll,python38.dll以及vcruntime.dll复制过来,为该项目单独做个python环境。(参考在QTC++中调用Python并将软件打包发布(裸机可运行)_互联网集市)我是创建一个python_38的python环境,拷贝了miniconda3/envs/cat虚拟环境中的DLLs,include,Lib,libs和python3.dll,python38.dll以及vcruntime140.dll(这个python环境要能够支撑后面的python代码的运行,就是在原来的虚拟环境中,下面的python代码也可以执行的)INCLUDEPATH+=-ID:\output\envs\python_38\include#python.hLIBS+=-LD:\output\envs\python_38\libs-lpython38#python38.lib其中 INCLUDEPATH里面配置的是python.h的路径,LIBS配置的是python38.lib的路径。(参考Qt调用Python详细图文过程记录_python_脚本之家)问题1:出现C2059错误解决办法:在object.h中把slots改成slots1。Python将slots作为变量,而Qt将slots作为关键字,所以冲突了,再次编译该问题就没有了(参考Qt调用Python详细图文过程记录_python_脚本之家)问题2 如果出现找不到python38_d.lib是因为系统默认我们采用的是debug模式编译的(图片左下角所示)我们可以1在D:\output\envs\python_38\libs复制python38.lib,粘贴成python38_d.lib2将编译模式修改成release模式。以下操作在release模式进行再次编译,如果不报错,则表示编译成功,点击运行,出现弹窗。为了方便调试我们的程序是否成功,我们在mainwindow.h中加入QDebug然后再mainwindow.cpp中编写如下,运行。(参考C++调用python脚本-知乎)#include"mainwindow.h"#include"ui_mainwindow.h"MainWindow::MainWindow(QWidget*parent)MainWindow(parent),ui(newUi::MainWindow){ui->setupUi(this);//初始化python解释器.C/C++中调用Python之前必须先初始化解释器Py_Initialize();//判断python解析器的是否已经初始化完成if(!Py_IsInitialized())qDebug()<<"[db:] Py_Initialize fail"; else qDebug()<<"[db:]>setupUi(this);//初始化python解释器.C/C++中调用Python之前必须先初始化解释器Py_Initialize();//判断python解析器的是否已经初始化完成if(!Py_IsInitialized())qDebug()<<"[db:] Py_Initialize fail"; else qDebug()<<"[db:] Py_Initialize success"; // 执行 python 语句 PyRun_SimpleString("print('hello world') "); // 导入sys模块设置模块地址,以及python脚本路径 PyRun_SimpleString("import sys"); // 该相对路径是以build...为参考的 PyRun_SimpleString("sys.path.append('../py_scripts')"); // 加载 python 脚本 PyObject *pModule = PyImport_ImportModule("py_test"); // 脚本名称,不带.py if(!pModule) // 脚本加载成功与否 qDebug()<<"[db:] pModule fail"; else qDebug()<<"[db:] pModule success"; // 创建函数指针 PyObject* pFunc= PyObject_GetAttrString(pModule,"write_file"); // 方法名称 if(!pFunc || !PyCallable_Check(pFunc)) // 函数是否创建成功 qDebug()<<"[db:] pFunc fail"; else qDebug()<<"[db:]>setupUi(this);//初始化python解释器.C/C++中调用Python之前必须先初始化解释器Py_Initialize();//判断python解析器的是否已经初始化完成if(!Py_IsInitialized())qDebug()<<"[db:] Py_Initialize fail"; else qDebug()<<"[db:] Py_Initialize success"; // 执行 python 语句 PyRun_SimpleString("print('hello world') "); // 导入sys模块设置模块地址,以及python脚本路径 PyRun_SimpleString("import sys"); // 该相对路径是以build...为参考的 PyRun_SimpleString("sys.path.append('../py_scripts')"); // 加载 python 脚本 PyObject *pModule = PyImport_ImportModule("py_test"); // 脚本名称,不带.py if(!pModule) // 脚本加载成功与否 qDebug()<<"[db:] pModule fail"; else qDebug()<<"[db:] pModule success"; // 创建函数指针,有参调用 PyObject* pFunc= PyObject_GetAttrString(pModule, "process_data"); // 有参调用的 // 定义一个随机器 QRandomGenerator generator; // 创建一个定长元组,用来存放传入参数 PyObject* pyArgs = PyTuple_New(20); // 每个元组类似于结构体,包含字符串,整型和浮点类型数据 // 填充元组 for (int i = 0; i < 20; ++i) { PyObject* pyTuple = PyTuple_New(3); //元组由三部分组成 // 组合下字符串 QString qst = "test string " + QString::number(i); QByteArray baq = qst.toLatin1(); PyTuple_SetItem(pyTuple, 0, Py_BuildValue("s", baq.data())); // 字符串 PyTuple_SetItem(pyTuple, 1, Py_BuildValue("i", generator.generate() % 100)); // 整型 PyTuple_SetItem(pyTuple, 2, Py_BuildValue("f", 3.14f)); // 浮点型 PyTuple_SetItem(pyArgs, i, pyTuple); // 将结构体填充到列表中 } // 调用python函数 PyObject* pyResult = PyObject_CallObject(pFunc, pyArgs); int list_len = PyObject_Size(pyResult);// 计算返回过来的列表长度 qDebug() << list_len; // 判单是否成功 if (pyResult == NULL) { PyErr_Print(); } else { // 解析返回值 for (int i = 0; i < 20; ++i) { // 已知列表长度有20个,预先不知道的话就使用上面定义的list_len PyObject* pyTuple = PyList_GetItem(pyResult, i); QString strVal = QString::fromUtf8(PyUnicode_AsUTF8(PyList_GetItem(pyTuple, 0))); int intVal = PyLong_AsLong(PyList_GetItem(pyTuple, 1)); double floatVal = PyFloat_AsDouble(PyList_GetItem(pyTuple, 2)); qDebug() << strVal << intVal << floatVal; // 打印 } } // 清理Python变量 Py_DECREF(pyArgs); Py_DECREF(pFunc); Py_DECREF(pModule); Py_DECREF(pyResult); // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。 Py_Finalize(); } MainWindow::~MainWindow(){ delete ui;}
python源码如下,文件名称仍然是 py_test.py
def process_data(*args): result = [] f = open("b.txt", "w") for arg in args: # 从元组中读取数据 strVal, intVal, floatVal = arg # 按顺序一一对应取数据 f.write(strVal + " " + str(intVal) + '\n') # 写文档 # process the data processed_strVal = strVal.upper() processed_intVal = intVal + 1 processed_floatVal = floatVal ** 2 sub_result = [processed_strVal, processed_intVal, processed_floatVal] result.append(sub_result) # 按列表格式返回数据 f.close() return result'运行运行
---------------------------------------------------------------------------------------------------------------------------
具体修改内容是
1.创建对有参函数的调用,和一个定长元组,用来存放传入参数,中间还有个随机生成器
// 创建函数指针,有参调用 PyObject* pFunc= PyObject_GetAttrString(pModule, "process_data"); // 有参调用的 // 定义一个随机器 QRandomGenerator generator; // 创建一个定长元组,用来存放传入参数 PyObject* pyArgs = PyTuple_New(20);
2.填充元组数据
每个元组类似于结构体,包含字符串,整型和浮点类型数据
for (int i = 0; i < 20; ++i) { PyObject* pyTuple = PyTuple_New(3); //元组由三部分组成 // 组合下字符串 QString qst = "test string " + QString::number(i); QByteArray baq = qst.toLatin1(); PyTuple_SetItem(pyTuple, 0, Py_BuildValue("s", baq.data())); // 字符串 PyTuple_SetItem(pyTuple, 1, Py_BuildValue("i", generator.generate() % 100)); // 整型 PyTuple_SetItem(pyTuple, 2, Py_BuildValue("f", 3.14f)); // 浮点型 PyTuple_SetItem(pyArgs, i, pyTuple); // 将结构体填充到列表中 }
3.对mainwindow.h的修改
因为用到了QRandomGenerator ,所以在mainwindow.h中引入#include 头文件#include#include"Python.h"#include#include 4.调用python函数,并输出使用返回值注意我们传参的时候是使用元组(tuple),返回的时候使用的列表(list),这个见python代码//调用python函数PyObject*pyResult=PyObject_CallObject(pFunc,pyArgs);intlist_len=PyObject_Size(pyResult);//计算返回过来的列表长度qDebug()<< list_len; // 判单是否成功 if (pyResult == NULL) { PyErr_Print(); } else { // 解析返回值 for (int i = 0; i < 20; ++i) { // 已知列表长度有20个,预先不知道的话就使用上面定义的list_len PyObject* pyTuple = PyList_GetItem(pyResult, i); QString strVal = QString::fromUtf8(PyUnicode_AsUTF8(PyList_GetItem(pyTuple, 0))); int intVal = PyLong_AsLong(PyList_GetItem(pyTuple, 1)); double floatVal = PyFloat_AsDouble(PyList_GetItem(pyTuple, 2)); qDebug() << strVal << intVal << floatVal; // 打印 } }
5.python代码的修改
仍然使用py_test文件,在文件中定义process_data函数。读取tuple内容,将结构体用list包装,并使用list 返回内容如下:
def process_data(*args): result = [] f = open("b.txt", "w") for arg in args: # 从元组中读取数据 strVal, intVal, floatVal = arg # 按顺序一一对应取数据 f.write(strVal + " " + str(intVal) + '\n') # 写文档 # process the data processed_strVal = strVal.upper() processed_intVal = intVal + 1 processed_floatVal = floatVal ** 2 sub_result = [processed_strVal, processed_intVal, processed_floatVal] result.append(sub_result) # 按列表格式返回数据 f.close() return result
执行结果是会在build-simple_test-Desktop_Qt_5_10_0_MSVC2015_64bit-Release文件夹下生成一个b.txt文件,并且qt端输出内容。
3. 打包部署
以上我们已经实现QT C++调用python的程序,现在我们要将项目部署在一个没有python环境下的机器上,QT打包发布成exe执行的。对代码的改动不多,对文件夹的修改移动比较多,注意一点。
由于.pro只修改一行,这里只附上mainwindow.cpp的源码
#include "mainwindow.h"#include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this);//设置python路径Py_SetPythonHome((wchar_t*)(L"./python_38"));//相对位置以exe为参考//初始化python解释器.C/C++中调用Python之前必须先初始化解释器Py_Initialize();//判断python解析器的是否已经初始化完成if(!Py_IsInitialized())qDebug()<<"[db:] Py_Initialize fail"; else qDebug()<<"[db:] Py_Initialize success"; // 执行 python 语句 PyRun_SimpleString("print('hello world') "); // 导入sys模块设置模块地址,以及python脚本路径 PyRun_SimpleString("import sys"); // 该相对路径是以build...为参考的 PyRun_SimpleString("sys.path.append('./py_scripts')"); //以exe为参考位置 // 加载 python 脚本 PyObject *pModule = PyImport_ImportModule("py_test"); // 脚本名称,不带.py if(!pModule) // 脚本加载成功与否 qDebug()<<"[db:] pModule fail"; else qDebug()<<"[db:] pModule success"; // 创建函数指针,有参调用 PyObject* pFunc= PyObject_GetAttrString(pModule, "process_data"); // 有参调用的 // 定义一个随机器 QRandomGenerator generator; // 创建一个定长元组,用来存放传入参数 PyObject* pyArgs = PyTuple_New(20); // 每个元组类似于结构体,包含字符串,整型和浮点类型数据 // 填充元组 for (int i = 0; i < 20; ++i) { PyObject* pyTuple = PyTuple_New(3); //元组由三部分组成 // 组合下字符串 QString qst = "test string " + QString::number(i); QByteArray baq = qst.toLatin1(); PyTuple_SetItem(pyTuple, 0, Py_BuildValue("s", baq.data())); // 字符串 PyTuple_SetItem(pyTuple, 1, Py_BuildValue("i", generator.generate() % 100)); // 整型 PyTuple_SetItem(pyTuple, 2, Py_BuildValue("f", 3.14f)); // 浮点型 PyTuple_SetItem(pyArgs, i, pyTuple); // 将结构体填充到列表中 } // 调用python函数 PyObject* pyResult = PyObject_CallObject(pFunc, pyArgs); int list_len = PyObject_Size(pyResult);// 计算返回过来的列表长度 qDebug() << list_len; // 判单是否成功 if (pyResult == NULL) { PyErr_Print(); } else { // 解析返回值 for (int i = 0; i < 20; ++i) { // 已知列表长度有20个,预先不知道的话就使用上面定义的list_len PyObject* pyTuple = PyList_GetItem(pyResult, i); QString strVal = QString::fromUtf8(PyUnicode_AsUTF8(PyList_GetItem(pyTuple, 0))); int intVal = PyLong_AsLong(PyList_GetItem(pyTuple, 1)); double floatVal = PyFloat_AsDouble(PyList_GetItem(pyTuple, 2)); qDebug() << strVal << intVal << floatVal; // 打印 } } // 清理Python变量 Py_DECREF(pyArgs); Py_DECREF(pFunc); Py_DECREF(pModule); Py_DECREF(pyResult); // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。 Py_Finalize(); } MainWindow::~MainWindow(){ delete ui;}
具体修改如下:
1. 修改编译输出目录(生成exe的目录),到 qt_output
在项目.pro中添加
DESTDIR = $PWD/../qt_output
与python路径合在一起展示如下。
FORMS += \ mainwindow.ui DESTDIR = $PWD/../qt_output INCLUDEPATH += -I D:\output\envs\python_38\include # python.hLIBS += -LD:\output\envs\python_38\libs -lpython38 # python38.lib
编译后结果如下 ,qt_output中只有exe文件,可知这个PWD的路径是以build-simple_test-Desktop_Qt_5_10_0_MSVC2015_64bit-Release为参考的
2.将python 环境拷贝到qt_output目录下
即将python_38文件夹复制到qt_output目录下。
3.在QT C++ 中指定python 库地址
在初始化之前,添加
Py_SetPythonHome((wchar_t*)(L"./python_38")); // 相对位置以exe为参考
ui->setupUi(this);//设置python路径Py_SetPythonHome((wchar_t*)(L"./python_38"));//相对位置以exe为参考//初始化python解释器.C/C++中调用Python之前必须先初始化解释器Py_Initialize();4.将python脚本文件移入qt_output文件夹中,并修改相对路径//导入sys模块设置模块地址,以及python脚本路径PyRun_SimpleString("importsys");//该相对路径是以build...为参考的PyRun_SimpleString("sys.path.append('./py_scripts')");//以exe为参考位置//加载python脚本PyObject*pModule=PyImport_ImportModule("py_test");//脚本名称,不带.py2,3,4,步执行完成后,文件夹中内容如下。程序中运行,会在exe同文件夹下生成b.txt在文件夹中,点击exe文件直接运行,会出现找不到Qt5Core.dll和Qt5Widgets.dll错误。这就用到windeployqt命令了,5.开始键输入找到如图的客户端,打开后输入windeployqtD:\workspace\qt\qt_output\simple_test.exe具体内容根据项目路径来写,运行完成后会在qt_output文件夹中生成程序运行所需要的依赖包(具体叫啥不知道,这个是qt的东西)。qt_output文件夹内除了以前的这些文件外,又多了些文件夹和文件(依赖库) 。再点击simple_test.exe,则出现QT的弹窗,并且生成新的b.txt文件。6.经验证,还需要将python_38文件夹里面的python38.dll文件移到外面来,放在和simple.exe同一级别。4.python中带有第三方包的部署(忘了参考哪个了,主要是找不到参考的那个网页了)我们首先修改下py_test.py的内容,引入numpy包,因为numpy是第三方的包。修改如下:importnumpyasnpdefwrite_file():withopen("a.txt","w")asf:f.write("test")defprocess_data(*args):result=[]f=open("b.txt","w")forarginargs:#从元组中读取数据strVal,intVal,floatVal=arg#按顺序一一对应取数据f.write(strVal+""+str(intVal)+'\n')#写文档#processthedataprocessed_strVal=strVal.upper()processed_intVal=intVal+1processed_floatVal=floatVal**2sub_result=[processed_strVal,processed_intVal,processed_floatVal]result.append(sub_result)#按列表格式返回数据f.close()arr=np.array(result)returnresult 在返回之前,生成一个并不使用的变量arr=np.array(result),这个我们就是为了测试第三方包而做的,生成的arr没有任何意义。再次点击simple_test.exe不会生成b.txt,表示python脚本程序运行错误,第三方包调用失败。解决方法1.使用pyinstaller生成依赖文件这个就需要我们python的一个包了,需要pip(conda)安装pyinstaller。因为我这里供QTC++使用的python环境(D:\workspace\qt\qt_output\python_38)是从D:\miniconda3\envs\cat虚拟环境中复制出来的部分,所以我使用的是激活cat的虚拟环境,并再这里面执行pyinstaller,生成依赖文件。(cat)PSD:\tmp>condaactivatecat(cat)PSD:\tmp>cdd:/tmp(cat)PSD:\tmp>pyinstallerD:\workspace\qt\qt_output\py_scripts\py_test.py1.激活环境,2.生成的依赖在那个文件夹中(随便写的一个文件夹),3.对那个python文件生成依赖执行完成之后,会在d:/tmp中生成两个文件夹,dist和build 2.我们将dist/py_test/_internal 中的所有文件(夹)全部复制到QT编译生成的qt_output文件夹中将得到依赖包的qt_output文件夹放在在新机器上部署执行带有第三方包就没有问题了经本人测试包括cv2包也可以,但是本人对matlablib这个包没有导入成功。其他说明1.qt生成的文件一定要注意他们的相对位置,是相对于哪一个文件的位置。2.QTC++调用python后,我没有能够进行调试,不知道是什么原因不能调试。3.python和C++的数据类型,有些QTC++数据类型传到python中不好使用,主要我对QT和C++不了解。4.带有第三方打包的就比普通打包部署多一步,这一步需要pyinstaller生成动态链接库等文件,复制进和QTC++生成的exe同一个文件夹中。参考网页Qt调用Python详细图文过程记录_python_脚本之家在QTC++中调用Python并将软件打包发布(裸机可运行)_互联网集市C++调用python脚本-知乎如何在C++中使用一个Python类-[PyImport_ImportModule、PyModule_GetDict、PyDict_GetItemString、PyObject_CallFuncti]-CSDN博客Qt项目中C++调用Python函数传多参问题_qt调用python_平头猿小哥的博客-CSDN博客C++调用Python(混合编程)函数整理总结_jindayue的博客-CSDN博客PyObject_CallObject,PyObject_Call,PyObject_CallFunction使用例子-CSDN博客QtC++Python混合编程测试文档-知乎
|
|