久しぶりの「C言語からPythonの関数呼び出し」記事として,WinPythonとVisual Studio 2017でPython embeddable用の実行ファイルを作るということを再検討していた.
まず,Python側のコードの例は以下のようにした.
今回もlistを引数としてPythonに渡して,listを返り値としてもらうという例になっている.
# -*- coding: utf-8 -*-
def SampleFunc1(t, f):
output = [10, 9, 7, 5, 3, 1];
return output
C(C++)言語側のコードは以下のようにした.
#include <stdio.h>
#include <windows.h>
#define _USE_MATH_DEFINES
#include <cmath>
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif
int main()
{
char Path[MAX_PATH + 1];
char Dir[MAX_PATH + 1 + 32];
char drive[MAX_PATH + 1]
, dir[MAX_PATH + 1]
, fname[MAX_PATH + 1]
, ext[MAX_PATH + 1];
if (0 != GetModuleFileNameA(NULL, Path, MAX_PATH)) {
_splitpath_s(Path, drive, dir, fname, ext);
int j = 0;
for (int i = 0; i < MAX_PATH + 1; i++) {
Dir[j] = dir[i];
j++;
if (dir[i] == '\\') {
Dir[j] = dir[i];
j++;
}
else if (dir[i] == '\0') {
break;
}
}
sprintf_s(dir, "%s%s", drive, Dir);
printf("ディレクトリ パス : %s\n", dir);
}
char buf1[2000];
sprintf_s(buf1, "import sys\r\nsys.path.append('%s')\r\nprint(sys.path)\r\n", dir);
// 実行ファイルと同じフォルダにあるpyモジュールも使いたいので,前回より少し手を加えた.
PyObject *pName, *pModule, *pFunc;
PyObject *pArgs, *pArg, *pArg2, *pValue, *pValue2;
Py_Initialize();
PyRun_SimpleString(buf1);
pName = PyUnicode_DecodeFSDefault("SampleFunc");
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, "SampleFunc1");
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(2); // 関数fncの引数の数を引数にする
pArg = PyList_New(1000);
pArg2 = PyList_New(1000);
for (int i = 0; i < 1000; i++) {
double value = sin(2.0 * M_PI * 0.01 * 1.0 * i);
pValue = PyFloat_FromDouble(value);
pValue2 = PyFloat_FromDouble(2.0);
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pArg);
Py_DECREF(pArg2);
Py_DECREF(pModule);
return 1;
}
PyList_SetItem(pArg, i, pValue);
PyList_SetItem(pArg2, i, pValue2);
}
if (PyTuple_SetItem(pArgs, 0, pArg2) != 0)
return 1;
if (PyTuple_SetItem(pArgs, 1, pArg) != 0)
return 1;
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
//printf("%d\n", PyList_CheckExact(pValue));
Py_ssize_t len = PyList_Size(pValue);
for (int i = 0; i < len; i++) {
//printf("%I32d ", PyFloat_Check(PyList_GetItem(pValue, i)));
printf("%.4g\n", PyFloat_AsDouble(PyList_GetItem(pValue, i)));
}
//printf("return: %s\n", (char*)PyUnicode_DATA(pValue));
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr, "Call failed\n");
return 1;
}
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function\n");
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
}
else {
PyErr_Print();
fprintf(stderr, "Failed to load module\n");
return 1;
}
if (Py_FinalizeEx() < 0) {
return 120;
}
return 0;
}
プロジェクトのプロパティを次のように設定しておくとF5キーでデバッグがかかるようになる.
(Python部分はデバッグできない) 「ビルド後のイベント」のコマンドライン
copy .\*.py $(TargetDir)
「デバッグ」の「環境」にPython Embeddableのインストールパスを設定する.例えば
PATH="$(Path)C:\python-3.7.2.post1-embed-amd64\;"
なお,[C/C++]の[追加インクルードディレクトリ]にはWinPythonのインストールパスを設定する.例えば
C:\WPy-3710Zero\python-3.7.1.amd64\include
[リンカー]の[追加のライブラリディレクトリ]には 例えば
C:\WPy-3710Zero\python-3.7.1.amd64\include
[リンカー]の[追加の依存ファイル]には
python3.lib
を設定する.
なお途中にでている「手を加えた」としている部分は次の記事ですこし考え直した.