《CMake + Qt + Python:如何在 Qt的c 项目中嵌入 Python》简单示列

1.相关版本

anaconda(python 3.10.16)

        Anaconda 是一个开源的 Python 和 R 数据科学平台,广泛用于科学计算、数据分析、机器学习等领域。它提供了一个便捷的包管理和环境管理系统,可以帮助用户更轻松地安装、更新和管理 Python 库及其他依赖。

qt6(cmake+msvc)

        Qt6 是一个跨平台的 C++ 图形用户界面(GUI)库,广泛用于开发桌面和移动应用程序。Qt 提供了丰富的控件、图形、网络、数据库等模块,并且支持跨平台开发,允许开发者在 Windows、Linux、macOS 和其他平台上编写一次代码,然后运行在多种操作系统上。

pybind11(Version: 2.13.6)

        pybind11 是一个轻量级的库,它允许将 C++ 代码和 Python 代码进行高效的绑定。它的主要目的是通过简洁的 API 和强大的功能,帮助开发者将 C++ 库暴露给 Python 用户。通过 pybind11,开发者可以非常方便地将 C++ 的高性能代码与 Python 的灵活性和易用性结合起来。

2.简单介绍下相关创建或下载。

        1.创建anaconda环境:conda create -n myenv python=3.10

        2.qt creator创建项目时选择qcmake

        两个编译器可以都选择

运行项目时选择msvc

3. pybind11下载网址pybind/pybind11: Seamless operability between C++11 and Python (github.com)

下载解压,在qt项目下创建external文件,

将pybind11放入

保证pybind11文件夹下即是内容

3.代码示列

1.python代码

# script.py
def hello_from_python():
    print("Hello from Python!")

def add(a, b):
    return a + b

将其放入项目根目录中。

2.qt代码中的CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(ttt VERSION 0.1 LANGUAGES CXX)
# 如果不需要避免 Qt 定义 'slots' 和 'signals',可以移除以下行
add_definitions(-DQT_NO_KEYWORDS)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)



# 指定 Python 解释器路径
set(Python3_EXECUTABLE "D:/Anaconda/envs/sc/python.exe")

# 指定 Python 的头文件目录
set(Python3_INCLUDE_DIR "D:/Anaconda/envs/sc/include")

# 指定 Python 的库文件路径
set(Python3_LIBRARY "D:/Anaconda/envs/sc/libs/python310.lib")


# 添加 pybind11 子目录
add_subdirectory(external/pybind11)

# 查找 Python3 的解释器、开发库和头文件
find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development)


set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mainwindow.h
        mainwindow.ui
)



if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(ttt
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET ttt APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(ttt SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(ttt
            ${PROJECT_SOURCES}
        )
    endif()
endif()



# 链接 Qt Widgets 和 Python3 的库
target_link_libraries(ttt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Python3::Python pybind11::embed)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.ttt)
endif()
set_target_properties(ttt PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS ttt
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(ttt)
endif()

从你 CMake 配置代码来看,整体的设置是为一个 Qt 6 项目与 Python 环境(通过 CMake 和 MSVC 编译)结合使用。下面是对这段代码的简要说明,以及一些可能需要注意的地方。

代码解析

1. CMake 最低版本和项目名称
cmake_minimum_required(VERSION 3.5)

project(ttt VERSION 0.1 LANGUAGES CXX)
  • cmake_minimum_required(VERSION 3.5):要求使用的 CMake 版本至少为 3.5,这通常用于确保能够使用特定的 CMake 功能。
  • project(ttt VERSION 0.1 LANGUAGES CXX):指定项目名称为 ttt,版本为 0.1,且使用 C++ 作为开发语言。
2. Qt 设置
add_definitions(-DQT_NO_KEYWORDS)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
  • add_definitions(-DQT_NO_KEYWORDS):避免使用 Qt 特定的关键字(如 signalsslots)以免与 C++ 关键字冲突。
  • set(CMAKE_AUTOUIC ON), set(CMAKE_AUTOMOC ON), set(CMAKE_AUTORCC ON):自动启用 Qt 的 UI、MOC 和 RCC 处理。
  • find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets):查找 Qt 的安装,并确保找到正确版本的 Qt(优先 Qt6,如果没有则回退到 Qt5)。
  • find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets):为 Qt 添加具体的模块支持(此处为 Widgets 模块,用于创建桌面应用的 GUI)。
