Windows执行Python模块的C++企业应用程序。下面是我在此技术融合过程中的经历,从第一行代码到应用程序交付。

简介
Python的简洁性、广泛的软件包生态系统和支持性社区使其成为一个有吸引力的选择。相反,当处理复杂的用户界面、多线程数据流和企业解决方案中的全天候鲁棒性时,C++成为核心。有时候,需要将这两者融合起来,力求兼得。
经常情况下,Python代码调用C++方法进行复杂的算法计算。然而,我遇到了一个相反的情况,我的机器学习应用程序迫切需要使用基于Python的推理代码调用自定义模型。重新用C++编写它或采用相应的推理引擎是不可行的。我最初的想法很简单:“从C++调用Python应该很容易。”然而,我低估了其中涉及的复杂性!
期望与现实
有几种可以在C++和Python之间进行交互的可搜索的桥梁:
- Python的内置C++接口,可以通过与标准Python软件包捆绑的C++头文件和库(Python.h、Python.lib)访问。
- Boost.Python,适用于熟悉Boost库的用户。
- Pybind11,我偏爱它的强大功能和易于集成到项目中。
乍一看,解决方案似乎近在咫尺,唯一需要做的是从这三种桥梁中选择最合适的。但让我们仔细看看!
上述方法在实现Python-C++通信的核心目标方面表现出色,对于初步试验和实验已经足够。然而,当我们的目标是构建一个可交付的解决方案时,我们需要考虑到整个软件生命周期。其中一个需要解决的挑战是分发。为了澄清,任务是在Python组件旁边分发C++二进制文件。即使考虑到将解释器安装到操作系统中,共享Python代码本身也可能很困难。此外,多个Python版本可能共存,并且每个额外的软件包都需要正确安装,包括解决依赖关系。问题变成了:在我们的情况下,有哪些可行的选择?
- 要求用户在使用C++应用程序之前安装Python和所需的软件包。
- 将Python安装程序合并到C++安装程序中,自动解决相关问题,包括用户系统上的其他Python安装。
两者都不是最佳选择。因此,我踏上了寻找更可靠和稳定的方法的任务,我很愿意与您分享。
嵌入式Python
在您上次访问Python主页时,是否探索过其发行选项?其中一个引人注目的选择是Windows嵌入式软件包。这个选择引起了我的好奇心,提供了一个看似神奇的解决方案来解决分发问题。在官方文档中深入研究后,我发现了关于这个嵌入版本的细节 – 令人惊讶的是,Python提供了一个嵌入版本,可以无缝集成到其他应用程序中。
在浏览嵌入版本存档的内容时,会出现几个问题:如何将额外的模块添加到这个设置中?如何指导C++应用程序使用这个嵌入的版本?
回答第一个问题相对简单:可以直接从标准Python安装中复制软件包。
第二个问题需要进行广泛的研究,如前所述,Python桥梁只适用于默认安装。虽然更改Python环境变量(PYTHONPATH、PYTHONHOME)有时可能有帮助,但这不是切换到嵌入设置的终极解决方案,因为Python可能会将其文件放在不同的操作系统位置。
C++的Python标记
最简单的Python程序可能只包含一行:
print(‘Hello, World!’)
Python屏蔽了程序员的大量内部例程,以实现这种表面上的简洁性。在默默地启动这行代码时,解释器处理安装参数、初始化、默认模块导入、字节码编译、函数查找等等。然而,有一种方式可以干预这些隐藏的机制。引用官方文档的话,“Python具有用于全局配置的变量,用于控制不同的功能和选项”。它们存在于命令行参数或作为C++代码的标志和方法,可以通过前面提到的Python.h访问。在这种情况下,我们需要使用Py_SetPath()方法来覆盖Python的搜索路径。在某些特殊情况下,其他标志也会发挥作用:Py_NoSiteFlag、Py_NoUserSiteDirectory、Py_IgnoreEnvironmentFlag。这些标志用于跳过加载默认模块site或忽略用户主目录中的单独模块目录。检查它们以处理特定情况!
摘要指南
- 准备好你的C++项目,开始集成Python。
- 决定使用的Python版本,并将其作为默认安装到Windows中。
- 下载相同版本的Python作为嵌入包,并将其解压到项目目录中。
- 编写调用所有需要的Python方法的Python测试代码,以进一步进行模块复制和测试。
- 使用嵌入安装运行Python测试代码(使用命令行标志覆盖搜索路径)。如果使用了一些非默认模块,就会出现缺少模块的错误。将缺失的模块从默认安装中逐个复制到嵌入安装中,以使Python测试代码成功完成。
- 转到你的C++项目。获取pybind11。添加包含搜索路径的项目。
- 将默认Python安装的头文件和库的搜索路径添加到你的项目中。
- 在C++代码中使用Py_SetPath()来设置相对路径到Python嵌入安装中。根据需要使用额外的标志。
- 如果Python软件包中的一个位于用户站点包目录中,只需将其复制到嵌入包文件夹中,并在Py_SetPath()中添加一个单独的路径。
- 在C++代码中,使用pybind11 API访问Python方法。如果在调用Python方法时C++程序冻结,那么很可能是某个Python模块无法导入 – 使用步骤[5]中的Python测试代码解决。
- 将包含嵌入Python文件夹的C++二进制文件作为一个组成部分进行分发。你可能还需要将一些DLL复制到你的二进制文件位置,如python3.dll和Windows redistributables。
最后部分
生产用途的C++应用程序可以将嵌入的Python作为其一部分,用户甚至不会知道。这是一个伟大的成就,因为我们都喜欢通过简单而强大的应用程序来完成任务,而不是在安装过程中苦苦思索!
我分享了我的演示项目,其中包含了本文的所有见解,供你快速开始。