Pybind11でC++関数をPythonから実行する（Windows & Visual Studio Codeな人向け） デバッグ編

[環境構築編](https://qiita.com/k_maki/items/01d425c06f990e34870d)に続き、デバッグの方法を説明します。

# 1. 呼び出し用スクリプトの準備
ビルドが終わった段階のフォルダーのルートに、`main.py`を追加します。

```:フォルダー構成
project_root
├ build
│ └ Debug
│   └ cmake_example.cp37-win_amd64.pyd
├ pybind11
├ src
│ └main.cpp
├ CMakeLists.txt
└ main.py              ←これを追加
```

`main.py`はC++関数を呼び出すスクリプトで、内容は次のとおり：

```python:main.py
import os
from build.Debug.cmake_example import add


print(f'PID: {os.getpid()}') # アタッチしてデバッグするためのもの

a = add(1,2)
print(a)
```

# 2. C++拡張のデバッグ
はじめにC++関数部分のみのデバッグ方法を説明します。ブレークポイントを設定できるのはC++のみです。
## 2.1. デバッグ用の構成の作成
コマンドパレット(`Ctrl+p`)から`Debug: Open launch.json`と入力し、`C++ (Windows)`を選択。launch.jsonが開かれる:

```json:launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(Windows) 起動",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "プログラム名を入力してください (例: ${workspaceFolder}/a.exe)",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false
        }
    ]
} 
```

上記を修正。`"program"`部分にPythonのパスを、`"args"`の括弧内に`"${file}"`を入力する:

```json:launch.json（修正例）
        {
            "name": "(Windows) 起動",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "c:/programdata/anaconda3/python.exe", //ここと
            "args": ["${file}"],                      //ここ
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false
        }
```

**注意: `"stopAtEntry"`の値は`false`のままにすること。**`true`にすると、Pythonの実行ファイル(python.exe)をデバッグしようとしてしまい、`Unable to open 'python.c'`というエラーで止まるため。

## 2.2. デバッグの実行

`main.c`の`return i + j;`の部分にブレークポイントを設定し、`main.py`を開いた状態でデバッグを実行（`F5`）します。問題なければブレークポイントで止まるはずです。

# 3. PythonとC++拡張の混合モードでのデバッグ
今度はPythonとC++拡張の同時デバッグ方法を説明しす。PythonとC++の両方にブレークポイントを設定できます。

Pythonのデバッガーを起動して、そこにC++のデバッガーをアタッチすることで実現します。[こちら](https://daeudaeu.com/opencv_debug/)で非常にわかりやすく解説されていますので、是非ご覧ください。

## 3.1. デバッグ用の構成の作成
Python用、C++用の二つの構成を追加します。


- C++: コマンドパレットから、`Debug: Select and Start Debugging`->`構成の追加...`->`C/C++: (Windows) 接続`で追加
- Python: `main.py`を開いて、コマンドパレットから`Debug: Select and Start Debugging`->`構成の追加...`->`Python`->`Python File`
で追加

`launch.json`に次が追加されます:

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

## 3.2. デバッグの実行
`main.py`の`a = add(1,2)`、`main.c`の`return i + j;`にブレークポイントを設定します。
デバッグを実行しましょう。まずはPythonから。`main.py`を開き、コマンドパレット->`Debug: Select and Start Debugging`->`Python: Current File`で実行すると、先ほどのブレークポイントで止まります。また、ターミナルにプロセスのIDが*PID: xxxx*と表示されます(*xxxx*は数字)。
次にC++。コマンドパレット->`Debug: Select and Start Debugging`->`(Windows) 接続`で開始します。"アッタッチするプロセスを選択する"と表示されますので、先ほどのPIDを入力すれば準備完了です。
`main.py`に戻り、ステップ実行（F10）すればC++のブレークポイントで止まります。
![Python.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/9b19fba0-663e-f002-10d2-40ebbcbba235.jpeg)



# 4. （おまけ1）Jupyterとの連携
`main.py`と同様の内容を`.ipynb`ファイルで実行し、そのPIDにアタッチすれば、JupyterとC++のデバッグを連携できます。これは便利かも。
![Jupyter.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/d7657241-5ee4-665f-9660-0ea3edebd7c3.jpeg)



# 5. （おまけ2）Excel VBAとPythonとC++拡張の混合デバッグ
ExcelからPythonを呼び出して、そのPythonからC++を呼び出すことがあるかもしれません。各種設定をExcelのシートに入力して、そこからPythonを呼び出して計算して出力するけれど、計算負荷が高そうな（or 既存のC++資産を活かしたい）時にC++を使うという感じで。Excelはフロントエンド、Pythonは全体的な処理と出力、C++は計算を担当する使い方です。

そんんことしねーよという声も聞こえてきましたが、引き続き今回のコードで説明します。

## 5.1. Excelファイルの準備等

Excel VBAから利用&デバッグ可能な次のファイル`main_xw.py`を準備します。

```python:main_xw.py
import os
from build.Debug.cmake_example import add
import xlwings as xw


@xw.func
def xw_add(a, b):
    return add(int(a), int(b))


if __name__ == '__main__':
    print(f'PID: {os.getpid()}')
    xw.serve()

```

Excel VBAとの連携には[xlwings](https://docs.xlwings.org/ja/latest/)を使用します。同じフォルダーに`main_xw.xlsm`ファイルを置き、リボン`xlwings`の`Import Functions`をクリックし、VBエディター（Alt+F11）の参照設定で`xlwings`にチェックを入れます。適当なセルで`xw_add`関数が使えれば、呼び出し元Excelファイルの準備はOKです。（xlwingsの詳しい説明は[こちら](https://qiita.com/k_maki/items/9efdb70f69d6f3b00f80)をご覧ください。）

（xlwingsは私の推しメンなんですが、残念なことにopenpyxlに比べて知名度がいまいちです（本が売っていない！）。これを機に是非使ってみてください。ドキュメントも[しょぼいですが日本語化](https://qiita.com/k_maki/items/e20a3225428e580c2072)されています。）


## 5.2. デバッグの実行
ブレークポイントは以下の3か所に設定します:

- VBA: 標準モジュール`xlwings_udfs`内のどこか
- Python: `main_xw.py`の`return add(int(a), int(b))`
- C++: `main.c`の`return i + j;`

デバッグを実行しましょう。3.2.と同様に、`main.py`を開き、`Python: Current File`を実行し、`(Windows) 接続`でアタッチします。xlwingsのデバッグ用のサーバーも立ち上がっているので、Excelのxliwngsリボンで`Debug UDFs`にチェックを入れれば準備完了です。

適当なセルに`=xw_add(1,2)`と入力すれば、VBAからPythonに、PythonからC++にブレークポイントを移動しながら実行することができます:
![xlwings.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239172/d53e85c5-c8eb-db07-a061-9bd4fcdb4489.jpeg)



# 6. トラブルシューティング
（今後追記予定）

# 参考
以下を参考にさせていただきました。

## デバッグ方法
- [VSCode で Python と OpenCV を混合でデバッグ（ステップ実行）する方法！](https://daeudaeu.com/opencv_debug/)