[Python]pybind11を使用してC++とPythonを連携する


タイトル通りのpybind11を使ってC/C++とPythonを連携してみます。
ぶっちゃけ何番煎じかわからない記事ですが自分のメモもかねてまとめていきます。
間違っていたりしたらマサカリ飛ばしていただけると喜んで頭かち割られに行きます。
実行環境はWindows10、VisualStudio2017、Python3.7です。





準備

pybind11の最新リポジトリをクローンしてきます。
か、もしくはMinicondaやAnacondaのようなフレームワークを用いて、中に用意されているPybind11を使います。
僕の場合は自分でビルドするのが面倒だったのでWindows用のMiniconda3をインストールして、その中のPybind11を使っています。
なので本記事もMiniconda3ベースで説明するので、自分でビルドとかライブラリのパスとかよくわからんという方は是非Minicondaをインストールして真似てやってみてください。
Miniconda
これのPython3.7版が僕と同じ環境です。





VisualStudioで新規プロジェクト作成

では、VisualStudioで新規プロジェクトを作成しましょう。VisualC++ -> EmptyProjectで作成します。
名前は任意のもので大丈夫です。僕は "pybindtest" という名前で作成することにします。

作成したら、一旦いらないフィルターたちを消して、プロジェクトのプロパティで各項目を設定していきます。
ConfigurationProperties -> General -> General -> TargetExtension を .pydに変更
ConfigurationProperties -> General -> ProjectDefaults -> ConfigurationType を DynamicLibrary(.dll)
ConfigurationProperties -> C/C++ -> AdditionalIncludeDirectories に pybind11を含むMinicondaのIncludeディレクトリパスを入れます。
ConfigurationProperties -> C/C++ -> CodeGeneration -> RuntimeLibrary を Multi-threadedDLL(/MD) に変更。
ConfigurationProperties -> Linker -> AdditionalLibraryDirectories にライブラリ(この場合Miniconda内のライブラリ)のパスを入れて...
ConfigurationProperties -> Linker -> Input -> AdditionalDependencies にライブラリの本体、python37.libというふうに名前を指定します。


以上でプロジェクトの設定は終了です。やってみるとあっけないですが、項目が多くて初めてやると結構戸惑いますね。
画像をいくつか用意したので、そちらも参考に設定してみてください。















実行スクリプトの準備

ではプロジェクトにmylibs.hとmylibs.cppというファイルを追加して、中身を以下のように実装してみましょう。

mylibs.h

	
#include <vector>

//------------------------------
//足し算
int add(int x, int y);


//------------------------------
//与えられた配列(int)の各値を2倍にする
std::vector<int> vec_double(std::vector<int> &v);


//------------------------------
//各配列の合計値をそれぞれ返す
std::vector<std::vector<int>> vec_add(std::vector<std::vector<int>> &vec);


//------------------------------
//クラスを使用する際の例
class POINT {
private:
    int x;
    int y;
public:
    int sum;

    POINT(int x, int y) { this->x = x; this->y = y; this->sum = x+y; }
    int X() { return x; }
    int Y() { return y; }
};

//クラスであるPOINT型を返り値にした例
POINT move_p(POINT p, int d);
	
	


mylibs.cpp

	

//pybind11をインクルード
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "mylibs.h"

using namespace std;

//------------------------------
int add(int x, int y) {
    return x+y;
}


//------------------------------
vector<int> vec_double(vector<int> &vec) {
    for(auto &v : vec) {
        v *= 2;
    }
    return vec;
}


//------------------------------
vector<vector<int>> vec_add(vector<vector<int>> &vec) {
    vector<vector<int>> result(vec.size(), vector<int>());
    for(int i = 0; i < vec.size(); i++) {
        int tmp = 0;
        for(auto &t : vec[i]) {
            tmp += t;
            result[i].push_back(tmp);
        }
    }
    return result;
}


//------------------------------
POINT move_p(POINT p, int d) {
    return POINT(p.X() + d, p.Y() + d);
}



//------------------------------
// python側で呼び出すための準備
//------------------------------
namespace py = pybind11;
//python側で使用するときのモジュール名になる。この場合"pybindtest"となる
PYBIND11_MODULE(pybindtest, m) {
	//モジュールの各種設定
	//このモジュールの説明
    m.doc() = "mylibs made by pybind11";
	//使用する関数を指定
    m.def("add", &add);
    m.def("vec_double", &vec_double);
    m.def("vec_add", &vec_add);

	//使用するクラスを指定
    py::class_<POINT>(m, "POINT")
        .def_readwrite("sum", &POINT::sum)
        .def(py::init<int, int>())
        .def("X", &POINT::X)
        .def("Y", &POINT::Y);

    m.def("move_p", &move_p);
}
	
	

上記をビルドして、ビルド先に.pydというファイルができていれば成功です。あとはこれを実際にPythonスクリプト側で呼び出してみましょう。






Python側のスクリプト作成

では先ほど作った.pydと同じ階層にtest.pyというPythonファイルを作ることにします。
中身は以下のようになっています。

test.py

	

# 作成したモジュールのインポート
import pybindtest

#--------------------
# 足し算
print(pybindtest.add(3, 45))

#--------------------
# 与えられた配列(int)の各値を2倍にする
print(pybindtest.vec_double([1, 2, 4, 5]))

#--------------------
# 各配列の合計値をそれぞれ返す
print(pybindtest.vec_add([
		[1, 1, 1],
		[1, 2, 3, 4, 5],
		[1, -1, 1, -1, 1, -1],
		[1, 2, 4, 8, 16, 32, 64]
	]))


#--------------------
# クラスを使用する際の例
p = pybindtest.POINT(5, 10)
print(p.X(), p.Y(), p.sum)

# クラスであるPOINT型を返り値にした例
q = pybindtest.move_p(p, 10)
print(q.X(), q.Y(), q.sum)
	
	

できたら実際にコンパイルしてみましょう。以下のような処理結果になっていれば大成功です。





お疲れさまでした

python便利すぎひん?ていうか言語自体が"みんなで楽しよう"の精神で作られているだけに融通が利くこと利くこと。
例えば画像処理とか、速度ではC/C++のが早いですが実際に実行環境用意してライブラリとリンクして...とかやっていると結構大変ですが、
Pythonの場合はパッケージインポート一つでできるのでとても便利ですね。
Gui環境とかはC/C++で作って、ちょっと細かい部分はPythonにぶん投げとかも選択肢として全然アリ。
自分が今やろうとしていることに対して、時間、お金、etcの状況を踏まえて最善だと思われる手をとっていきたいものです。


copyright @2019 Hiromitsu Iwanaka