3. Python 设置
set(Python3_EXECUTABLE "D:/Anaconda/envs/sc/python.exe")
set(Python3_INCLUDE_DIR "D:/Anaconda/envs/sc/include")
set(Python3_LIBRARY "D:/Anaconda/envs/sc/libs/python310.lib")

# 查找 Python3 的解释器、开发库和头文件
find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development)
  • set(Python3_EXECUTABLE ...)set(Python3_INCLUDE_DIR ...)set(Python3_LIBRARY ...):这三行代码手动设置了 Python 解释器、头文件和库的路径,确保 CMake 可以找到对应的 Python 环境(D:/Anaconda/envs/sc 是 Anaconda 环境路径)。
  • find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development):查找 Python 3.10.16 的解释器和开发工具(用于编译扩展)。
4. Pybind11 设置
add_subdirectory(external/pybind11)
  • add_subdirectory(external/pybind11):将 pybind11 的源码添加为子模块。你需要确保 external/pybind11 目录下已经包含了 pybind11 的源代码。
5. 项目源文件
set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mainwindow.h
        mainwindow.ui
)
  • set(PROJECT_SOURCES ...):指定项目的源文件,包括 C++ 源文件(main.cppmainwindow.cpp),头文件(mainwindow.h),以及 UI 文件(mainwindow.ui)。
6. 生成可执行文件
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(ttt
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
else()
    if(ANDROID)
        add_library(ttt SHARED
            ${PROJECT_SOURCES}
        )
    else()
        add_executable(ttt
            ${PROJECT_SOURCES}
        )
    endif()
endif()
  • qt_add_executable(ttt ...):如果 Qt 版本为 Qt 6 或更高,使用 qt_add_executable 创建可执行文件。
  • add_executable(ttt ...):如果是 Qt 5,则使用 add_executable 创建可执行文件。
7. Pybind11 和 Qt 链接
# 链接 Qt Widgets 和 Python3 的库
target_link_libraries(ttt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Python3::Python pybind11::embed)
  • target_link_libraries:链接 Qt 、python和 Pybind11 库,以便可以在 C++ 项目中使用 Python 和 Qt。

一些建议和注意事项:

  1. Pybind11 子模块:确保 external/pybind11 文件夹下已经存在 pybind11 的源代码。如果没有,确保你已经通过 Git 将 pybind11 的代码作为子模块引入项目,或者手动下载并放置在该目录下。

    使用 Git 子模块的命令:

    git submodule add https://github.com/pybind/pybind11 external/pybind11
    git submodule update --init --recursive
    
  2. Python 和 Qt 版本兼容性:确认你的 Python 环境(3.10.16)与 Qt6 和 Pybind11 版本兼容。通常,Pybind11 支持 Python 3.6 到 3.10 版本,因此 3.10.16 应该没有问题。

  3. MSVC 设置:确保在 Windows 上使用 MSVC 编译时,CMake 能找到正确的 MSVC 编译器。如果使用 Visual Studio,确保已安装支持 C++ 和 Qt6 的编译工具链。

  4. 编译和运行:在构建时,你可能需要一些额外的配置,确保 CMake 能找到 Python 和 Qt 相关的库和头文件。如果遇到任何编译错误,可以检查 CMake 输出,确认路径设置是否正确。(以上解析由gpt4o生成)

 2 qt中的main.cpp代码

#include <QCoreApplication>
#include <QDebug>
#include <Python.h>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
#ifdef _WIN32
    // 设置 PYTHONHOME 和 PYTHONPATH
    _putenv_s("PYTHONHOME", "D:/Anaconda/envs/sc");
    _putenv_s("PYTHONPATH", "D:/Anaconda/envs/sc/Lib;D:/Anaconda/envs/sc/DLLs;D:/Anaconda/envs/sc/Lib/site-packages");

    // 获取当前的 PATH 环境变量
    char* existingPath = nullptr;
    size_t len = 0;
    _dupenv_s(&existingPath, &len, "PATH");

    std::string newPath = "D:/Anaconda/envs/sc/DLLs;";
    if (existingPath) {
        newPath += existingPath;
        free(existingPath);
    }

    // 更新 PATH 环境变量
    _putenv_s("PATH", newPath.c_str());
#endif
    // 初始化 Python
    qDebug() << "Initializing Python...";
    Py_Initialize();

    if (!Py_IsInitialized()) {
        qDebug() << "Python initialization failed!";
        return 1;
    }

    qDebug() << "Python initialized successfully.";

    // 设置 Python 脚本路径
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('.')"); // 当前目录

    // 测试导入一个 Python 脚本
    qDebug() << "Importing Python module...";
    PyObject *moduleName = PyUnicode_FromString("script"); // 假设有一个 script.py 文件
    PyObject *module = PyImport_Import(moduleName);
    Py_DECREF(moduleName);

    if (!module) {
        qDebug() << "Failed to import Python module!";
        PyErr_Print();
        Py_Finalize();
        return 1;
    }

    qDebug() << "Python module imported successfully.";

    // 调用 Python 中的函数
    qDebug() << "Calling Python function...";
    PyObject *func = PyObject_GetAttrString(module, "hello_from_python");
    if (func && PyCallable_Check(func)) {
        PyObject *result = PyObject_CallObject(func, nullptr);
        Py_XDECREF(result);
    } else {
        qDebug() << "Failed to call Python function!";
        PyErr_Print();
    }
    Py_XDECREF(func);
    Py_DECREF(module);

    // 关闭 Python
    Py_Finalize();
    qDebug() << "Python finalized.";

    return app.exec();
}

