文章
问答
冒泡
Qt 调用python 脚本并打包

业务中需要用opencv进行图像处理,视觉算法部分由其他团队处理,由于算法部分是用python编写的,我们需要将python集成到我们的Qt程序中。这里,记录下Qt调用Python踩坑的过程。
1. 在Qt中调用python文件
找到本机下python的安装路径,将include,libs,python3.dll,python310.dll拷贝到工程下


将头文件和lib加入到cmakelist中

include_directories("${PROJECT_SOURCE_DIR}/include/python" )
if(WIN32)
    link_directories("${PROJECT_SOURCE_DIR}/dependencies/windows-x86_64/libs")
endif()

set(PY_LIBS _tkinter python3 python310)

add_executable(MaterialSizeDetectionEdge ${PROJECT_SOURCES})

target_link_libraries(MaterialSizeDetectionEdge
        Qt::Core
        Qt::Gui
        Qt::Widgets
        ${PY_LIBS}
        ws2_32
        )

 

编写一个简单的python脚本,mytest.py
注意,这里的文件一定不能命名为test.py 会与python自带的test模块冲突

def hello():
    print("hello word")

 

qt调用python

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QCoreApplication>
#include <iostream>
#pragma push_macro("slots")
#undef slots
#include <Python.h>
#pragma pop_macro("slots")

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    qDebug() << QCoreApplication::applicationDirPath();
    Py_Initialize();
    if (!Py_IsInitialized()) {
        qDebug()<<" py init faild ";
      }
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./py')");
    PyObject* pModule = PyImport_ImportModule("mytest");
    PyObject* pFunhello= PyObject_GetAttrString(pModule,"hello");
    PyObject_CallFunction(pFunhello,NULL);
    Py_Finalize();
}


注意,这里需要先取消Qt 中对slots的宏定义

#pragma push_macro("slots")
#undef slots
#include <Python.h>
#pragma pop_macro("slots")

 

运行结果如下


2. 处理pyhon脚本中对第三方库的引用
大多数时候,我们不只是用python本身的函数,还需要用到很多第三方库,这个时候,我们可以把第三方库直接copy到工程下。
这里,我们用numpy为例,展示,在Qt中调用py脚本中依赖三方库

import numpy as np
def hello():
    l = np.ones([3, 3])
    print(l)

 

numpy库,我们就简单粗暴的用pycharm 下载,下载完成之后,把numpy文件夹复制到 工程中

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QCoreApplication>
#include <iostream>
#pragma push_macro("slots")
#undef slots
#include <Python.h>
#pragma pop_macro("slots")

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    qDebug() << QCoreApplication::applicationDirPath();
    Py_Initialize();
    if (!Py_IsInitialized()) {
        qDebug()<<" py init faild ";
      }
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");
    QString pyPackagesPath = "sys.path.append('"+QCoreApplication::applicationDirPath()+"/site-packages')";
    PyRun_SimpleString(pyPackagesPath.toLatin1().data());
    PyObject* pModule = PyImport_ImportModule("mytest");
    PyObject* pFunhello= PyObject_GetAttrString(pModule,"hello");
    PyObject_CallFunction(pFunhello,NULL);
    Py_Finalize();
}


这里需要把 site-packages文件夹复制到cmake路径下

运行结果如下

执行成功

3. 打包
我们期望的效果是,直接把应用解压就可以运行,而不需要再去安装其他的依赖。这样就需要把python环境一起打包到应用中。
在执行打包的时候,很多资料都说是需要把python目录下的dll 还有 libs打包成一个zip文件 ,其实这个是不对的。无论怎么尝试各种结构都会报如下错误

"D:/develop/workspace/xxx/material-size-detection/material-size-detection-edge/cmake-build-debug-visual-studio-2022"
Python path configuration:
  PYTHONHOME = (not set)
  PYTHONPATH = (not set)
  program name = 'python'
  isolated = 0
  environment = 1
  user site = 1
  import site = 1
  sys._base_executable = 'D:\\develop\\workspace\\xxx\\material-size-detection\\material-size-detection-edge\\cmake-bu
