欢迎访问我的博客首页。
Python 调用 C++
1. pybind11
使用 pybind11 创建一个 python 扩展模块,并使用 python 测试。
1.1 使用 cmake 编译
- 配置 pybind11
下载 pybind11,确保文件夹名称为 pybind11,无需编译和安装。
- 新建 main.cpp
#include <pybind11/pybind11.h>
#include <string>
namespace py = pybind11;
class Person {
public:
Person() {}
Person(const std::string &_name) { name = _name; }
void setName(const std::string &_name) { name = _name; }
std::string &getName() { return name; }
private:
std::string name;
};
int subtract(int a, int b) { return a - b; }
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example module";
m.def("add", [](int a, int b) { return a + b; });
m.def("subtract", &subtract);
py::class_<Person>(m, "Person")
.def(py::init())
.def(py::init<const std::string &>())
.def("setName", &Person::setName)
.def("getName", &Person::getName);
}
这个例子实现了两个函数和一个类。第一个函数使用 lambda 表达式实现加法;第二个函数实现减法;类有无参构造函数和有参构造函数。
- 编辑 cmake 配置文件
project 后面的括号中是项目名称,使用 cmake 生成的 vs 项目名称也是由这个名称决定的。
cmake_minimum_required(VERSION 3.2)
project(test)
include_directories(pybind11/include) # 指定 pybind11/pybind11.h 所在文件夹。
add_subdirectory(pybind11) # 下载得到的 pybind11。
pybind11_add_module(example main.cpp) # 使用 pybind11 的命令生成 python 扩展模块。
- 编译
新建文件夹 build,在其中依次执行下面的命令,生成 example.cp37-win_amd64.pyd。生成的扩展模块名字的某些字段会因不同的 python 版本和平台而有所差异。
cmake -G "MinGW Makefiles" ..
make
- 测试
在 build 文件夹中创建 main.py,运行即可看到结果。
import example
if __name__ == '__main__':
print(example.add(1, 2))
print(example.subtract(1, 2))
person1 = example.Person()
person1.setName("XiaoMing")
print(person1.getName())
person2 = example.Person("LiHua")
print(person2.getName())
pybind11 是一个 C++ 库,它能把用 C++ 语言写的代码编译成 python 扩展模块。注意两个命令 PYBIND11_MODULE 与 pybind11_add_module 指定的扩展模块名必须一致。
1.2 使用 visual studio 编译
-
创建 vs 项目
创建空项目,项目名称为 example,解决方案名称为 exampleproject。 -
准备第三方库
2.1 下载 pybind11,得到文件夹 pybind11。
2.2 假如使用的 python 版本是 3.8。创建文件夹 python38,拷入 python3.8 安装位置中的 include 和 libs 文件夹。
2.3 在文件夹 exampleproject/example 中新建文件夹 thirdpart,拷入上面得到的两个文件夹 pybind11 和 python38。 -
配置 vs 属性
3.1 在常规选项卡上,设置目标文件扩展名为 .pyd,设置配置类型为动态库(.dll)。有的版本的 vs,目标文件扩展名在高级选项卡上。
3.2 设置包含目录为 thridpart\pybind11\include 和 thridpart\python38\include。设置库目录为 thridpart\python38\libs。设置附加依赖项为 python3.lib 和 python38.lib。 -
添加 main.cpp 生成 example.pyd。
-
测试
因为编译扩展模块时使用了 python 环境,所以测试也要在该 python 环境下进行。
1.3 使用 cmake 生成 vs 项目
上面的两种方法中,使用 cmake 编译不需要配置 python 环境中的头文件目录和库目录,pybind11 会自动查找;使用 visual studio 编译需要配置 python 环境中的头文件目录和库目录,且生成的扩展库名字只包含项目名。原因是,使用 cmake 时,我们用的编译命令是 pybind11 的 pybind11_add_module;使用 vs 时,我们没有用 pybind11 的命令,而是把它当作 dll 编译。
一些第三方库很容易在 vs 上配置,却不一定能在 cmake 上配置,因此使用 vs 编译 python 扩展模块是很有必要的。这时,我们可以先用 cmake 编译上面那样简单的 pybind11 例程,然后生成 vs 项目,接着在 vs 项目上配置第三方库,再把例程修改为需要的代码,最后就可以生成名字类似 example.cp37-win_amd64.pyd 的 python 扩展模块了。
2. ctypes
使用 vs 新建空项目,配置类型设置为动态库(.dll),然后使用下面的代码生成 dll 文件。
#define EXPORT extern "C" __declspec(dllexport)
EXPORT int add(int a, int b) { return a + b; }
3. 总结
使用 pybind11 生成 python 控制模块时,可以不需要 cmake,仅使用 vs 和 pybind11 库。pybind11 库由头文件组成,无须编译安装。但是这种方法生成的 python 扩展模块只能在对应的 python 版本下使用。
使用 ctypes 时,编译的 dll 和 python 版本无关,可以在任意 python 版本下使用。但 ctypes 只能调用函数,不能创建类,而且 python 调用 dll 较复杂,比如需要使用 restype 和 argtypes 指出 python 所调 C++ 函数的返回值类型和参数类型。
我使用这两种方式调用一个包含类似下面函数的第三方库时,该函数得到的返回值和实参是 0 或 ±inf,其它不涉及浮点数的函数都正常。用自己编译的一个包含这种函数的库,又完全正常,目前还不知道什么原因。解决办法是不使用堆内存,即,不使用 new 创建对象。
float Example::fac(float& a);
4. 参考
- pybind11 的安装与配置使用,CSDN,2022。
- pybind11在visual studio中的配置,CSDN,2022。