代码解析

1. 头文件

#include <QCoreApplication>
#include <QDebug>
#include <Python.h>
  • #include <QCoreApplication>:这是 Qt 的一个核心模块,用于管理应用程序的控制流(例如,初始化、事件循环等)。它适用于没有图形界面的 Qt 应用程序。
  • #include <QDebug>:这是 Qt 的调试工具类,用于输出调试信息,类似于标准 C++ 中的 std::cout,但它提供了更强大的功能,如自动格式化。
  • #include <Python.h>:这是 Python C API 的主要头文件,它提供了与 Python 解释器交互的所有功能。你需要在 C++ 中嵌入 Python 时使用这个头文件。

2. 环境变量设置

#ifdef _WIN32
    // 设置 PYTHONHOME 和 PYTHONPATH
    _putenv_s("PYTHONHOME", "D:/Anaconda/envs/sc");
    _putenv_s("PYTHONPATH", "D:/Anaconda/envs/sc/Lib;D:/Anaconda/envs/sc/DLLs;D:/Anaconda/envs/sc/Lib/site-packages");

    // 获取当前的 PATH 环境变量
    char* existingPath = nullptr;
    size_t len = 0;
    _dupenv_s(&existingPath, &len, "PATH");

    std::string newPath = "D:/Anaconda/envs/sc/DLLs;";
    if (existingPath) {
        newPath += existingPath;
        free(existingPath);
    }

    // 更新 PATH 环境变量
    _putenv_s("PATH", newPath.c_str());
#endif
  • _putenv_s:这是 Windows 平台上用于设置环境变量的函数。_putenv_s("KEY", "VALUE") 会设置一个新的环境变量,或者更新现有的环境变量。这里用来设置 PYTHONHOMEPYTHONPATH,确保 Python 环境能够找到必要的库文件。

  • 这两句代码设置的环境变量 不是永久生效的。它们仅在程序的运行期间有效。
    • PYTHONHOME:指定 Python 解释器的主目录,通常是 Anaconda 环境的路径。
    • PYTHONPATH:指定 Python 库的位置,Python 需要这个路径来找到它的标准库和安装的模块。
  • _dupenv_s:这个函数用于获取现有的环境变量的值(例如获取当前 PATH 环境变量的内容)。它把当前 PATH 环境变量复制到 existingPath 变量中,供后续更新使用。

  • _putenv_s("PATH", newPath.c_str()):这里更新了 PATH 环境变量,确保 Python 的 DLL 文件目录被包含在其中,从而可以在 C++ 程序中调用 Python 相关的 DLL。

3. 初始化 Python

    qDebug() << "Initializing Python...";
    Py_Initialize();

    if (!Py_IsInitialized()) {
        qDebug() << "Python initialization failed!";
        return 1;
    }

    qDebug() << "Python initialized successfully.";
  • Py_Initialize():这是 Python C API 中的一个函数,用来初始化 Python 解释器。它需要在任何 Python C API 调用之前调用。它会设置 Python 运行时所需的基本环境(例如内存管理、垃圾回收等)。

  • Py_IsInitialized():这个函数用于检查 Python 解释器是否已经成功初始化。如果返回 0,表示初始化失败;如果返回非零值,表示初始化成功。

