基于pybind11的python调用c++模块

本文介绍如何在Ubuntu18.04环境下,使用Python3通过pybind11调用C++生成的动态链接库。涵盖pybind11的安装方法,CMakeLists.txt配置,接口函数封装及Python调用流程。强调了变量命名一致性的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于ubuntu 18.04系统,使用python3调用c++生成的动态链接库

1. pybind11的三种安装方式:

(1) 命令安装

sudo apt-get install python-pybind11

(2) pip命令安装

pip3 install pybind11

(3) 源码编译安装

pip3 install pytest
git clone https://github.com/pybind/pybind11.git
cd pybind11
mkdir build
cd build
cmake …
make -j4
sudo make install

2. 使用CMakeList生成动态链接库

这里简单列一下以前使用pybind11写的一个借口函数,并使用CMakeLists.txt生成 .so 动态链接库,如下所示,
map_interface.h

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>

namespace py = pybind11;

constexpr int kLocalDescriptorSize2  = 256;
constexpr int kGlobalDescriptorSize2 = 4096;

class MapInterface
{
public:
    int CreateObj(int img_row, int img_col);

    void SetCameraParas(int camera_model, Eigen::Ref<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> camera_intrinsic, 
                        Eigen::Ref<Eigen::Matrix<float, 5, 1, Eigen::ColMajor>> camera_distort);

    bool AddImgFeatures(int frame_index, Eigen::Ref<Eigen::Matrix<float, 4, 4, Eigen::RowMajor>> camera_pose,
                Eigen::Ref<Eigen::Matrix<float, 1, kGlobalDescriptorSize2, Eigen::RowMajor>> global_descriptor,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_image_keypoints,
                Eigen::Ref<Eigen::Matrix<float, kLocalDescriptorSize2, Eigen::Dynamic, Eigen::RowMajor>> row_local_descriptors);

    // 为了便于pybind11 调用,这里暂时不使用重载函数功能
    bool AddImgFeatures2(int frame_index, Eigen::Ref<Eigen::Matrix<float, 4, 4, Eigen::RowMajor>> camera_pose,
                Eigen::Ref<Eigen::Matrix<float, 1, kGlobalDescriptorSize2, Eigen::RowMajor>> global_descriptor,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_image_keypoints,
                Eigen::Ref<Eigen::Matrix<float, 2, Eigen::Dynamic, Eigen::RowMajor>> row_norm_keypoints,
                Eigen::Ref<Eigen::Matrix<float, kLocalDescriptorSize2, Eigen::Dynamic, Eigen::RowMajor>> row_local_descriptors);

    void DestroyObj();
};

PYBIND11_MODULE(map_interface, m) {
    m.doc() = "pybind11 Hierarchical Localization cpp backend";

    py::class_<MapInterface>(m, "MapInterface")
    .def(py::init())
    .def("CreateObj", &MapInterface::CreateObj, py::return_value_policy::copy)
    .def("SetCameraParas", &MapInterface::SetCameraParas)
    .def("AddImgFeatures", &MapInterface::AddImgFeatures, py::return_value_policy::copy)
    .def("AddImgFeatures2", &MapInterface::AddImgFeatures2, py::return_value_policy::copy)
    .def("DestroyObj", &MapInterface::DestroyObj);
};

注意需要添加#include <pybind11/pybind11.h>头文件, 因为使用Eigen作为数据传输的接口,因此需要使用#include <pybind11/eigen.h>文件, 在使用Eigen作为函数参数,用来传输数据时, 注意要指定Eigen矩阵的行列有限排列,即Eigen::RowMajor/ColMajor. 需要特别注意的是,当矩阵是m × \times × 1(m行,1列)情况下,必须要使用列优先排列(Eigen::ColMajor),不然会出现错误. 例如:

    void SetCameraParas(int camera_model, Eigen::Ref<Eigen::Matrix<float, 3, 3, Eigen::RowMajor>> camera_intrinsic, 
                        Eigen::Ref<Eigen::Matrix<float, 5, 1, Eigen::ColMajor>> camera_distort);

当然也可以使用numpy做函数参数的数据传输.
这里将c++的函数接口封装到类里面, 在最后添加了python调用c++的接口函数

PYBIND11_MODULE(map_interface, m) {
    m.doc() = "pybind11 Hierarchical Localization cpp backend";

    py::class_<MapInterface>(m, "MapInterface")
    .def(py::init())
    .def("CreateObj", &MapInterface::CreateObj, py::return_value_policy::copy)
    .def("SetCameraParas", &MapInterface::SetCameraParas)
    .def("AddImgFeatures", &MapInterface::AddImgFeatures, py::return_value_policy::copy)
    .def("AddImgFeatures2", &MapInterface::AddImgFeatures2, py::return_value_policy::copy)
    .def("DestroyObj", &MapInterface::DestroyObj);
};

