【Embeddable Python】C++からPythonを呼び出すWindowsソフトを作る

# やりたいこと
C++でWindows用のリズムゲームを作っていたが、譜面の読み込みが面倒だったので、
1. C++ からPythonの関数を呼び出す
2. Pythonを使って譜面読み込みをする
3. 関数の戻り値として譜面を解釈した数値データをリストで返す
4. C++でリストを`vector`に変換
をやってみる。ここでは簡単のため、譜面の数学的な処理は省略する。

# 下準備
## デバッグシンボルの導入
Visual Studio 2019で開発をするため、デバッグ用のシンボルが必要になる。シンボルはPythonのインストーラを起動したときに、`Advanced Options`で`Download debugging symbols`と`Download debug binaries`にチェックを入れることで導入できる。
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275949/5f233f51-acb9-3000-302d-4e3005e5acf6.png)
Pythonのインストール場所はできるだけシンプルな場所にすることをお勧めする。(今回は`C:\Program Files\Python36\`)

## Embeddable Pythonをダウンロード
[Pythonの公式HP](https://www.python.org/downloads/)から、任意のバージョンの`Windows x86-64 embeddable zip file`をダウンロード、解凍。開発するパソコンにインストールしてあるPythonとバージョンを合わせた方が無難だろう。

## Visual Studioの設定
Pythonを導入したいプロジェクトで、「プロジェクト(P)」→「プロパティ」を開き「<プロジェクト名> プロパティ ページ」ウィンドウを開き、そのウィンドウの左上にある「構成(C)」を選択して、「すべての構成」に変更する。「構成プロパティ」→「VC++ディレクトリ」→「全般」→「インクルード ディレクトリ」を編集して、`<Pythonをインストールしたディレクトリ>\include`を追加する。(今回は`C:\Program Files\Python36\include`)また、「ライブラリ ディレクトリ」を編集して、`<Pythonをインストールしたディレクトリ>\libs`を追加する。(今回は`C:\Program Files\Python36\libs`)

## Embeddable Pythonの導入
プロジェクトのメインフォルダ(デフォルトで`main.cpp`などが入っているフォルダ)に、先ほどダウンロードしたEmbeddable Pythonの`python3.dll`,`python36._pth`,`python36.dll`,`sqlite3.dll`,`vcruntime.dll`を入れる。また、`python`フォルダを作成し、ダウンロードした残りのファイルをそこに入れる。
メインフォルダ
![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275949/2776504b-9334-e883-f858-290af13cf37f.png)
`python`フォルダ
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275949/57997b50-6878-0d70-c722-3ccc3119f2c2.png)
ここで、メインフォルダに入れた、`python36._pth`を編集して、

```txt:python36._pth
python/python36.zip
.
```
にする。
# いよいよコーディング
## C++でPythonの初期化
公式リファレンスで`Py_SetProgramName`を設定することを推奨していたので、そうした。
今回は自作したPythonの関数を使用したいので、Pythonのモジュール検索パスにカレントディレクトリを追加した。(7-9行目)

```c++:main.cpp
#include <Python.h>

int main(int argc, char *argv[]) {
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    Py_SetProgramName(program);
    Py_Initialize();
    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(sys, PyUnicode_FromString("./"));
    //ここにコードを書いていく
    return 0;
}
```

## Pythonを使って譜面ファイルを読み込み→処理→返す
譜面のファイルパスを動的に受け取りたかったので、引数にファイルパスを指定できるようにした。また、読み込む譜面の難易度も指定できるようにした。戻り値は、Pythonのリストで、`[int<ノーツが流れてくるレーン>, float<ノーツが流れてくる時間>]`が一つのノーツデータとしてリストにノーツ数ぶん入っている。つまり、`[[int, float], [int, float], [int, float], ...]`のようになっている。尚、一時的に計算の精度を高めるために`float`になっているため、C++に読み込まれるときには`long`型にキャストされる。
このファイルは`main.cpp`と同じ階層に置く。ビルド後は`<プロジェクト名>.exe`と同じ階層に置く。

```python:notegetter.py
import re

def getNoteData(filepath, difficulty):
    notes = []
    #ファイルを読み込む処理
    return notes
```

## C++でPython関数を呼び出し
流れとしては、
1. 自作モジュールをインポート
2. 指定した関数を関数オブジェクトとして取得
3. 関数オブジェクトを呼び出し、戻り値をゲット
4. 戻り値がリストであることを確認してリストのサイズを調べる
5. 調べたサイズ分for文で中身をC++の`long`型にキャストしながら取得。
6. 使わなくなった`pRet`変数の参照を減らす
どうやらPython型→C++型にできるのは`float`型と`long`型だけらしいので、しょうがなくそれを使っている。
あと、引数はタプルでひとまとめにしてから渡すらしい。尚、引数が必要無い場合はそこを`NULL`にすればいい。
今回は簡単のため、ヘッダファイルにべた書きである。

```c++:getnotedata.hpp
#include <Python.h>
#include <vector>
#include <string>

std::vector<std::pair<int, long>> getNoteData(const std::string &filepath, const std::string &difficulty) {
    std::vector<std::pair<int, long>> notedata;
    PyObject *pName, *pModule, *pFunc, *pRet, *pArgs;
    pName = PyUnicode_DecodeFSDefault("notegetter");
    pModule = PyImport_Import(pName);

    if (pModule) {
        pFunc = PyObject_GetAttrString(pModule, "getNoteData");
        if (PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(2);
            PyTuple_SetItem(pArgs, 0, PyUnicode_FromString(filepath.c_str()));
            PyTuple_SetItem(pArgs, 1, PyUnicode_FromString(difficulty.c_str()));
            pRet = PyObject_CallObject(pFunc, pArgs);
            if (pRet && PyList_Check(pRet)) {
                Py_ssize_t len = PyList_Size(pRet);
                notedata.resize(len);
                for (Py_ssize_t i = 0; i < len; i++) {
                    PyObject *c = PyList_GetItem(pRet, i);
                    notedata[i].first = PyLong_AsLong(PyList_GetItem(c, 0));
                    notedata[i].second = PyLong_AsLong(PyList_GetItem(c, 1));
                }
            }
            Py_XDECREF(pRet);
        }
    }
    return notedata;
}
```

```c++:main.cpp
#include <Python.h>
#include <vector>
#include "getnotedata.hpp"

int main(int argc, char *argv[]) {
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    Py_SetProgramName(program);
    Py_Initialize();
    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(sys, PyUnicode_FromString("./"));
    //ここにコードを書いていく
    std::vector<std::pair<int, long>> notedata = getNoteData("./sampleMusic.txt", "normal");

    return 0;
}
```
これで一通りの処理は完成である。尚、ビルドした後のフォルダにも当然Pythonを導入しなくては動かないので、ビルド後は`<プロジェクト名>.exe`があるフォルダで「Embeddable Pythonの導入」でやった操作をすることを忘れてはならない。あと、PythonではUTF-8が使われているため、注意が必要だ。

# おわりに
C++とPythonの連携に関する情報は、Python公式の[Python/C API リファレンスマニュアル](https://docs.python.org/ja/3/c-api/index.html)を素直に読むのが良いだろう。また、参照カウントについてはよく調べることを勧める。それでは良いC++/Pythonライフを！