Make Cpp Work with Python

C++Python是世界上最难学好的语言!

本文中,你将看到如何配置一个同时使用Python和C++两种语言的项目。

前言

Python 高度封装又非常易用,同时在很多情况下也是进行机器学习等任务的唯一选择。另一方面,如果调教得当,C++则在数据集的读取及预处理上具备性能上的显著优势。将二者作为“联合主演”,融入到一个project中是一个值得考虑的选项。

开发工具

Python 没什么好说的。对于C++,如果是在Linux平台下,常用的工具是g++ & make,输出的动态库后缀是.so。在Windows下,我使用了Visual Studio - 不得不说相比 apt install make gcc,VS的安装配置痛苦许多,但VS也确实没什么好的替代(不知道为什么,我就是不想用MinGW或者CMake这种跨平台的工具)。

因此,这里我们使用VS编译出的供Python调用的C++库也是面向Windows的版本。我安装的使用C++的桌面开发组件的最小集合是(VS 2022):

  • MSVC:C++编译器及库
  • Windows 10 SDK:包含了一些C++ API在Windows平台的定义与实现(如cstring)。如果未安装,在调用一些NT与POSIX的实现不一样的C++ API时无法顺利编译,提示找不到头文件。
  • 实时调试器:就是字面意思

流程及注意事项

  1. 在VS中,新建C++项目,注意以下几条项目属性(项目-属性)
    • 常规-配置类型:动态库(.dll)
    • 常规-C++语言标准
    • 高级-目标文件拓展名:.dll
    • 高级-使用调试库:发布时,选择否
  2. 完成C++项目
    • 在开放给Python调用的函数前,添加如下装饰符,否则Python无法调用
      1
      2
      extern "C" __declspec(dllexport)
      void foo() {...}
    • 无需编写makefile:VS会帮你打理一切!
  3. 生成项目
    • 发布时,选择Release
    • 注意区分x86/x64。特别是当你使用了外部的库时
  4. 在Python中,通过如下方式引入并调用C++-based content:
    1
    2
    3
    import ctypes
    cpp = ctypes.cdll.LoadLibrary(path_to_dll)
    cpp.foo()

完成!

...真的会这么顺利吗?

你可能遇到以下问题:

OSError: [WinError 126] 找不到指定的模块。

什么,找不到?不慌,反手一个 assert os.path.exists(path_to_dll) 。什么?文件路径没错?那是怎么会事呢?

这时候就要祭出一个VS自带的小工具 dumpbin (需要从VS命令行工具中调用)了。它可以对你要引入的dll进行分析,报告其依赖的其他dll。如黄色框所示,待引入的dll依赖于两个dll。至于其依赖的dll需要到哪里去找?如果你编译时引用了第三方的库(如lib静态库),那么第三方的库可能也提供了dll版本。而像kernel32.dll这类dll是Windows系统提供的,它们一般位于Windows的system32或SysWOW64目录下,将这些依赖复制一份到要引入的dll所在目录即可。

最后的最后,如何让Python识别到依赖的dll库文件呢?热心网友提供的一种建议是,使用os.chdir切换Python的工作目录到dll目录。然而,这种方式不太优雅,而且可能会导致无法import同一项目下自己写的其他模块。我认为的一种比较好的方式是临时修改系统环境变量:

1
2
3
4
5
6
# 在windows系统中,os.pathsep即为;
os.environ["PATH"] += os.pathsep + dll_path
dll_file = os.path.join(dll_path, "dll_to_import.dll")
assert (os.path.exists(dll_file))
# 传入的是文件名而不是完整路径,因为路径已经在PATH中
cpp = ctypes.cdll.LoadLibrary(dll_file)

现在应该没有问题了。至于C++怎么写,性能怎么优化……祝你好运!

其他参考

这里有一份MS的文档,但是说实在的,真的有人用VS写Python吗?还是那句话,特定于某种工具(如这里的VS)或平台、又有替代的方法,可能并不值得花功夫去学。尤其是MS搞的这些东西