4. 设置 Python 脚本路径

    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('.')"); // 当前目录
  • PyRun_SimpleString():这个函数用于执行 Python 代码字符串。这里它执行了两行 Python 代码:
    • import sys:导入 Python 标准库中的 sys 模块。
    • sys.path.append('.'):将当前目录(.)添加到 Python 模块的搜索路径中。这样 Python 会从当前目录加载模块,确保脚本文件能被找到。如果你的 script.py 模块的位置改变了,那么你需要更新 sys.path.append() 中的路径,以确保 Python 能够找到该模块。
    • 举例:假设你有一个 Python 脚本 my_script.py,并且它在当前目录下有一个 mymodule.py 模块。如果你想在 my_script.py 中导入 mymodule.py,你需要确保当前目录被包含在 sys.path 中。否则,Python 就无法找到 mymodule.py
    • 注意!!由于将之前将scrapy.py放入了项目根文件,这里的‘.’需要修改,或者可以直接将代码放入和项目名.exe相同的目录,也就是生成的debug文件夹下。

5. 导入 Python 模块

    qDebug() << "Importing Python module...";
    PyObject *moduleName = PyUnicode_FromString("script"); // 假设有一个 script.py 文件
    PyObject *module = PyImport_Import(moduleName);
    Py_DECREF(moduleName);

    if (!module) {
        qDebug() << "Failed to import Python module!";
        PyErr_Print();
        Py_Finalize();
        return 1;
    }

    qDebug() << "Python module imported successfully.";
  • PyUnicode_FromString():将 C 字符串转换为 Python 字符串对象。"script" 是 Python 脚本的名称(假设脚本文件名为 script.py)。

  • PyImport_Import():该函数用于导入指定名称的 Python 模块。这里尝试导入名为 "script" 的模块,PyImport_Import 会返回一个 Python 模块对象(PyObject*)。如果模块导入失败,返回 nullptr

  • Py_DECREF(moduleName):减少 moduleName 对象的引用计数。PyUnicode_FromString 创建了一个 Python 字符串对象,使用完毕后应该调用 Py_DECREF() 来减少引用计数,防止内存泄漏。

  • PyErr_Print():如果导入模块失败,可以调用这个函数来打印 Python 错误信息。

6. 调用 Python 函数

    qDebug() << "Calling Python function...";
    PyObject *func = PyObject_GetAttrString(module, "my_function"); // 假设脚本中有一个函数 my_function
    if (func && PyCallable_Check(func)) {
        PyObject *args = PyTuple_Pack(0);  // 假设函数没有参数
        PyObject *result = PyObject_CallObject(func, args);
        Py_XDECREF(result); // 如果有返回值,减少引用计数
        Py_DECREF(args);
    } else {
        qDebug() << "Function not found or not callable!";
    }
  • PyObject_GetAttrString():获取模块中的属性(通常是函数或类)。这里我们试图获取模块 module 中名为 my_function 的函数。

  • PyCallable_Check():检查 func 是否是一个可调用的对象(即函数)。如果是,就可以用 PyObject_CallObject() 调用它。

  • PyTuple_Pack():创建一个 Python 元组对象,作为函数的参数。这里我们假设 my_function 函数没有参数,所以传入 0 表示空元组。

  • PyObject_CallObject():调用 Python 中的函数,并将参数传递给它。函数的返回值会存储在 result 中。

  • Py_XDECREF():减少 result 的引用计数。如果 result 不为 nullptr,调用此函数可以避免内存泄漏。

7. 清理

    Py_Finalize();
  • Py_Finalize():这个函数用于清理 Python 解释器,释放所有内存和资源。它应该在程序结束时调用。

以上解析由gpt4o生成


运行结果

对了,记得将Python 的动态链接库文件(DLL)放入debug下,

若用debug运行,报错

但生成releas即可,不要忘记将Python 的动态链接库文件(DLL)放入release下。
运行输出

15:25:15: Starting E:\qtcode\ttt\build\Desktop_Qt_6_7_1_MSVC2019_64bit-Release\ttt.exe...
Initializing Python...
Python initialized successfully.
Importing Python module...
Python module imported successfully.
Calling Python function...
Hello from Python!
Python finalized.

解决debug下不能运行的方法查看作者下一篇文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值