14

Pybind11 理解

 4 years ago
source link: http://satanwoo.github.io/2020/08/29/Pybind11/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Pybind11 源码分析

MNN 社区上提供了通过 Python 使用 MNN 的方式,具体可以见:

https://github.com/alibaba/MNN/tree/master/pymnn/src

我们通过 Pybind11 来提供比较优雅的桥接方式。由于 Pybind 11 是一层抽象 C++ 到 Python 桥接的库,上层封装了很多难以理解的细节和流程,本文就带大家抽丝剥茧一下。

关于 Python 桥接,如果你不是很了解,那么在阅读本文之前,请记住如下两句话:

  • 代码必须要以传统的 module 添加类的方式来进行,相关代码是:

    • PyInitModule
    • PyModule_AddObject(xxx)
  • Python 没办法直接编写 C++ Extension,都是通过再包一层 C 方法 (Python 自身的 C 接口)的方式来进行。

换句话说,不管什么,都需要以一个 module 为载体,且需要name

创建模块方法

因此我们的入口就在 PYBIND11_MODULE(name, variable)

全部的宏就是:

#define PYBIND11_MODULE(name, variable) \ 【1】static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \ 【2】PYBIND11_PLUGIN_IMPL(name) { \ 【3】PYBIND11_CHECK_PYTHON_VERSION \ auto m = pybind11::module(PYBIND11_TOSTRING(name)); \ try { \ PYBIND11_CONCAT(pybind11_init_, name)(m); \ return m.ptr(); \ } PYBIND11_CATCH_INIT_EXCEPTIONS \ } \ void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &variable)

比较晦涩,我们以 mnn 来举例,帮宏全部展开来探索下。

  • 首先声明一个函数 static void pybind11_init_mnn(pybind11::module &);,在宏的最后会有实现。

  • PYBIND11_PLUGIN_IMPL(mnn)这一步是必要的一步,所有 Python 的扩展模块都需要声明对应的初始化方法,用于注册被调用。

    import mnn 就会调用 init_mnn 这样的方法。

    截屏2020-07-27 下午5.33.02.png
  • initmnn 里面定义了 pybind11_init_wrapper 这是真正的初始化实现。(注意 static 声明)

  • pybind11_init_wrapper 里面真正的实现分为几步:

    • 构造一个 module 对象(class module),auto m = pybind11::module(PYBIND11_TOSTRING(name));

    • 调用之前声明的 pybind11_init_mnn 函数,传入module对象,仿造 Python C Extension 的方式来添加方法、定义、变量等。 pybind11_init_mnn(m)

    • 实现 pybind11_init_mnn {xxx}

  • 所以一切核心的关键就是在 class module,抛开繁杂的东西:

    • 第一步,创建一个 CPython 中的 module object,见:

      截屏2020-07-27 下午6.14.57.png

      所以,本质上无论 Pybind11 在干什么,都是利用 CPython 底层的技术在那里操作。

    • 把对应的方法加入到这个 CPython Module 中,如下:

      截屏2020-07-27 下午6.17.23.png

      截屏2020-07-27 下午6.17.30.png

创建类的方法相对难一点,但是也不难理解,我们还是找到根源 class class_(其实一切只要理解 Pybind11 是用 C++ 去模拟 CPython 的流程就行了)

找到对应的构造函数,首先从函数签名上我们就能窥探一些东西:

截屏2020-07-27 下午6.38.07.png
  • scope 对应类所属的模块。
  • name 就是类名。
  • Extra 就是 C++ 模版机制对应的真正类。

看起来上面和对应的 Python 类型初始化没关系,来看看是不是 generic_type::initialize 造成的。

截屏2020-07-27 下午6.44.13.png
  • make_new_python_type 这名字一看就很符合,哈哈,点进去一看,果然是类型初始化流程。
截屏2020-07-27 下午6.46.15.png

简要概括下添加方法的实现。

  • 生成模版化的初始化模块方法,即 Wrapper 方法。

  • 提供一个仿造 Python Module 对象(便于 C++ 编写 / 使用智能指针管理引用计数),在对应的自定义函数里面添加对应 Module 的实现。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK