xlwings,pybind11を用いてC++,Python,Excel連携してデバッグする

# はじめに
pybind11とは、計算処理が速いC++とPythonを連携するためのライブラリ、xlwingsとは、Pythonと元データの書き込みに使われやすいExcelを連携するためのライブラリである。
これらのライブラリをもとに簡単な関数を作成し、混合デバッグをしてみよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/5102b684-6872-1ed9-c9b7-70a8639af734.png)


# 1 環境構築
## 1.1 ソフトウェアインストール
エディタ：VSCode ：PythonやC++を動作させるためのエディタ
Python:3.11
CMake:3.19.4：msvcを動作させるためのもの[ https://cmake.org/download/ ]
Visual Studio Build Tools 2019 Release：C++ソースをコンパイル(機械語に変更)するためのもの(以下msvcとよぶ)
pybind11:2.11.1：C++関数をPython関数に変化するC++ライブラリ

## 1.2 VSCodeの拡張機能インストール
CMakeTools:CMakeを使用

## 1.3フォルダ構成
pybind_testフォルダをデスクトップ等に作成し以下のようにフォルダ階層を設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/3212cf34-bc1a-f19b-1d36-979977a74c9a.png)

### 〇各ソフトウェアとファイルの関係性イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/8cbece76-0779-23dc-07ab-4efa60a095f7.png)

まずCMakeは、CMakeLists.txtをもとにプロジェクト(pybind_test.sln)を作成する。次にmsvcは、プロジェクトとpybindライブラリをもとにpython用C++ライブラリ(pyd)を作成。以上の内容を1.1～1.4で説明する。
するとpythonがpydファイルをimportするとモジュールの関数が使えるようになる。特にlaunch.jsonの設定により、pythonのデバッグプロセスに、C++のデバッグプロセスを接続することでpythonとC++の混合デバッグが可能となる。この内容は2章で説明する。
さらにxlwingsのデバッグ機能でExcelとpythonの混合デバッグが可能となるが、このpythonのデバッグプロセスにC++のバッグプロセスを接続することで、Excel、pythonとC++の混合デバッグが可能となる。この内容は、3章で説明する。
以下、各ファイルの内容を説明する。

### 〇main.cpp：例としてadd関数(足し算)を定義
```C++:main.cpp
// ライブラリをインクルード
#include <pybind11/pybind11.h>
// 整数 i と j を引数に取り、その和を返す add 関数を定義
double add(double i, double j) {
    return i + j;
}
//処理速度確認用の関数
double add_1e7(double i, double j) {
    for (int n=0; n<1e7; ++n){
        i = i + j; 
    }
    return i;
}

// pybind_test という名前の Python モジュールを定義。このモジュールは、Python からインポートして使用可能
PYBIND11_MODULE(pybind_test, m) {
// add 関数を Python モジュールに追加。この関数は、Python から pybind_test.add として呼び出すことが可能。R"pbdoc( ... )pbdoc" は、関数のドキュメンテーション文字列を定義
    m.def("add", &add, R"pbdoc(
        Add two numbers
        Some other explanation about the add function.
    )pbdoc");
    m.def("add_1e7", &add_1e7, "add_1e7");
}

```

### 〇CMakeLists.txt：CMakeでプロジェクトをどのように作成するかを指示するファイル
```CMake:CMakeLists.txt
# CMakeプロジェクトが最低限必要とするCMakeのバージョン
cmake_minimum_required(VERSION 3.17.2)
# プロジェクトの名前を設定
project(pybind_test)
# pybind11ディレクトリをプロジェクトに追加
add_subdirectory(pybind11)
# pybind11ライブラリを使用して、src/main.cppからpybind_testという名前のPythonモジュールを作成
pybind11_add_module(pybind_test src/main.cpp)
```

### 〇settings.json：ソフトウェアに認識させるパスなどの設定
```json:settings.json
{
    // CMakeの設定引数を指定.build時に作成されるpython用C++ライブラリ(pyd)をpythonのverに合わせる
    "cmake.configureArgs": [
        "-DPYTHON_EXECUTABLE=C:\\program files\\python311\\python.exe"
    ]
}

```

### 〇launch.json：デバッグの設定
```json:launch.json
{
// デバッグ設定の配列
// デバッグ設定の名前 C++ のデバッグ (cppvsdbg) と Python のデバッグ (python) がある
// デバッグセッションの種類。launch は新しいプログラムの起動を意味し、attach は既存のプロセスへの接続を意味する
// 接続するプロセスの ID を指定します。${command:pickProcess} は手動でプロセスを選択することを意味
// launch リクエストで起動するプログラムのパスを指定します。${file} は現在開いているファイルを示す
// 出力を表示するコンソールの種類を指定します。integratedTerminal は VS Code の統合ターミナルを使用することを意味する

"configurations": [
        {
            "name": "(Windows) 接続",
            "type": "cppvsdbg",
            "request": "attach",
            "processId": "${command:pickProcess}"
        },
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
}

```
# 2 pybind11の機能でC++関数をpythonで利用する
1章の環境のもとで、pythonからpydファイルを使用してC++関数を利用できる
## 2.1 .pyd(モジュール)の作成、利用およびデバッグ方法
### 1章の環境のもとで、VSCode上でCtr+Pを押下し以下のコマンドによりpyd作成
CMake:Configure：プロジェクトファイルを作成
CMake:Select a Kit：コンパイラの選択Visual Studio Build Tools 2019 Release –amd64
CMake:Select a Variant：C++関数のDebug,Releaseモードを設定
CMake Build：以上の設定でコンパイルしpydファイル作成

pybind_testフォルダ直下に以下pybind11test.pyを作成してpydファイルを使用してみる。
なお、コメントアウトしているコードの一部は、2章および3章で利用する。

### 〇pybind11test.py

```python:pybind11test.py
import os
import time
# (2章で利用)デバッグする際は、build.Debugフォルダのpydファイルを、そうでないときはbuild.Releaseフォルダのpydファイルを使用
# from build.Debug.pybind_test import add, add_1e7
from build.Release.pybind_test import add, add_1e7

# # (3章で利用)xlwingsのデバッグ時に利用
# # C++関数をpython関数でラップした後、その関数をさらにxw.funcでラップしてexcel関数にする
# import xlwings as xw
# @xw.func
# def p_add(i:float, j:float) -> float:
#     return add(i, j)
# @xw.func
# def p_add_1e7(i:float, j:float) -> float:
#     return add_1e7(i, j)

if __name__ == '__main__':
    # 混合デバッグ時には、pid(プロセスid)を指定して、pythonデバッグプロセスにC++のデバッグプロセスを接続するため、pidを把握
    pid = os.getpid()
    print(f'PID: {pid}')
    print(f'add(1, 2): {add(1, 2)}')

    # # (3章で利用)xlwingsのデバッグ時に利用
    # xw.serve()

    # パターン1：pythonの足し算(i=i+2)をpythonのforループで1000万回処理
    i = 1
    j = 2
    # 開始時間を記録
    start_time = time.time()
    for _ in range(10**7):
        i = i + j
    end_time = time.time()
    print(f"パターン1：Calculation took {end_time - start_time} seconds. i:{i}")

    # パターン2：C++の足し算(i=i+2)をpythonのforループで1000万回処理
    i = 1
    j = 2
    start_time = time.time()
    for _ in range(10**7):
        i = add(i, j)
    end_time = time.time()
    print(f"パターン2：Calculation took {end_time - start_time} seconds. i:{i}")

    # パターン3：C++の足し算(i=i+2)をC++のforループで1000万回処理
    i = 1
    j = 2
    start_time = time.time()
    i = add_1e7(i, j)
    end_time = time.time()
    print(f"パターン3：Calculation took {end_time - start_time} seconds. i:{i}")

```
まずReleaseモードで実行した結果、パターン3の処理が、パターン1より100倍程度早い。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/623f0e90-c4f3-407c-4501-4ff250dbbec4.png)


