Visual Studio 2022 を用いて CMake プロジェクトから pybind11 を利用

Visual Studio 2022 を使用して C++ のプロジェクトを CMake で作成する方法を紹介しています。今回は find_package() によって pybind11 (https://pybind11.readthedocs.io/en/stable/index.html) を利用するやり方を報告します。CMake で作成した C++ の関数やクラスが python から利用できます。

# 環境
* 開発環境として、Visual Studio 2022（Community）の「C++デスクトップ環境」がインストールされていること。
* python がインストールされ、コマンドラインから ```> python``` もしくは ```> py``` で実行できること。複数のバージョンがインストールされていても構わない。
* CMake (最新版を推奨）がインストールされ、コマンドラインから cmake が実行できること。[^2]
* 環境変数の設定ができること。[^3]
* コマンドラインでの作業には「Developer Command Prompt for VS 2022」を使用すること。[^4]
[^1]: Git from the command line and also from 3rd-party software にチェックを入れてインストールする。
[^2]: [https://cmake.org/](https://cmake.org/)  にある Latest Release の Windows x64 Installer を実行して 「Add CMake to the system PATH for all users」にチェックを入れてインストールする。
[^3]: 環境変数は、設定の「システム」＞「バージョン情報」＞「システムの詳細設定」から変更できます。```> set``` で環境変数の一覧が見れるので、設定が反映されていない場合には一旦 Windows からサインアウトしてサインインし直してください。
[^4]: スタートメニューの「Visual Studio 2022」フォルダ内にショートカットがあります。「ターミナル」の設定を変更し、「既定のプロファイル」に「Developer Command Prompt for VS 2022」を登録しておくと、エクスプローラーで作業するディレクトリを開き、コンテキストメニューから「ターミナルで開く」ことができて便利です。

# pybind11 のインストール
コマンドラインから
```> pip install pybind11```
でインストールできる。複数のバージョンの python がある場合には
```> py -3.8 -m pip install pybind11```
のようにして、使用する全てのバージョンにインストールする。

インストールが終わったら、python でモジュールのインストール場所を確認する。
```
> python
Python 3.10.8 (tags/v3.10.8:aaaf517, Oct 11 2022, 16:50:30) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import pybind11
>>> pybind11.__path__
['C:\\Users\\*****\\AppData\\Roaming\\Python\\Python310\\site-packages\\pybind11']
>>> quit()
```
バージョンを指定する場合には ```> py -3.8 ``` などで起動して同様にする。

表示された場所をエクスプローラーで開き（```AppData``` は隠しフォルダなので、隠しファイルが表示されるように設定を変更する）、その下にあるフォルダで pybind11Config.cmake を含むものを見つける。（上の例では ```C:\Users\*****\AppData\Roaming\Python\Python310\site-packages\pybind11\share\cmake\pybind11```）見つかったフォルダを環境変数 ```pybind11_DIR``` の値として設定する。

# プロジェクトの作成
公式ドキュメントの関数の例

https://pybind11.readthedocs.io/en/stable/basics.html

で説明する。適当な作業用ディレクトリに
```example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring
    m.def("add", &add, "A function that adds two numbers");
    m.def("add1", &add, "A function that adds two numbers",
        py::arg("i")=1, py::arg("j")=2);
    
    using namespace py::literals; 
    m.def("add2", &add, "i"_a=1, "j"_a=2);

    m.attr("the_answer") = 42;
    auto world = py::cast("World");
    m.attr("what") = world;
}
```
と
```CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(test_cmake CXX)

set(CMAKE_CXX_STANDARD 14) 

find_package(Python COMPONENTS Interpreter Development)
find_package(pybind11 REQUIRED)

# pybind11 method:
pybind11_add_module(example example.cpp)

# auto copy
add_custom_command(TARGET example POST_BUILD 
    COMMAND ${CMAKE_COMMAND} -E copy  $<TARGET_FILE:example> ${CMAKE_SOURCE_DIR})
```
を作成する。
・python 3.8 用のターゲットを作成するには ```find_package(Python EXACT 3.8 COMPONENTS Interpreter Development)``` と変更する。
・python のバージョンを指定した場合には、環境変数 ```pybind11_DIR``` に対応するバージョンのフォルダを設定する。（pybind11 はヘッダオンリライブラリなので、別バージョンのままで大丈夫かも。保証はないので自己責任で。）
・```add_custom_command``` は利便性のためのもの。無くても良い。
・```pybind11_add_module(example example.cpp)``` は ```pybind11_add_module(example)``` と ```target_sources(example PUBLIC example.cpp)``` に分けても良い。

# 実行
Developer Command Prompt for VS 2022 で、ソースファイルを用意したディレクトリから 
```
> md build
> cd build
> cmake ..
> cmake --build . --config Release
```
とする。成功すれば ```Release``` ディレクトリに作成された ```example.cp310-win_amd64.pyd``` が作業用ディレクトリにコピーされる。（310 の部分は対応する python のバージョンを表している。）```example.cp310-win_amd64.pyd``` があるディレクトリから
```
> python
Python 3.10.8 (tags/v3.10.8:aaaf517, Oct 11 2022, 16:50:30) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(2,43)
45
```
とすれば動作を確認できる。なお、デバッグ中にソースを変更してビルドをし直す場合には、python を一旦終了しないと ```add_custom_command()``` が上書きに失敗するので注意。