ild-debug-visual-studio-2022\\MaterialSizeDetectionEdge.exe'
  sys.base_prefix = ''
  sys.base_exec_prefix = ''
  sys.platlibdir = 'lib'
  sys.executable = 'D:\\develop\\workspace\\jscoe\\material-size-detection\\material-size-detection-edge\\cmake-build-de
bug-visual-studio-2022\\MaterialSizeDetectionEdge.exe'
  sys.prefix = ''
  sys.exec_prefix = ''
  sys.path = [
    'D:\\develop\\workspace\\xxx\\material-size-detection\\material-size-detection-edge\\cmake-build-debug-visual-stud
io-2022\\python310.zip',
    '.\\DLLs',
    '.\\lib',
    'D:\\develop\\workspace\\xxx\\material-size-detection\\material-size-detection-edge\\cmake-build-debug-visual-stud
io-2022',
  ]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'

Current thread 0x0000bdac (most recent call first):
  <no Python frame>


正确的做法是,找到官网对应的embeddable版本,也就是可嵌入版本
https://www.python.org/downloads/windows/

将这个3个文件复制到执行目录下即可


4. 优化
以上是把相关的功能实现了,下面需要把一些构建进行优化,打包的时候 ,可以不用手动去拷贝

cmake_minimum_required(VERSION 3.22)
project(MaterialSizeDetectionEdge)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

#set(CMAKE_PREFIX_PATH "D:/develop/program/Qt/6.3.1/mingw_64")
set(CMAKE_PREFIX_PATH "D:/develop/program/Qt/6.3.1/msvc2019_64")

set(CONF_FILES config.xml)
set(TS_FILES material-size-detection-edge_zh_CN.ts)
set(QRC_FILES resource.qrc)

set(PROJECT_SOURCES
        ${TS_FILES}
        src/main.cpp
        src/mainwindow.cpp
        src/mainwindow.h
        src/mainwindow.ui
)

find_package(Qt6 COMPONENTS
        Core
        Gui
        Widgets
        Xml
        REQUIRED)

include_directories("${PROJECT_SOURCE_DIR}/include/python" )
if(WIN32)
    link_directories("${PROJECT_SOURCE_DIR}/dependencies/windows-x86_64/libs")
endif()

set(PY_LIBS _tkinter python3 python310)

add_executable(MaterialSizeDetectionEdge ${PROJECT_SOURCES})

target_link_libraries(MaterialSizeDetectionEdge
        Qt::Core
        Qt::Gui
        Qt::Widgets
        Qt6::Xml

        ${PY_LIBS}
        ws2_32
        )

if (WIN32)
    set(DEBUG_SUFFIX)
    if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")
        set(DEBUG_SUFFIX "d")
    endif ()
    set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
    if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
        set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
        if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
            set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
        endif ()
    endif ()
    if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E make_directory
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy
                "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
    endif ()
    foreach (QT_LIB Core Gui Widgets Xml )
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy
                "${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll"
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
    endforeach (QT_LIB)

    file(GLOB DLL_LIST ${PROJECT_SOURCE_DIR}/dependencies/windows-x86_64/dlls/*.dll)
    foreach(DLL_FILE ${DLL_LIST})
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy
                "${DLL_FILE}"
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
    endforeach()

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/dependencies/windows-x86_64/site-packages"
            "$<TARGET_FILE_DIR:${PROJECT_NAME}>/site-packages")

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/py"
            "$<TARGET_FILE_DIR:${PROJECT_NAME}>/py")

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            "${PROJECT_SOURCE_DIR}/dependencies/windows-x86_64/python310.zip"
            "$<TARGET_FILE_DIR:${PROJECT_NAME}>")

endif ()

参考文档
https://blog.csdn.net/yulinxx/article/details/90231955
https://cloud.tencent.com/developer/ask/sof/367090

python
Qt

关于作者

落雁沙
非典型码农
获得点赞
文章被阅读