ブログ

割とコンピュータよりの情報をお届けします。

2018/09/9

C言語からPythonの関数呼び出し2

先に作っていたサンプルでは文字列を返す例しか示していないので,Visual Studio Express 2017 for Windows Desktopで作るアプリ(Visual C++プロジェクトで作るアプリ)からPythonの関数を呼び出すという例をさらに作成していた。ただし,今回はWinPython-64bit-3.7.0.1Qt5の場合である。

詳しい扱い方,ビルド方法と実行の方法は前の記事を参照してほしい。
というのは,今回もWinPythonなのでPYTHONHOMEは必要である。もっとも,作ったexeファイルをPythonのフォルダで実行する場合にはその限りではないようだ。

サンプルコードPython側

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

def sample2(t, f):
    f = np.array(f)
    freq = np.linspace(0, 1.0 / (f[1] - f[0]), len(f))

    F = np.fft.fft(f) / len(f) * 2
    F[0] = F[0] / 2.0

    # 振幅スペクトルを計算
    Amp = np.abs(F)

    # plot
    plt.figure()
    plt.subplots_adjust(wspace=0.4, \
                        hspace=0.0)
    plt.subplot(121)
    plt.plot(t, f, label='Raw signal')
    plt.xlabel("time")
    plt.ylabel("signal")
    plt.grid()
    plt.ylim([-7, 7])
    leg = plt.legend(loc=1)

    plt.subplot(122)
    plt.plot(freq[0:int(len(F)/2)], \
             Amp[0:int(len(F)/2)], \
             label='Amplitude')
    plt.xlabel('frequency')
    plt.ylabel('amplitude')
    plt.grid()
    plt.ylim([0, 2])
    leg = plt.legend(loc=1)
    plt.show()
    return Amp.tolist()

次にC側のサンプルコードを示す。


#define _USE_MATH_DEFINES
#include <cmath>
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif

int main()
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pArg, *pArg2, *pValue, *pValue2;

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault("fft_test");

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, "sample2");

        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(0.01 * i);
                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("%I64d ", 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;
}

PyListやPyFloatなどはPython標準なのでPython.hをインクルードするだけで使える。もちろんNumpyなどの主要な拡張モジュールもヘッダファイルを提供していることがある(今回は面倒だから使っていない)。
ちなみに上の例ではPyFloatのリストとして扱っているが,もしPython側の変数Fを返したらPyComplexのリストとして扱うことが必要である。

ちなみにPy_DECREFなどはガーベージコレクションのために呼んでいるようだ。C言語では明示的に開放しないといけないが,Python側はそうなっていないためその調整のため,参照がなくなったかどうか(捨ててもよい領域かどうか)を判断するために使うらしい。

≫ Read More

2018/09/09 コンピュータ   TakeMe

C言語からPythonの関数呼び出し(WinPython-64bit-3.6.3.0Qt5の場合)

Visual Studio Express 2017 for Windows Desktopで作るアプリ(Visual C++プロジェクトで作るアプリ)からPythonの関数を呼び出すという例を作成していた。ただし,今回はWinPython-64bit-3.6.3.0Qt5の場合である。

Pythonの関数の側

まず,Pythonの関数の側を用意してみました。内容は,引数として文字列を受け取り,文字列を返す関数です。この例では,この関数の記述をしたファイルをtest.pyとして実行ファイルと同じファイルにおくように用意しました。

def fnc(msg):
    msg2 = msg + 'ehe' + msg
    print(msg);
    return msg2

C++/C言語のプログラムの側

Visual Studioで新しいプロジェクトとしてVisual C++のプロジェクトを作成します。C++/C言語のプログラムの側の中身をご紹介します。

#include <Python.h>

int main()
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pArg, *pValue;

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault("test");

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, "fnc");

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(1); // 関数fncの引数の数を引数にする
            pArg = PyUnicode_FromString("test");
            if (!pArg) {
                Py_DECREF(pArgs);
                Py_DECREF(pModule);
            }
            PyTuple_SetItem(pArgs, 0, pArg); // 2nd argument is the position of the positional argument
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                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;
}

ビルドの方法

Python.hはWinPythonのインストールディレクトリのpython-3.6.3.amd64\includeにある。 python36.libはE:\WinPython-64bit-3.6.3.0Qt5\python-3.6.3.amd64\libsにある。これらはプロジェクトのプロパティで設定する必要がある。 いまのところDebug構成の場合,リンクの入力にpython36.libを指定していてもpython36_d.lib(デバッグ用)をリンクしようとしてしまうらしいので,うまく使えない。
WinPythonの場合にはデバッグ用のpython36_d.lib/python36_d.dllを作成するにはソースからビルドすることになってしまいかなり面倒である。
したがって,Debug構成とRelease構成を見分けている_DEBUGという定義を一時的にでも消す方法もある。

#ifdef _DEBUG
  #undef _DEBUG
  #include <Python.h>
  #define _DEBUG
#else
  #include <Python.h>
#endif

実行

WinPython Command Prompt.exeでコマンドプロンプトを開き,Visual Studioがわでビルドしてできたファイルをx64\Releaseに見つける。そして上で用意したtest.pyを同じディレクトリにコピーして。
set PYTHONHOME=<WinPythonインストールディレクトリ>\python-3.6.3.amd64
を指定。現在のPython3のWinPython Command PromptではPYTHONHOMEの設定がなされなくなっているこれがないと,ファイルを実行したときに

Fatal Python error: Py_Initialize: unable to load the file system codec
ModuleNotFoundError: No module named 'encodings'

Current thread 0x00003a98 (most recent call first):

というメッセージが表示されてしまう。
それからピルドしたファイルを実行する。
(WinPython Command PromptはPathを適切に設定して起動するコマンドプロンプトなだけで,普通のコマンドプロンプト(CMD)でもset Path=...を適切に設定していれば可)

≫ Read More

2018/09/09 コンピュータ   TakeMe