LoginSignup
11
6

More than 3 years have passed since last update.

libclangのC++構文木からBoost.Python/pybind11/Embind定義処理を自動生成する

Last updated at Posted at 2017-05-18

Boost.Python

Boostライブラリの中にC++とPythonを連携させるBoost.Pythonがあります。

このライブラリを使用するとPython側でC++の処理を呼び出したり、C++側でPythonの処理を呼び出すことができるようになります。
これにより、Pythonでは現実的ではない重い処理をC++で殴って力ずくで解決することが可能になります。

C++からPythonの処理を呼び出す際はインタプリタに入力する要領で逐次コードを渡せばよいのですが(さらっと書きましたが、元々C APIのコードをC++式に扱えるようにラップされており、非常に狂気(boost)を感じます)、
PythonからC++の処理を呼び出すにはC++の関数定義等を事前に登録する必要があります。(類似のものとしてSWIGとかsipがありますが、定義ファイルがC++で完結しているのがとってもBoost。)

簡単なコードであれば数行の定義で済みます。
例えばこの python-editdistance は編集距離を算出するライブラリですが、C++で書かれたeditdistance関数をeval関数として登録する処理は次のような一行になります。

boost::python::def("eval", &editdistance);

しかし、クラス定義の公開になるとメソッドの数だけ定義が増え、ライブラリとしてまとまった数のクラスを相手にすると気が遠くなる作業になります。

これらのライブラリはPythonでXerces-C++とその派生ライブラリ(XQuery実装, XML暗号・署名)をBoost.Pythonで記述したライブラリになります。

たとえばDOMElementクラスの登録処理を見ると、
https://github.com/mugwort-rc/pyXerces/blob/master/src/dom/DOMElement.cpp
このように大変長い定義を記述する必要があります。

一つだけならあまり大した労力ではないですが、DOMDocument,DOMAttribute,DOMNode...とPython側で触れるクラスを増やしていくと非常に退屈な作業に突入します。
(ライブラリ全体の把握のための写経としては比較的生産的であるので、悪いことではないと思います。……思いたい。)

libclang

ところで、Pythonには標準でastモジュールがあり、構文木を直接触ることができるのでコードから別のコードを簡単に(?)作成することが出来ます。
Python2のコードをPython3に変換する2to3などはそうした方法で作られています。
(余談ですが、PythonコードからC++コードを生成するライブラリがこちらになります。)

同様にC++のヘッダーファイルに書かれた定義の情報だけ引き抜いてBoost.Pythonの登録処理に使えないかと、DoxygenやGCC treeの情報などを調べていたのですがあまり情報が充実しているとはいえませんでした。

それから数年経って再びC++の構文木について調べていたところlibclangがC++の構文木を提供しているというではありませんか!

pypp

そんなわけで作成したのがpyppになります。

ヘッダファイルを渡すと、libclangで構文木を取得し、登録処理を記述した関数を生成します。

// helloworld.hpp
void helloworld()
{
    std::cout << "Hello World!" << std::endl;
}
python -m pypp helloworld.hpp
// generate by pypp
// original source code: helloworld.hpp

#include <boost/python.hpp>

#include "helloworld.hpp"


void init_helloworld_hpp()
{
    boost::python::def("helloworld", &helloworld);
}

手作業で定義を記述する際、特に仮想関数や純粋仮想関数の登録が非常に作業的で退屈だったのですが、pyppは全て自動でやってくれます。

// virtual_method.hpp
class VirtualMethod {
public:
    virtual ~VirtualMethod()
    {}

    virtual void v(int hoge)
    {}

    virtual int p(int x, int y) const = 0;

};
python -m pypp virtual_method.hpp
// generate by pypp
// original source code: virtual_method.hpp

#include <boost/python.hpp>

#include "virtual_method.hpp"


class VirtualMethodWrapper : public VirtualMethod
{
public:
        void v (int hoge) {
            if ( auto v = this->get_override("v") ) {
                v(hoge);
            } else {
                VirtualMethod::v(hoge);
            }
        }
        int p (int x, int y) const {
            return this->get_override("p")(x, y);
        }
};


void init_samples_virtual_method_hpp() {
    boost::python::class_<VirtualMethodWrapper>("VirtualMethod", boost::python::no_init)
        .def("v", &VirtualMethod::v)
        .def("p", boost::python::pure_virtual(&VirtualMethod::p))
        ;
}

これを使えばあんなライブラリやこんなライブラリでPythonとC++を相互に行き交うプログラムが書けるようになるのではないでしょうか。

実際のところ単純な定義にしか対応していないので、まだまだ道のりが果てしなく長いですが、同様にC++とPythonをズブズブ(♀×♀)な関係にしたいという稀有な方がおられましたらプルリクエスト投げていただけると幸いです。

GitHub - mugwort-rc/pypp: Boost.Python generator.

pybind11 / Embind対応

pybind11やEmbindも類似の公開インターフェイスを定義していたため、出力形式を変えるオプションを追加しました。

pybind11

python -m pypp helloworld.hpp --experimental-pybind11
// generate by pypp
// original source code: helloworld.hpp

#include <pybind11/pybind11.h>

#include "helloworld.hpp"


void init_helloworld_hpp(pybind11::module scope)
{
    scope.def("helloworld", &helloworld);
}

Embind

python -m pypp helloworld.hpp --experimental-embind
// generate by pypp
// original source code: helloworld.hpp

#include <emscripten/bind.h>

#include "helloworld.hpp"


void init_helloworld_hpp()
{
    emscripten::function("helloworld", &helloworld);
}

参考

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6