プログラミングなど

2009年8月1日土曜日

C++アプリケーションからPythonの関数を呼び出す

ここのソースをちょこっと改造しただけ。

---- コード(Python側) testmodule.py ----

# -*- coding: utf-8 -*-

def testplus(i, j):
return i + j + 3

---- コード(C++側) ----

#include <Python.h>
#include <iostream>
#include <string>
#include <sstream>

#define VARNAME(var) #var
// ---- exception ----
class PythonException
{
protected:
 const char* message_;
 const char* varName_;
public:
 static const char* errorMessge_ImportModule;
 static const char* errorMessage_GetObject;
 static const char* errorMessage_CallFunction;
 static const char* errorMessage_CreateTuple;
 PythonException(const char* message, const char* varName)
    : message_(message), varName_(varName) {}
 virtual ~PythonException() throw() {}
 virtual std::string what() const throw() {
  std::stringstream ss;
  ss << message_ << " (" << varName_ << ")";
  return ss.str();
 }
};
const char* PythonException::errorMessge_ImportModule
 = "Module import error.";
const char* PythonException::errorMessage_GetObject
 = "Get object error.";
const char* PythonException::errorMessage_CallFunction
 = "Call function error.";
const char* PythonException::errorMessage_CreateTuple
 = "Create tuple error.";

// ---- main ----
int main(int argc, char *argv[])
{
 const char* moduleName = "testmodule";
 const char* funcName = "testplus";
 Py_Initialize();

 PyObject* pyModule = NULL;
 PyObject* pyFunc = NULL;
 PyObject* pyArgs = NULL;
 PyObject* pyArg1 = NULL;
 PyObject* pyArg2 = NULL;
 PyObject* pyResult = NULL;
 try {
  PyObject* pyModuleName = PyString_FromString(moduleName);
  pyModule = PyImport_Import(pyModuleName);
  Py_DECREF(pyModuleName);
  // module呼び出し失敗時に NULL が返る
  if (NULL == pyModule) {
   throw PythonException(
    PythonException::errorMessge_ImportModule,
    VARNAME(pyModule));
  }
  // 以下、二つは、"借りた参照"
  PyObject* pyDict = PyModule_GetDict(pyModule);
  pyFunc = PyDict_GetItemString(pyDict, funcName);

  // PyObject_GetAttrString()の場合、
  // "新たな参照"のため、DECREFしないといけない
  //PyObject* pyFunc = PyObject_GetAttrString(pyModule, funcName);

  // 取得失敗時に NULL が返る(PyModule_GetDict()は失敗しない)
  if (NULL == pyFunc) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyFunc));
  }
  // 呼び出し不可オブジェクトの場合、0 が返る
  if (0 == PyCallable_Check(pyFunc)) {
   throw PythonException(
    PythonException::errorMessage_CallFunction,
    VARNAME(pyFunc));
  }

  // 以下、失敗時には NULL が返る
  pyArgs = PyTuple_New(2);
  if (NULL == pyArgs) {
   throw PythonException(
    PythonException::errorMessage_CreateTuple,
    VARNAME(pyArgs));
  }
  PyObject* pyArg1 = PyInt_FromLong(2);
  if (NULL == pyArg1) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyArg1));
  }
  PyTuple_SetItem(pyArgs, 0, pyArg1);
  PyObject* pyArg2 = PyInt_FromLong(5);
  if (NULL == pyArg2) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyArg2));
  }
  PyTuple_SetItem(pyArgs, 1, pyArg2);
  pyResult = PyObject_CallObject(pyFunc, pyArgs);
  if (NULL == pyResult) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyResult));
  }
  std::cout << "Result: "
   << PyInt_AsLong(pyResult) << std::endl;
 } catch (const PythonException& pyEx) {
  if (PyErr_Occurred()) {
   PyErr_Print();
  }
  std::cout << pyEx.what() << std::endl;
 }
 Py_XDECREF(pyResult);
 Py_XDECREF(pyArg2);
 Py_XDECREF(pyArg1);
 Py_XDECREF(pyArgs);
 //Py_XDECREF(pyFunc); // PyObject_GetAttrString()の場合
 Py_XDECREF(pyModule);

 Py_Finalize();
 return 0;
}

// End of file

コードは、ほぼ真似なので...(^^; 気をつけなければいけないことは、参照カウンタの操作です。 Python/C API関数を使用した際、戻り値の PyObject* が"新たな参照" の場合、 Py_DECREF() し、"借りた参照" の場合は、Py_DECREF()してはいけません。 これらについては、Python/C API リファレンスマニュアルで、それぞれの関数の戻り値を調べれば、わかるかと思います。

上記の例では、モジュールから、関数を取得する場合に、PyModule_GetDict()と、PyDict_GetItemString()を使用しています。
この二つは、"借りた参照"を返すため、戻り値の PyObject* を Py_DECREF() していません。
しかし、PyObject_GetAttrString() でも関数を取得できますが、こちらは、"新たな参照"を返すため、Py_DECREF() をします。そうしないと、メモリにごみが残ります。
参照カウンタの詳しい説明は、以下などを見ればよいかと思います。
GCアルゴリズム詳細解説
Py_DECREF() には、以下の2種類があります。
Py_DECREF() は、参照カウンタを1デクリメントします。
Py_XDECREF() は、NULLオブジェクトの場合、Py_DECREF()しません。NULLかどうかわからない場合は、こちらを使用したほうがよいでしょう。

0 件のコメント:

コメントを投稿