Pybind11でC++関数をPythonから実行する（Windows & Visual Studio Codeな人向け） 環境構築編

Pybind11を使ってC++の関数をPythonから呼び出せるようにする環境を構築する方法を説明します。WindowsかつVisual Studio Code（以下、VSCode）を使っている人向けの記事があまりなかったので、いろいろ調べたことをまとめておきます。
デバッグ編は[こちら](https://qiita.com/k_maki/items/75bf05e4159be92c0bd9)。

# 0. 環境
以下の環境で確認しています。CMake以下のインストール方法はこの記事で説明します。

- OS: Windows 10 Home 22H2
- エディター: Visual Studio Code 1.83.1 
- Python: 3.11
- この記事でインストールするもの
  - CMake: 3.19.4
  - msvc: 2019 Release
  - pybind11: 2.11.1

# 1. 環境構築
## 1.1. CMakeのインストール
[CMake](https://cmake.org/)とは、C++等をビルドする際の設定を様々なコンパイラーで利用できるようにするツールです。が、私自身よく分かっていないので、詳しくは[こちら](https://kamino.hatenablog.com/entry/cmake_tutorial1)をみてください。

[ダウンロード ページ](https://cmake.org/download/)からWindows win64-x64 Installerをダウンロードし、インストールします。

また、VSCode用の拡張機能[CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools)もVSCodeにインストールしておきます。端末がオフラインの場合は[こちら](https://qiita.com/ss_tom_jp/items/5977e4f16d78b8ca7cc8)を参考にしてください。


## 1.2. msvcのインストール
msvcとは、Microsoft製C++コンパイラです。Visual StudioのC++コンパイラー部分です。

インストール方法は、端末がオンラインの場合は[こちら](https://www.python.jp/install/windows/install_vstools2017.html)を、オフラインの場合は[こちら](https://qiita.com/k_maki/items/a2f08c0d4130d907e04d)をご覧ください。

:::note warn
**注意**
必須コンポーネントだけでは、CMakeのConfigureで失敗します。 **推奨コンポーネントも必要です。**

「必須」か「推奨」かは以下のドキュメントで「依存関係の種類」で確認可能です。
https://learn.microsoft.com/ja-jp/visualstudio/install/workload-component-id-vs-build-tools?#desktop-development-with-c

:::

## 1.3. Pybind11のインストール
オンライン環境なら、`pip`でインストールします。

```powershell:Powershell
> pip install pybind11
```

オフライン環境の場合は、
[PyPIのダウンロード ページ](https://pypi.org/project/pybind11/#files)から`pybind11-x.y.z-py2.py3-none-any.wh`(x, y, zは数字)をダウンロードしてから、`pip`でインストールします。

```powershell:Powershell
> pip install ダウンロード先/pybind11-2.5.0-py2.py3-none-any.whl  #2.5.0部分は適宜読み替え 
```

後で使うので、公式のサンプルもダウンロードします。オンライン環境（かつGitをインストール済み）なら、Powershell上でダウンロードしたい場所まで移動し、以下のコマンドでダウンロードします:

```powershell:Powershell
git clone --recursive https://github.com/pybind/cmake_example.git
```

オフライン環境の場合は、[cmake_example](https://github.com/pybind/cmake_example)から、`↓ Code ▾`（緑色） -> `Download ZIP`でダウンロード、また、`pybind11`フォルダーが空になっているので、[pybind11](https://github.com/pybind/pybind11)からも同様にダウンロードし、中身を`pybind11`フォルダーに展開します。

~~**【重要】`cmake_example/pybind11/include/pybind11`以下のファイルを、`C:\ProgramData\Anaconda3\include\pybind11`以下のファイルで置き換えます。**置き換える前のファイルは古いみたいで、コンパイルは通りますが、`warning: 'void PyThread_delete_key_value(int)' is deprecated [-Wdeprecated-declarations]`というワーニングが大量に出ます。~~（2021/1修正。現時点ではワーニングが出ないため削除。）


# 2. やってみるさ（ ■ 一 ■）
先ほどダウンロードした`cmake_exmple`フォルダーから、`pybind11`、`src`、`CMakeLists.txt`を適当なフォルダー（以下、`project_root`とします）にコピーします。~~なお、パス名に日本語が含まれるとConfigureとGenarateができないため、半角英数字（スペースと記号もOK）にしてください。~~（コンパイラーにg++を使うと日本語はダメだけど、msvcならOKみたい）

```:フォルダー構成
project_root
├ .vscode
│ └ settings.json
├ pybind11
├ src
│ └ main.cpp
└ CMakeLists.txt
```

`setting.json`でPythoのバージョンを指定する。以下はPython 3.11の例。

```cjson:project_root/.vscode/settings.json
{
    "cmake.configureArgs": [
        "-DPYBIND11_FINDPYTHON:BOOL=OFF", //OFFにしないとPYTHON_EXECUTABLEが無視される
        "-DPYTHON_EXECUTABLE:FILEPATH=C:/Program Files/Python311/python.exe"
    ]
}
```

コンパイル エラー（なぜかはわからない）になるため、`main.cpp`の`#ifdef VERSION_INFO`～`#endif`部分をコメントアウトor削除。

```cpp:project_root/src/main.cpp
#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

namespace py = pybind11;

PYBIND11_MODULE(cmake_example, m) {
    (略)
    m.def("add", &add, R"pbdoc(
        Add two numbers

        Some other explanation about the add function.
    )pbdoc");
   (略)
//コンパイルエラー（なぜかはわからない）になるので以下をコメントアウトor削除
//#ifdef VERSION_INFO
//    m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
//#else
//    m.attr("__version__") = "dev";
//#endif
}
```

```cmake:project_root/CMakeLists.txt
cmake_minimum_required(VERSION 3.4...3.18)
project(cmake_example)

add_subdirectory(pybind11)
pybind11_add_module(cmake_example src/main.cpp)

# EXAMPLE_VERSION_INFO is defined by setup.py and passed into the C++ code as a
# define (VERSION_INFO) here.
target_compile_definitions(cmake_example PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO})

```

`project_root`をVSCodeで開き、コマンド パレット（`Ctrl+p`）からCMake Toolsを使ってビルドします。

1. `Cmake: Select a Kit`で`Visal Studio Build Tools 2019 Release - amd64`を選択
1. `Cmake: Select Variant`で`Debug`を選択
1. `Cmake: Build`を実行

なお、上記はVSCodeのステータスバー（下部の青いバー）をクリックして設定することもできます。

成功すれば、`./build/Debug`フォルダーに`cmake_example.cp37-win_amd64.pyd`が作成されます。VSCodeで`Ctrl+@`でターミナルを開き、試してみましょう。

```Powershell:Powershell
> python           # Pythonを起動
>>> from build.Debug.cmake_example import add 
>>> add(1,2)
3
>>> exit()         # Powershellに戻る
```

`add`関数を呼び出せていれば成功です。

`add`関数のデバッグ方法は[デバッグ編](https://qiita.com/k_maki/items/75bf05e4159be92c0bd9)にて説明していますのでそちらもご覧ください。

# 3. トラブルシューティング
## 3.1. ConfigureとGenarate
### 3.1.1 Unknown CMake command "pybind11_add_module".
pybind11フォルダーが空になっていませんか？`git clone`時に`--recursive`オプションを付け忘れるか、zipダウンロードの場合は[pybind11](https://github.com/pybind/pybind11)のダウンロード & 展開を忘れるとそのようになります。

### ~~3.1.2. `cmake -G "MinGW Makefiles" ..`ができない~~
~~パス名に日本語が含まれるとConfigureとGenarateができません。パス名は大人しく半角英数字（スペースと記号もOK）にしましょう。日本語に対応して欲しいですね。~~（コンパイラーにg++を使うと日本語はダメだけど、msvcならOKみたい）

## 3.2. ビルド時
### 3.2.1. error: '::hypot' has not been declared
2018年12月以前にリリースされたPythonの各バージョンを使っていると、このエラーでビルドが失敗します。原因はpyconfig.hで、次のように修正します。

```cpp:Pythonインストール先/include/pyconfig.h
  #define COMPILER "[gcc]"
- #define hypot _hypot     ←この行を削除する。
  #define PY_LONG_LONG long long
  #define PY_LLONG_MIN LLONG_MIN
  #define PY_LLONG_MAX LLONG_MAX
```

この問題は[公式でも議論](https://bugs.python.org/issue11566)されており、[プルリクエスト](https://github.com/python/cpython/pull/880)後、マージされています（実際の[コミット](https://github.com/python/cpython/commit/87667c54c6650751c5d7bf7b9e465c8c4af45f71)）。

### ~~3.2.2. warning: 'void PyThread_delete_key_value(int)' is deprecated [-Wdeprecated-declarations]~~
~~上で書きましたが、`cmake_example/pybind11/include/pybind11`以下のファイルを、`C:\ProgramData\Anaconda3\include\pybind11`以下のファイルで置き換えます。置き換える前のファイルは古いみたいで、コンパイルは通りますが、`warning: 'void PyThread_delete_key_value(int)' is deprecated [-Wdeprecated-declarations]`というワーニングが大量に出ます。~~（2021/1修正。現時点ではワーニングが出ないため削除。）

# 4. おわりに
最後まで読んでいただき、ありがとうございます。
この記事では公式のサンプルを使っただけでしたが、他の人の記事を参考に、`main.cpp`や`CMakeLists.txt`を修正してみてください。
C++に疎いため、たったこれだけの事が出来るようになるまでに時間がかかりました。これからC++デビューしたいと思います。


# 参考
以下の記事を参考にさせていただきました。
## CMakeの解説
- [勝手に作るCMake入門](https://kamino.hatenablog.com/entry/cmake_tutorial1)
- [Cmakeの使い方（その1）](https://qiita.com/shohirose/items/45fb49c6b429e8b204ac#cmake%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)
- [CMakeを使ってみた（2）もう少しまともなプロジェクト](https://www.wagavulin.jp/entry/2011/11/27/222642)

## CMake Toolsの使い方
- [CMake Tools for Visual Studio Code](https://vector-of-bool.github.io/docs/vscode-cmake-tools/)

## Pybind11の使い方
- [公式のサンプル](https://github.com/pybind/cmake_example)
- [pybind11でC++の関数をpythonから使う](https://qiita.com/ignis_fatuus/items/c7523c0fe2bc2f415d50)
- [pybind11を使ってPythonからC++コードを実行する方法](https://myenigma.hatenablog.com/entry/2016/12/17/075812)