やりたいこと
C++でWindows用のリズムゲームを作っていたが、譜面の読み込みが面倒だったので、
- C++ からPythonの関数を呼び出す
- Pythonを使って譜面読み込みをする
- 関数の戻り値として譜面を解釈した数値データをリストで返す
- C++でリストを
vector
に変換
をやってみる。ここでは簡単のため、譜面の数学的な処理は省略する。
下準備
デバッグシンボルの導入
Visual Studio 2019で開発をするため、デバッグ用のシンボルが必要になる。シンボルはPythonのインストーラを起動したときに、Advanced Options
でDownload debugging symbols
とDownload debug binaries
にチェックを入れることで導入できる。
Pythonのインストール場所はできるだけシンプルな場所にすることをお勧めする。(今回はC:\Program Files\Python36\
)
Embeddable Pythonをダウンロード
Pythonの公式HPから、任意のバージョンのWindows x86-64 embeddable zip file
をダウンロード、解凍。開発するパソコンにインストールしてあるPythonとバージョンを合わせた方が無難だろう。
Visual Studioの設定
Pythonを導入したいプロジェクトで、「プロジェクト(P)」→「プロパティ」を開き「<プロジェクト名> プロパティ ページ」ウィンドウを開き、そのウィンドウの左上にある「構成(C)」を選択して、「すべての構成」に変更する。「構成プロパティ」→「VC++ディレクトリ」→「全般」→「インクルード ディレクトリ」を編集して、`\include`を追加する。(今回は`C:\Program Files\Python36\include`)また、「ライブラリ ディレクトリ」を編集して、`\libsを追加する。(今回は
C:\Program Files\Python36\libs`)
Embeddable Pythonの導入
プロジェクトのメインフォルダ(デフォルトでmain.cpp
などが入っているフォルダ)に、先ほどダウンロードしたEmbeddable Pythonのpython3.dll
,python36._pth
,python36.dll
,sqlite3.dll
,vcruntime.dll
を入れる。また、python
フォルダを作成し、ダウンロードした残りのファイルをそこに入れる。
メインフォルダ
python
フォルダ
ここで、メインフォルダに入れた、python36._pth
を編集して、
python/python36.zip
.
にする。
いよいよコーディング
C++でPythonの初期化
公式リファレンスでPy_SetProgramName
を設定することを推奨していたので、そうした。
今回は自作したPythonの関数を使用したいので、Pythonのモジュール検索パスにカレントディレクトリを追加した。(7-9行目)
# 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
と同じ階層に置く。
import re
def getNoteData(filepath, difficulty):
notes = []
#ファイルを読み込む処理
return notes
C++でPython関数を呼び出し
流れとしては、
- 自作モジュールをインポート
- 指定した関数を関数オブジェクトとして取得
- 関数オブジェクトを呼び出し、戻り値をゲット
- 戻り値がリストであることを確認してリストのサイズを調べる
- 調べたサイズ分for文で中身をC++の
long
型にキャストしながら取得。 - 使わなくなった
pRet
変数の参照を減らす
どうやらPython型→C++型にできるのはfloat
型とlong
型だけらしいので、しょうがなくそれを使っている。
あと、引数はタプルでひとまとめにしてから渡すらしい。尚、引数が必要無い場合はそこをNULL
にすればいい。
今回は簡単のため、ヘッダファイルにべた書きである。
# 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;
}
# 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 リファレンスマニュアルを素直に読むのが良いだろう。また、参照カウントについてはよく調べることを勧める。それでは良いC++/Pythonライフを!