python_cpp_interaction

1. Python调用C++

python调用C++需要编译出 python 的 module so,是一种区别于普通依赖库的动态库,可以通过pybind11 中的cmake 函数进行生成。 - step 1 首先要配置好pybind11 的依赖,其是纯头文件库,在CMakeLists.txt 中可以通过CPM 拉取,除此之外还需要依赖的 eigen, pybind11中有一些矩阵计算需要用到的。

1
2
3
4
5
6
7
8
9
10
CPMAddPackage(
NAME pybind11
GIT_TAG v2.9
GIT_REPOSITORY https://github.com/pybind/pybind11.git
)

CPMAddPackage(
NAME eigen
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz
)
除了通过CPM 拉取源码外,也可以通过编译源码然后install 到 /usr 的方式;应该也可以通过apt 安装。

  • step 2 生成扩展包格式的动态库需要使用pybind11 提供的 pybind11_add_module 函数,然后去链接pybind11、和 python 库,以及引用头文件
    1
    2
    3
    4
    5
    6
    7
    pybind11_add_module(cpp2py_binding src/cpp2py_binding.cpp)
    target_link_libraries(cpp2py_binding PUBLIC python3.8)
    target_include_directories(cpp2py_binding
    PRIVATE ${pybind11_SOURCE_DIR}/include
    PRIVATE ${eigen_SOURCE_DIR}
    PRIVATE /usr/include/python3.8
    )
  • step 3 编写 src/cpp2py_binding.cpp,其中实现提供给python 调用的接口。如果有C++中定义的结构体需要传递给python,则需要实现类型转换器,示例如下,get_lane_instance_from_cpp 是暴漏给 python 的接口,type_caster 是python 接收到 c++ 中的 LaneInstance 类型时进行的转换,其由解释器调用,无需用户操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #include <pybind11/pybind11.h>
    #include <iostream>

    namespace py = pybind11;

    class LaneInstance {
    public:
    int id;

    LaneInstance() : id(0) {}

    void print() const {
    std::cout << "Lane ID: " << id << std::endl;
    }
    };

    LaneInstance get_lane_instance_from_cpp() {
    LaneInstance lane;
    // 填充 lane 对象的数据
    lane.id = 99;
    return lane;
    }

    // 自定义类型转换器
    namespace pybind11 { namespace detail {
    template <>
    struct type_caster<LaneInstance> {
    public:
    PYBIND11_TYPE_CASTER(LaneInstance, _("LaneInstance"));

    bool load(handle src, bool convert) {
    // 从 Python 对象转换到 C++ 对象(这里不实现,因为我们只关心从 C++ 到 Python 的转换)
    return false;
    }

    static handle cast(const LaneInstance &src, return_value_policy policy, handle parent) {
    // 将 C++ 对象转换为 Python 字典
    pybind11::dict dict;
    dict["id"] = src.id;
    return dict.release();
    }
    };
    }} // namespace pybind11::detail


    PYBIND11_MODULE(cpp2py_binding, m) {
    m.def("get_lane_instance", &get_lane_instance_from_cpp, "Get a LaneInstance object from C++");
    }
  • step 4 编译后会有生成一个 cpp2py_binding.cpython-38-x86_64-linux-gnu.so 的东西,其作为python 的扩展包由 python 使用,需要在启动 python 前将 .so 的目录路径放在 PYTHONPATH 环境变量中,然后就可以在 python 中调用 get_lane_instance 了。注意 get_lane_instance 是python 中的接口,其背后的实现是c++ 中的get_lane_instance_from_cpp。
1
2
3
import cpp2py_binding as cpp
lane_instance = cpp.get_lane_instance()
print("id: "lane_instance.id)
  • note
  1. 如果是C++中的自定义数据类型,一定要有类型转换器,不然就会报 TypeError: Unregistered type
  2. cpp文件中定义的 PYBIND11_MODULE 名称 一定要和 CMakeLists.txt pybind11_add_module 名称一致,不然会报 ImportError: dynamic module does not define module export function

2. C++调用Python

简单来说,就是通过pybind11中的数据类型,将C++类型转化为python 类型,比如 pybind11::dict、pybind11::list,也有一些cast 操作

  • step 1

    1
    2
    3
    4
    5
    6
    7
    8
    # c++ call python script
    add_executable(pyprocess src/pyprocess.cpp)
    target_include_directories(pyprocess
    PRIVATE ${pybind11_SOURCE_DIR}/include
    PRIVATE ${eigen_SOURCE_DIR}
    PRIVATE /usr/include/python3.8
    )
    target_link_libraries(pyprocess PUBLIC python3.8)

  • step 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #include <pybind11/pybind11.h>
    #include <pybind11/embed.h>
    #include <iostream>

    namespace py = pybind11;

    class LaneInstance {
    public:
    int id;

    LaneInstance() : id(0) {}

    void print() const {
    std::cout << "Lane ID: " << id << std::endl;
    }
    };
    int py_get_lane_instances(LaneInstance lane) {
    #if 0
    auto start_time = std::chrono::high_resolution_clock::now();
    std::shared_ptr<void> time_cost(nullptr, [start_time](void* p) {
    auto end_time = std::chrono::high_resolution_clock::now();
    std::cout << "py_get_valid_other_instances time cost: "
    << std::chrono::duration_cast<std::chrono::microseconds>(
    end_time - start_time).count()
    << " us" << std::endl;
    });
    #endif
    static py::scoped_interpreter py_interpreter_init = []() {
    py::scoped_interpreter guard {};
    return guard;
    }();

    // 准备参数
    // py::dict py_lane_instance = cpp2py_lane_instance(lane);
    py::dict py_lane_instance;
    py_lane_instance["id"] = lane.id; // py::dict 的key 只支持字符串类型

    py::int_ out_ret = -1;
    // 调用 Python 函数
    out_ret = py::module_::import("callee").attr("get_lane_instances")(
    py_lane_instance
    );

    std::cout << "in c++, out_ret: " << out_ret.cast<int>() << std::endl;

    return 0;
    }

    int main() {
    LaneInstance lane;
    lane.id = 999;
    py_get_lane_instances(lane);

    return 0;
    }

  • step 3

    1
    2
    3
    4
    5
    6
    7

    def get_lane_instances(lane_instance):
    print("in python, lane_instance: ", lane_instance)
    out_ret = 999
    print("in python out_ret: ", out_ret)
    return out_ret


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!