zoukankan      html  css  js  c++  java
  • Python扩展(pybind11混编)

    • 背景介绍
      pybind11是一个基于C++11标准的模版库. 与Boost.Python类似, pybind11主要着眼于创建C++代码的Python封装, 并为其提供了一套轻量级的解决方案.

    • 安装与代码示例
      ①. 安装C++编译器(各平台略有不同, 支持C++11标准即可)
      ②. 安装cmake工具(官网下载安装即可, 用于组织C++工程)
      ③. 安装Python解释器(官网下载安装即可)
      ④. 安装pybind11库
           终端运行: pip3 install pybind11 
      ⑤. 获取pybind11库相关目录
           解释器内运行:

      import pybind11
      pybind11.get_cmake_dir()     # 获取cmake目录
      pybind11.get_include()       # 获取include目录

      ⑥. 待封装之C++源码
           本文以一个main.cpp源文件为例, 简要给出一个函数与一个类的封装示例, 代码如下,

      #include <string>
      #include <iostream>
      
      #include <pybind11/pybind11.h>
      #include <pybind11/eigen.h>
      
      
      int MyFunc(int i, int j)
      {
          return i + j;
      }
      
      class MyClass
      {
      public:
          MyClass(const std::string& msg) : msg_(msg) {}
      
          void printMsg()
          {
              std::cout << this->msg_ << std::endl;
          }
      
          Eigen::VectorXd add(const Eigen::VectorXd& lhs, const Eigen::VectorXd& rhs)
          {
              Eigen::VectorXd ret = lhs + rhs;
              return ret;
          }
      
          std::string msg_;
      };
      
      PYBIND11_MODULE(testlib, m)     // 此处设置模块名为testlib
      {
          m.doc() = "This is a test library";
          
          m.def("MyFunc", &MyFunc, "my first function",
              pybind11::arg("i") = 1, pybind11::arg("j") = 2);
      
          pybind11::class_<MyClass>(m, "MyClass")
              .def(pybind11::init<const std::string&>())
              .def("printMsg", &MyClass::printMsg)
              .def("add", &MyClass::add, pybind11::arg("lhs"), pybind11::arg("rhs"))
              .def_readwrite("msg_", &MyClass::msg_);
      }

      其中, MyFunc是待导出函数, MyClass是待导出类. 注意, 上例含eigen库(C++)与numpy库(Python)之映射, 无eigen库的小伙伴可以注释相关内容.

    • cmake工程示例
      配合上述main.cpp源文件, CMakeLists.txt文件内容如下,

      cmake_minimum_required(VERSION 3.15)
      
      set(CMAKE_BUILD_TYPE "Release")
      set(CMAKE_CXX_STANDARD 11)
      project(test_lib)
      
      set(test_srcs
      main.cpp
      )
      
      set(pybind11_DIR "/opt/homebrew/lib/python3.9/site-packages/pybind11/share/cmake/pybind11")   # 此处设置pybind11之cmake目录, 即: pybind11.get_cmake_dir()
      find_package(pybind11 REQUIRED)
      pybind11_add_module(testlib ${test_srcs})   # 此处设置模块名为testlib
      target_include_directories(testlib PUBLIC "/Users/xxhbdk/MyLibs/eigen-3.4.0")   # 此处附加包含eigen库目录

      当前工程结构如下,

    • 编译及效果展示
      终端运行如下命令编译Python动态库:

      mkdir build      # 创建编译目录
      cd build
      cmake ..
      make

      运行完成后, 笔者build目录下生成了Python动态库文件"testlib.cpython-39-darwin.so". 随后即可在Python环境中使用之, 测试效果如下,

      可以看到, 接口导出整体符合预期.

    • 注意事项
      ①. C++源文件中模块名需要与cmake工程文件中模块名保持一致;
      ②. 本文着重阐述pybind11配合cmake之通用流程, 具体API使用细节, 请大家参考官方文档等资料.

    • 参考文档
      ①. https://pybind11.readthedocs.io/en/stable/
      ②. https://cmake.org/cmake/help/latest/

    • 补充1(C++调用Python)
      ①. 待调用之Python源码
           本文以一个my_func.py源文件为例, 简要给出一个函数示例, 代码如下,
      def MyFunc(i, j):
          return i + j

      ②. 待编译之C++源码
           本文以一个main.cpp源文件为例, 简要给出一个C++调用Python函数之示例, 代码如下,

      #include <iostream>
      #include <pybind11/embed.h>
      
      
      int main()
      {
          pybind11::scoped_interpreter guard;     // 初始化python解释器
      
          pybind11::module my_func = pybind11::module::import("my_func");
          int i = 11;
          int j = 22;
          pybind11::object ret = my_func.attr("MyFunc")(i, j);
          int n = ret.cast<int>();
          std::cout << i << " + " << j << " = " << n << std::endl;
      }

      ③. cmake工程示例
           配合上述main.cpp源文件, CMakeLists.txt文件内容如下,

      cmake_minimum_required(VERSION 3.15)
      
      set(CMAKE_BUILD_TYPE "Release")
      set(CMAKE_CXX_STANDARD 11)
      project(test_cppInvokePy)
      
      set(test_srcs
      main.cpp
      )
      
      add_executable(main ${test_srcs})
      set(pybind11_DIR "/opt/homebrew/lib/python3.9/site-packages/pybind11/share/cmake/pybind11")   # 此处设置pybind11之cmake目录, 即: pybind11.get_cmake_dir()
      find_package(pybind11 REQUIRED)
      target_link_libraries(main PUBLIC pybind11::embed)

      ④. 编译及效果展示
           当前工程结构如下,

           终端运行如下命令编译C++可执行文件,

      mkdir build      # 创建编译目录
      cd build
      cmake ..
      make

      运行完成后, 笔者build目录下生成了可执行文件"main". 随后将Python文件my_func.py拷贝至此build目录下.终端运行可执行文件main, 测试效果如下,

      可以看到, 执行结果符合预期, Python模块调用成功.

    • 补充1 - 参考文档
      ①. https://www.yuque.com/u461675/pcadi1/hf4fha#e82c4d4d
    • 补充2(Global Interpreter Lock)
      当执行流从Python侧进入C++侧时, GIL总是持有的. 因此, 如果C++侧代码长时间运行, 且不释放GIL, 则Python侧多线程可能无法达到预期的运行效果(如: UI运行阻塞等).
      因此, 通过Python调用C++时, 若C++侧代码执行时间较长且具备Python侧多线程需求, 建议在C++代码入口处释放GIL.
      释放GIL之方法①(功能代码执行处)pybind11::gil_scoped_release release; 
      释放GIL之方法②(模块接口定义处)pybind11::call_guard<pybind11::gil_scoped_release>(); 
    • 补充2 - 注意事项
      ①. C++侧多线程不受GIL影响.
    • 补充2 - 参考文档
      ①. https://pybind11.readthedocs.io/en/stable/advanced/misc.html#global-interpreter-lock-gil
  • 相关阅读:
    WPF 打开文件 打开路径对话框
    WPF Button添加图片
    Delphi 正则表达式PerlRegEx
    解决Inet控件下载utf8网页乱码的问题
    Delphi程序结构
    VB 936(gb2312)URL编码与解码
    Chr 将一个有序数据转换为一个ANSI字符
    Delphi正则表达式使用方法(TPerlRegEx)
    Delphi类型转换
    Delphi 正则表达式TPerlRegEx 类的属性与方法
  • 原文地址:https://www.cnblogs.com/xxhbdk/p/15758686.html
Copyright © 2011-2022 走看看