C++ プログラムに pybind11 で Python を組み込んでみる

[pybind11](https://github.com/pybind/pybind11) を使用して、 C++ プログラムに Python を埋め込む方法です。

Python から C++ コードを呼ぶ方法は、以下が参考になるかと思います。

* [pybind11でC++の関数をpythonから使う](http://qiita.com/ignis_fatuus/items/c7523c0fe2bc2f415d50)
* [pybind11を使ってPythonからC++コードを実行する方法](http://myenigma.hatenablog.com/entry/2016/12/17/075812)

pybind11 では C++ コードから Python を呼ぶこともできるようなので、試してみました。

# Python 環境

Python3 を前提としていますが、呼び出す Python ライブラリの関数を変えれば、Python2 でも動くと思います。

# セットアップ

pybind11 はヘッダーオンリーなので、ヘッダーをコピーしてくるだけです。
後は、ソースコードに pybind11 をインクルードしてください。

```cpp
#include <pybind11/pybind11.h>
#include <pybind11/eval.h>
```

`Python.h` は直接インクルードしないほうがいいです。
後述しますが、 Windows 環境で問題が起こることがあります。

# C++ コードから Python コードを実行する

もととなるコードを以下のようなものです。

```cpp
#include <pybind11/eval.h>
#include <pybind11/pybind11.h>

#include <iostream>

namespace py = pybind11;

int main(int argc, char** argv) {
	wchar_t* program = Py_DecodeLocale(argv[0], nullptr);
	if (program == nullptr) {
		fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
		exit(1);
	}
	Py_SetProgramName(program);
	Py_Initialize();

	try {
		auto global = py::dict(py::module::import("__main__").attr("__dict__"));
		auto local = py::dict();
		// ここにコードを記述していく
	} catch (py::error_already_set& e) {
		std::cout << "Python error.\n" << e.what() << "\n";
	}

	Py_Finalize();
	PyMem_RawFree(program);

	return 0;
}

```

pybind11 は、 Python コードに失敗した際、`py::error_already_set` 例外を投げます。
例外をキャッチし、エラーを表示するようにしています。

`global` と `local` 変数のデストラクタは、 `Py_Finalize` が呼び出される前に実行しなければなりませんので注意してください。

## 簡単なサンプル

以下で説明されいてるコードを実行します。
https://docs.python.org/3/extending/embedding.html#very-high-level-embedding

```cpp
py::eval<py::eval_single_statement>("from time import time,ctime", global, local);
py::eval("print('Today is', ctime(time()))", global, local);
```

Python の1行文は `py::eval_single_statement` を使います。
式は `py::eval_expr` を使います。 (デフォルト)

## 変数を定義する

```cpp
local["x"] = 100;
py::eval<py::eval_single_statement>("y = 200", global, local);
py::eval("print('x + y =', x + y)", global, local); // x + y = 300
```

変数名をキーとして、値を代入するだけです。

## 値の取得

```cpp
py::eval<py::eval_single_statement>("z = x + y", global, local);
auto z = local["z"].cast<int>();
std::cout << "cpp: z = " << z << "\n"; // cpp: z = 300
```

変数名をキーとして値を取得し、変換したい型へ `cast` を呼び出します。

## 関数定義と呼び出し

```cpp
py::eval<py::eval_statements>(
	"def func_01():\n"
	"	print('func_01: call')\n",
	global, local);
auto func_01 = local["func_01"];
func_01(); // func_01: call
```

関数名をキーとして関数オブジェクトを取得し、 `operator ()` で呼び出します。
`call()` という関数もあるのですが、使用は推奨されていないようです。

## 関数へ引数を渡す

```cpp
py::eval<py::eval_statements>(
	"def func_02(a, b):\n"
	"	print('func_02: {} + {} = {}'.format(a, b, a + b))\n",
	global, local);
auto func_02 = local["func_02"];
func_02(123, 456);     // func_02: 123 + 456 = 579
func_02("abc", "efg"); // func_02: abc + efg = abcdefg
```

便利ですね。

## 関数の戻り値を取得する

```cpp
py::eval<py::eval_statements>(
	"def func_03(a, b):\n"
	"	return a + b\n",
	global, local);
auto func_03 = local["func_03"];
auto result = func_03(123, 456);
std::cout << "cpp: func_03 result "
	  << py::str(result).cast<std::string>() << "\n"; // cpp: func_03 result 579
```

戻り値に何が入っているかわからないと仮定し `py::str` で文字列に変換しています。

# プログラム内でモジュールを用意する

pybind11 でモジュールを作成し、 C++ のコードを呼ぶ方法は、上記リンクに詳しく書いてあります。

C++ プログラムにモジュールを埋め込むこともできます。
https://docs.python.org/3/extending/extending.html#the-module-s-method-table-and-initialization-function

## 初期化関数の用意

pybind11 のマクロ `PYBIND11_PLUGIN` を使用せずに、初期化関数を作成します。
定義しているクラスは、 C++ のプログラムと連携させるサンプルに使用します。

```cpp
class Job {
public:
	std::string GetName() const { return m_name; }
	void SetName(const std::string& name) { m_name = name; }

private:
	std::string m_name;
};

class Person {
public:
	std::string GetName() const { return m_name; }
	void SetName(const std::string& name) { m_name = name; }

	std::shared_ptr<Job> GetJob() const { return m_job; }
	void SetJob(const std::shared_ptr<Job>& job) { m_job = job; }

private:
	std::string m_name;
	std::shared_ptr<Job> m_job;
};

namespace py = pybind11;

PyMODINIT_FUNC PyInit_sample() {
	py::module m("sample", "pybind11 module sample.");

	py::class_<Job, std::shared_ptr<Job>> job(m, "Job");
	job.def(py::init<>()).def_property("name", &Job::GetName, &Job::SetName);

	py::class_<Person, std::shared_ptr<Person>> person(m, "Person");
	person.def(py::init<>())
		.def_property("name", &Person::GetName, &Person::SetName)
		.def_property("job", &Person::GetJob, &Person::SetJob);

	return m.ptr();
}
```

## 初期化関数の登録

```cpp
int main(int argc, char** argv) {
	wchar_t* program = Py_DecodeLocale(argv[0], nullptr);
	if (program == nullptr) {
		fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
		exit(1);
	}

	PyImport_AppendInittab("sample03", PyInit_sample03);

	Py_SetProgramName(program);
	Py_Initialize();

	py::module::import("sample03");
```

`PyImport_AppendInittab` は `Py_Initialize` より前に呼ぶようにしてください。

## C++ プログラムとの連携

```cpp
try {
	auto global = py::dict(py::module::import("__main__").attr("__dict__"));

	// Python で関数を作成 (Name=Hoge, Job=Teacher)
	py::eval<py::eval_statements>(
		"import sample\n"
		"def initialize_person(p):\n"
		"	job = sample.Job()\n"
		"	job.name = 'Teacher'\n"
		"	p.name = 'Hoge'\n"
		"	p.job = job\n",
		global);
	{
		auto person = std::make_shared<Person>();
		global["initialize_person"](person);
		std::cout << "Name : " << person->GetName() << "\n";           // Name : Hoge 
		std::cout << "Job  : " << person->GetJob()->GetName() << "\n"; // Job  : Teacher
	}

	// 関数を変更 (Name=Foo, Job=Programmer)
	py::eval<py::eval_statements>(
		"import sample\n"
		"def initialize_person(p):\n"
		"	job = sample.Job()\n"
		"	job.name = 'Programmer'\n"
		"	p.name = 'Foo'\n"
		"	p.job = job\n",
		global);
	{
		auto person = std::make_shared<Person>();
		global["initialize_person"](person);
		std::cout << "Name : " << person->GetName() << "\n";           // Name : Foo
		std::cout << "Job  : " << person->GetJob()->GetName() << "\n"; // Job  : Programmer
	}
} catch (py::error_already_set& e) {
	std::cout << "Python error.\n" << e.what() << "\n";
}
```

C++ 側でオブジェクトを用意し、 Python 側で値を設定しています。

# Windows 環境の注意

Windows 環境で `#include <python.h>` を行うと、でバックビルド時にデバッグ版のライブラリをリンクするようになります。
デバッグ版はインストール時に追加しておかなければなりません。
また、Python のデバッグ版は、モジュール等に「_d」をつけなければなりません。

pybind11 の `<pybind11/pybind11.h>` では、デバッグビルド時でも、リリース版のライブラリをリンクするような設定が行われます。
`<Python.h>` を先にインクルードすると意味がなくなるので、注意してください。

## Windows 環境で CMake を使用する際の注意

CMake には Python 環境を見つける `FindPythonLibs` があります。
これを使用すると、 Windows 環境ではデバッグビルド時に、デバッグ版のライブラリをリンクするようになってしまいます。
その状態で pybind11 を使用すると、リンクできなくなるので注意が必要です。

CMake を使用する場合は、 pybind11 に付属している `PythonLibsNew` を使用するか、 `pybind11Tools` を使用してください。

```
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/pybind11/cmake")
set(Python_ADDITIONAL_VERSIONS 3.7 3.6 3.5 3.4)
find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED)
```

```
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/pybind11/cmake")
include(pybind11Tools)
```

`pybind11Tools` には、モジュールを簡単に定義できる `pybind11_add_module` があります。
これを使えば、作成されるモジュールの拡張子を `.pyd` にしてくれます。

```
pybind11_add_module(module_sample module_sample.cpp)
```