次にDebugモードで実行して、混合デバッグをしてみる。
pybind11test.pyのDebug用モジュールの読み込みをコメントアウトしていたが、Debug用のものに変更しておく。

### 〇pybind11test.py
```diff_python:pybind11test.py 
# (2章で利用)デバッグする際は、build.Debugフォルダのpydファイルを、そうでないときはbuild.Releaseフォルダのpydファイルを使用
+ from build.Debug.pybind_test import add, add_1e7
- from build.Release.pybind_test import add, add_1e7
```

そして以下の部分でブレイクポイントを置く。
### 〇pybind11test.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/247467e9-f0bf-e808-7d2c-4f45e8e3b53d.png)
### 〇main.cpp
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/f5bbe907-0ffe-2cdc-2433-eda49bdd2379.png)

この状態でまず、VSCode上でpybind11test.pyを開き、以下が"Python: Current File"となっている状態で押下。するとターミナル上でPIDが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/318374d3-5f62-89bc-d6f4-976c2186c2f8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/a70bb3e3-f7fe-1939-4cdf-f348c9e04fd9.png)

このPIDをもとに続けて、VSCode上で以下が"(Windows) 接続"となっている状態で押下(1.4のlaunch.jsonの設定がされていると存在)。するとC++のデバッグプロセスがpythonのデバッグプロセスにつながり、混合デバッグができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/4f915b41-283d-3c11-9ae5-0d44dcd9a021.png)

# 3 xlwingsの機能によるExcel、Python、C++を含めた混合デバッグ
pybind11test.pyでコメントアウトしていた、以下を追加する。

```diff_python:pybind11test.py 
# xlwingsのデバッグ時に利用
# C++関数をpython関数でラップした後、その関数をさらにxw.funcでラップしてexcel関数にする
+ import xlwings as xw
+ @xw.func
+ def p_add(i:float, j:float) -> float:
+    return add(i, j)
+ @xw.func
+ def p_add_1e7(i:float, j:float) -> float:
+    return add_1e7(i, j)
```

```diff_python:pybind11test.py
# xlwingsのデバッグ時に利用
+    xw.serve()
```

pybind11test.py と同じ階層に空のExcelファイル(pybindtest.xlsm)を用意して以下のように、A1、B1セルにUDF Modules、pybind11testが入力されている、xlwings.confシートを追加する。またVBAの参照設定で「xlwings」に✔をいれておく。

### 〇pybindtest.xlsm
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/84a6af13-e47d-f206-16d3-b84255568c5e.png)

この状態で、2.1と同様にpybind11test.pyをデバッグして、さらにC++のデバッグプロセスを接続しておく。pybindtest.xlsmでxlwingsリボンにおいて、Debug UDFsに✔を付けた上で「Import Function」を押下。すると[p_add]関数がインポートされている。
その関数を実行すると、Excel関数、python関数の混合デバッグとなり、2.1と同様にさらにC++関数の混合デバッグが可能となっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/4d7658ad-5969-24aa-f537-3c8867868986.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1354145/623d62bf-b9b4-7ee8-7f41-e2c32f4eff96.png)

# 参考文献
VBAユーザーのためのPython入門 ～xlwingsでExcelからPythonを呼び出す～ #Python - Qiita(https://qiita.com/k_maki/items/9efdb70f69d6f3b00f80)
Pybind11でC++関数をPythonから実行する（Windows & Visual Studio Codeな人向け） 環境構築編 #Python - Qiita(https://qiita.com/k_maki/items/01d425c06f990e34870d)
Pybind11でC++関数をPythonから実行する（Windows & Visual Studio Codeな人向け） デバッグ編 #Python - Qiita(https://qiita.com/k_maki/items/75bf05e4159be92c0bd9)