注意,上面接口为了方(偷)便(懒), AddImgFeatures()没有如果使用重载函数的话. 如果使用pybind11实现累的重载函数接口时,需要使用py::overload_cast, 具体可参考https://blog.csdn.net/weixin_41521681/article/details/106200017

.def("AddImgFeatures", py::overload_cast< 函数具体的参数 >(&MapInterface::AddImgFeatures))

上面函数的具体实现是在map_interface.cpp文件中, 这里不具体列. 需要特别注意的是,在写接口类,或者接口函数时,接口所在的.h文件尽量少的依赖自己写的一些.h文件, 这些依赖可以具体写在.cpp中. 这样, 在使用python调用(或者是主函数调用)时,使用接口的.h文件可以避免大量.h文件暴露出来. 另外为了更加规范,可以将PYBIND11_MODULE这个模块单独写在一个.h文件中.

CMakeList.txt文件的写法

cmake_minimum_required(VERSION 2.8.12)
project(HFnet_map)
add_compile_options(-std=c++11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
set(CMAKE_BUILD_TYPE "Release")
set(PYBIND11_CPP_STANDARD -std=c++11)


find_package(pybind11 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(Eigen3 REQUIRED)

INCLUDE_DIRECTORIES(
  ${OpenCV_INCLUDE_DIRS}
  ${EIGEN3_INCLUDE_DIR}
  ${pybind11_INCLUDE_DIRS}
  ./
)

add_library(map_interface MODULE 
            map_interface.cpp
            xxx.cpp
            xxx.cpp
            xxx.cpp
    )
target_link_libraries(map_interface 
            pybind11::module
            ${OpenCV_LIBS}
  )

CMakeList里面需要注意对pybind11的引用, 也有其他引用的方式,例如使用 pybind11_add_module,可参考pybind11的官方doc

3. python调用

python调用代码如下:

import os
import cv2
... 

import sys
sys.path.append("./build/")
import map_interface  # c++接口

# 创建c++接口对象
map_obj = map_interface.MapInterface()
# 调用函数
map_obj.CreateObj()
map_obj.SetCameraParas(<传入调用的参数>)
...

在使用python调用c++接口函数时, 如果python代码和.so不在同一个路径, 则需要指定生成的.so路径

import sys
sys.path.append("so的路径")

写在最后

在使用过程中, 有三个地方的变量命名需要一致,分别是上述代码中的: PYBIND11_MODULE接口函数中的map_interface, CMakeList中的add_library中的map_interface, python代码中的 import map_interface. 如下,

  1. PYBIND11_MODULE(map_interface, m)
  2. add_library(map_interface MODULE … )
  3. import map_interface # c++接口

这里,全部统一定义成map_interface. 否则在python调用时会出现如下等错误:

ModuleNotFoundError: No module named ’ ’

在运行python程序时,还会出现错误:

ImportError: dynamic module does not define module export function (PyInit_libxxxx)

这个问题仍然是这三个地方的命名不一致导致的,需要注意的是, linux 系统在使用 CMake 编译 c++ 动态链接库的时候, 会加一个前缀lib, 生成libmap_interface.so, 因此,这三个地方的命名应该修改如下:

  1. PYBIND11_MODULE(libmap_interface, m)
  2. add_library(map_interface MODULE … )
  3. import libmap_interface # c++接口

因为这个问题,也是调试了很久.

reference

  1. https://pybind11.readthedocs.io/en/stable/index.html
  2. https://zhuanlan.zhihu.com/p/80884925
  3. https://blog.csdn.net/weixin_41521681/article/details/106200017
<think>好的,我现在需要回答用户关于如何使用pybind11Python调用C++类的问题。用户已经提供了三个相关的引用内容,我需要结合这些引用,特别是引用[3],来构建一个清晰的示例。首先,我应该回顾pybind11的基本用法,确保自己理解如何绑定C++类到Python。 根据引用[3],用户提到需要创建一个C++类,比如DataParser,它有一些方法如mkdir_savefold()和work(),并且构造函数需要传入一个文件路径。所以,我需要构造一个类似的示例,展示如何用pybind11绑定这个类。 首先,我需要编写C++类的头文件和实现文件。例如,定义一个MyClass类,构造函数接受一个字符串参数,然后有若干方法,比如getName和processData。然后,在绑定代码中使用PYBIND11_MODULE来导出这个类,包括构造函数和方法。 接下来,编译这部分代码需要用到CMake或者直接使用命令行。引用[2]中提到了使用g++编译的命令,包括必要的编译选项和pybind11的包含路径。用户可能更倾向于使用CMake,因为引用[3]提到了CMAKE编译。因此,我需要提供一个简单的CMakeLists.txt示例,指导用户如何配置编译过程。 然后,测试部分也很重要。用户需要知道如何在Python中导入编译后的模块,创建类的实例,并调用其方法。这部分需要确保路径正确,并且示例代码能够直接运行。 此外,注意事项部分应该包括内存管理的问题,比如C++Python之间的对象所有权,以及异常处理。可能还需要提到如何传递不同类型的参数,比如const char*转换为std::string,这在引用[3]中也有涉及。 最后,根据用户的要求生成相关问题。这些问题应该围绕pybind11的高级用法、常见问题、性能优化等展开,帮助用户进一步学习。 需要确保步骤清晰,代码示例正确,并且符合用户提供的引用内容。特别是要引用正确的来源,比如在提到构造函数参数类型时引用[3],或者在编译命令时引用[2]。同时,所有代码块要正确格式化,数学公式如果有的话需要用正确的LaTeX格式,但本例中可能不需要数学公式。 总结步骤: 1. 创建C++类(头文件和实现) 2. 编写pybind11绑定代码 3. 配置CMakeLists.txt 4. 编译并生成Python模块 5. Python测试代码 6. 注意事项和相关问题 需要检查是否有遗漏的步骤,比如异常处理或参数转换的具体例子。另外,确保所有代码片段正确无误,特别是编译命令中的反引号是否正确转义,或者是否需要用更清晰的写法。</think>### 使用 pybind11 实现 Python 调用 C++ 类示例 以下是详细步骤和示例代码,展示如何通过 pybind11C++ 类暴露给 Python 使用: --- #### **1. 编写 C++ 类** 假设我们定义一个简单的 `DataProcessor` 类,包含构造函数、成员函数和属性: ```cpp // DataProcessor.h #include <string> class DataProcessor { public: DataProcessor(const std::string& file_path); // 构造函数 void mkdir_savefold(); // 创建文件夹 int process_data(int param); // 处理数据 std::string get_file_path() const; // 获取文件路径 private: std::string file_path_; }; ``` ```cpp // DataProcessor.cpp #include "DataProcessor.h" #include <iostream> #include <filesystem> DataProcessor::DataProcessor(const std::string& file_path) : file_path_(file_path) {} void DataProcessor::mkdir_savefold() { std::filesystem::create_directory(file_path_); std::cout << "文件夹已创建: " << file_path_ << std::endl; } int DataProcessor::process_data(int param) { return param * 2; } std::string DataProcessor::get_file_path() const { return file_path_; } ``` --- #### **2. 编写 pybind11 绑定代码** 创建 `bind.cpp` 文件,将 C++ 类绑定到 Python 模块: ```cpp #include <pybind11/pybind11.h> #include "DataProcessor.h" namespace py = pybind11; PYBIND11_MODULE(data_processor, m) { m.doc() = "Pybind11 示例:暴露 C++ 类给 Python"; // 绑定 DataProcessor 类 py::class_<DataProcessor>(m, "DataProcessor") .def(py::init<const std::string&>()) // 绑定构造函数 .def("mkdir_savefold", &DataProcessor::mkdir_savefold) .def("process_data", &DataProcessor::process_data) .def("get_file_path", &DataProcessor::get_file_path) .def_readwrite("file_path", &DataProcessor::file_path_); // 暴露成员变量(可选) } ``` --- #### **3. 配置 CMake 编译** 创建 `CMakeLists.txt` 文件,配置编译选项: ```cmake cmake_minimum_required(VERSION 3.12) project(data_processor) # 查找 pybind11 find_package(pybind11 REQUIRED) # 生成 Python 模块 add_subdirectory(pybind11) # 包含 pybind11 目录(假设已安装) pybind11_add_module(data_processor bind.cpp DataProcessor.cpp) ``` 编译命令: ```bash mkdir build && cd build cmake .. make ``` 成功后生成 `data_processor.cpython-xxx.so` 文件[^3]。 --- #### **4. Python 调用示例** 在 Python 中导入模块并使用 C++ 类: ```python import data_processor # 创建对象 processor = data_processor.DataProcessor("./output") # 调用方法 processor.mkdir_savefold() # 输出:文件夹已创建: ./output result = processor.process_data(5) print(result) # 输出:10 # 访问属性(如果暴露) print(processor.file_path) # 输出:./output ``` --- #### **注意事项** 1. **参数类型转换** C++ 的 `std::string` 会自动转换为 Python 的 `str`,基本类型(如 `int`)也会自动映射[^3]。 2. **内存管理** C++ 对象在 Python 中由 `pybind11` 自动管理生命周期,无需手动释放。 3. **异常处理** 若 C++ 代码抛出异常,pybind11 会将其转换为 Python 异常(如 `std::exception` 转为 `RuntimeError`)。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值