在centos7中利用pybind11构建C++的动态库供python调用
需求:在 CentOS 7 中,使用 pybind11 构建 C++ 的动态库供 Python 调用涉及几个步骤:安装依赖、获取 pybind11、编写绑定代码、编译动态库,以及在 Python 中调用。以下是详细的步骤:
步骤 1:安装依赖
首先,确保系统中安装了 Python、pip 和 C++ 编译环境:
sudo yum install epel-release
sudo yum update
sudo yum install gcc-c++ python3 python3-pip python3-devel
接着,使用 pip 安装 pybind11:
pip3 install pybind11
步骤 2:获取 pybind11 头文件
通过 pip 安装 pybind11 后,你可以在 Python 站点包目录中找到 pybind11 的头文件。使用以下命令查找 pybind11 的安装位置:
pip3 show pybind11 | grep Location
记下头文件的路径,你将在编译时需要它。
步骤 3:编写绑定代码
创建一个 C++ 文件(例如 example.cpp
),并使用 pybind11 编写绑定代码。下面是一个简单的例子:
/*--------------------------------------编译一个类--------------------------------*/
#include <pybind11/pybind11.h>
#include <iostream>
using namespace std;
#include "Combiner.h"
class Calculator {
public:
void test(double W, double H)
{
vector<map<double, vector<Line>>> combine_lines;
map<double, vector<Line>> angle_lines;
vector<Line> tmp_lines_0;
tmp_lines_0.push_back(Line(Point(0, 0), Point(0, 10)));
tmp_lines_0.push_back(Line(Point(0, 10), Point(20, 10)));
tmp_lines_0.push_back(Line(Point(20, 10), Point(20, 0)));
tmp_lines_0.push_back(Line(Point(20, 0), Point(0, 0)));
angle_lines[0] = tmp_lines_0;
vector<Line> tmp_lines_90;
tmp_lines_90.push_back(Line(Point(0, 0), Point(0, -20)));
tmp_lines_90.push_back(Line(Point(0, -20), Point(-10, -20)));
tmp_lines_90.push_back(Line(Point(-10, -20), Point(-10, 0)));
tmp_lines_90.push_back(Line(Point(-10, 0), Point(0, 0)));
angle_lines[90] = tmp_lines_90;
combine_lines.push_back(angle_lines);
Combiner combinber(W, H);
tuple<bool, vector<tuple<double, double, double>>> res = combinber.combine_with_rotate(combine_lines);
bool flag = std::get<0>(res); //访问bool值
std::vector<std::tuple<double, double, double>>& vec = std::get<1>(res); //访问vector
// 确保vector不为空
if (!vec.empty()) {
// 访问并打印vector中的第一个tuple
const auto& firstTuple = vec.front(); // 或vec[0]
cout << "First tuple: ("
<< std::get<0>(firstTuple) << ", "
<< std::get<1>(firstTuple) << ", "
<< std::get<2>(firstTuple) << ")" << endl;
}
else {
std::cout << "The vector is empty." << std::endl;
}
}
};
namespace py = pybind11;
PYBIND11_MODULE(combine_with_rotate, m)
{
py::class_<Calculator>(m, "Calculator")
.def(py::init<>()) // 注册构造函数
.def("test", &Calculator::test); // 注册test成员函数
}
/*--------------------------------------编译一个纯C函数--------------------------------*/
#include <pybind11/pybind11.h>
int add(int a, int b) {
return a + b;
}
PYBIND11_MODULE(example, m) {
m.def("add", &add, "A function which adds two numbers");
}
步骤 4:编译动态库
使用 c++ 命令手动编译动态库。你需要指定 Python 包含的目录、pybind11 包含的目录以及输出的动态库名称。根据你的 Python 版本和 pybind11 的安装位置,命令可能有所不同。以下是一个编译示例:
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
这条命令将生成一个动态库(例如 example.cpython-36m-x86_64-linux-gnu.so
),可以被 Python 导入。
步骤 5:在 Python 中调用
在同一目录下,启动 Python 解释器,然后导入你的模块并调用函数:
import example
print(example.add(1, 2))
如果一切正常,这将输出 3
。
编译命令解释
c++ -O3 -Wall -shared -std=c++11 -fPIC ( p y t h o n 3 − m p y b i n d 11 − − i n c l u d e s ) e x a m p l e . c p p − o e x a m p l e (python3 -m pybind11 --includes) example.cpp -o example (python3−mpybind11−−includes)example.cpp−oexample(python3-config --extension-suffix)
c++
:调用 C++ 编译器。-O3
:这是编译器的优化选项,-O3
代表进行高级优化。-Wall
:开启所有警告消息,帮助开发者发现潜在的问题。-shared
:生成动态链接库(shared library,或称为动态库)。-std=c++11
:使用 C++11 标准进行编译。-fPIC
:生成位置无关代码(Position Independent Code),这对于动态链接库是必需的。$(python3 -m pybind11 --includes)
:这部分命令用于自动获取 pybind11 的头文件路径,以及 Python 的头文件路径。这是为了让编译器知道 pybind11 和 Python 头文件的位置。$(...)
是 Bash 中的命令替换,意味着先执行括号内的命令,然后将输出替换到当前位置。example.cpp
:你的源文件,包含了要绑定的 C++ 代码。-o example$(python3-config --extension-suffix)
:指定输出文件的名称。$(python3-config --extension-suffix)
用于获取 Python 扩展模块的文件扩展名,确保生成的动态库与当前 Python 版本兼容。
注意
- 确保在编译命令中使用正确的 Python 版本和 pybind11 路径。
- 如果你的项目较大,考虑使用 CMake 来管理构建过程。pybind11 提供了支持 CMake 的示例和工具。
通过遵循这些步骤,你可以在 CentOS 7 上使用 pybind11 构建 C++ 的动态库,并在 Python 中调用它。
步骤 4:编译动态库可以用另一种简单的方法
方法二:也可以使用setup.py
来编译和安装使用pybind11的C++的扩展模块。这种方法可以更加方便地集成到Python的包管理和分发系统中。
from setuptools import setup
from pybind11.setup_helpers import Pybind11Extension, build_ext
# 定义扩展模块,指定源代码文件
ext_modules = [
Pybind11Extension(
'combine_with_rotate', # 模块名
['main.cpp', 'src/clipper.cpp', 'src/Combiner.cpp', 'src/fileProcess.cpp', 'src/Point.cpp'], # 源文件列表
include_dirs=['include'], # 包含头文件的目录,指向include目录
libraries=['curl'], # 要链接的库
# 这里可以指定额外的编译参数,但对于Pybind11Extension通常不必要
cxx_std=17, # 指定C++标准为C++17
),
]
setup(
name='combine_with_rotate',
version='0.1',
description='A sample Python package with C++ extension',
ext_modules=ext_modules, # 包含定义的扩展模块
cmdclass={"build_ext": build_ext}, # 指定build_ext命令
)
这个命令将编译你的 C++ 代码,并将生成的扩展模块安装到你的 Python 环境中,使你可以像导入普通 Python 模块一样导入它,该命令适用于部署,因为它将模块安装到 Python 的 site-packages
目录,这使得模块即使在源代码被修改或删除后也仍然可用。。
python3 setup.py install
开发和测试时,建议使用下面的命令
python3 setup.py build_ext --inplace
步骤 5:在 Python 中调用
在同一目录下,启动 Python 解释器,然后导入你的模块并调用函数:
import combine_with_rotate
calc = combine_with_rotate.Calculator()
calc.test(100, 200)
print("Good Lucky To You!")
setup.py通用模板
from setuptools import setup, Extension
from pybind11.setup_helpers import Pybind11Extension, build_ext
# 定义包含目录和库目录
include_dirs = [
"path/to/headers", # 头文件目录,如果你的 jsoncpp 头文件不在标准路径
"path/to/examplelib/headers", # examplelib 的头文件目录
# 其他必要的头文件目录
]
library_dirs = [
"path/to/library", # jsoncpp 库文件目录,如果库不在标准路径
"path/to/examplelib/library", # examplelib 库文件目录
# 其他库文件目录
]
ext_modules = [
Pybind11Extension(
"my_extension", # 扩展模块的名字
["binding.cpp", "class1.cpp", "class2.cpp"], # 需要编译的源文件列表
include_dirs=include_dirs, # 指定头文件目录
library_dirs=library_dirs, # 指定库文件目录
libraries=["jsoncpp", "examplelib"], # 指定需要链接的库名
# 这里还可以添加其他编译选项,如 define_macros, extra_compile_args 等
),
]
setup(
name="my_extension",
version="0.1",
author="Your Name",
description="A pybind11 example with multiple C++ files, header directories, and linking to jsoncpp",
long_description="",
ext_modules=ext_modules,
cmdclass={"build_ext": build_ext},
zip_safe=False,
)
注意事项
- 头文件和库文件的位置:确保你正确地指定了
jsoncpp
头文件和库文件的位置。如果你通过包管理器(如apt
、yum
或brew
)安装jsoncpp
,库文件和头文件可能已经在标准路径中了,这种情况下你可能不需要设置include_dirs
和library_dirs
。 - 库名:在
libraries
列表中,你只需要指定库的名称而不是具体的文件名。例如,对于libjsoncpp.so
或jsoncpp.lib
,你只需指定"jsoncpp"
。 - 跨平台构建:当你在不同平台上构建扩展时,可能需要根据操作系统调整库名称或路径。
- 依赖关系:如果
jsoncpp
依赖其他库,你可能还需要链接这些依赖库。这可以通过在libraries
列表中添加更多库名来实现。
更新GCC编译器
C++17标准需要较新版本的GCC编译器(至少是GCC 7)。您可以通过以下步骤更新GCC编译器:
-
查看当前GCC版本:
gcc --version
-
CentOS 7安装更新的GCC版本: CentOS 7的默认仓库中可能不包含最新版本的GCC,但您可以通过Software Collections (SCL)库来安装更新版本的GCC。首先,安装SCL:
sudo yum install centos-release-scl
然后,安装较新版本的GCC(例如,安装GCC 9):
sudo yum install devtoolset-9-gcc devtoolset-9-gcc-c++
激活新版本的GCC:
source /opt/rh/devtoolset-9/enable
这将只为当前终端会话启用新版本的GCC。您可能希望将这个命令添加到您的
.bashrc
或其他shell启动脚本中,以便每次启动新终端时自动激活。 -
确认更新后的GCC版本:
gcc --version
确保显示的版本支持C++17(GCC 7或更高)。
要在 CentOS 7 中安装 Python 3.8.0,并确保携带了 _ctypes
模块
-
安装依赖项:
在安装 Python 3.8.0 之前,确保你的系统已安装了编译 Python 所需的依赖项。在 CentOS 7 上,你可以运行以下命令安装这些依赖项:
sudo yum install gcc openssl-devel bzip2-devel libffi-devel
-
下载 Python 3.8.0 源代码:
访问 Python 官方网站,下载 Python 3.8.0 的源代码压缩包(tar.gz 格式)。
wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz
-
解压并编译安装 Python:
解压下载的 Python 源代码并进入解压后的目录:
tar -zxvf Python-3.8.0.tgz cd Python-3.8.0
然后,配置、编译并安装 Python:
./configure --enable-optimizations --with-ctypes make sudo make altinstall
make altinstall
命令会将 Python 安装为python3.8
,以避免覆盖系统默认的 Python 版本。 -
验证安装:
安装完成后,你可以使用以下命令验证新安装的 Python 版本和是否携带了
_ctypes
模块:python3.8 --version python3.8 -c "import _ctypes; print(_ctypes)"
如果输出显示了 Python 3.8.0 的版本信息和
_ctypes
模块相关信息,则说明安装成功。
通过以上步骤,你应该能够在 CentOS 7 中成功安装 Python 3.8.0,并确保携带了 _ctypes
模块。
如果你使用的是 Python 3.8.0 版本,那么应该使用 pip3.8
命令来安装 Pybind11,而不是 pip3
。因为 pip3
命令可能会安装到系统默认的 Python 版本,而不是你安装的 Python 3.8.0。
因此,正确的安装命令应该是:
bashCopy code
pip3.8 install pybind11
这样可以确保将 Pybind11 安装到你指定的 Python 3.8.0 版本中。