C/C++ / Pythonを組み込む

アプリケーションにPythonを組み込むサンプルです。

Python 3.3, boost 1.55で実装しました。

要求

  1. shared library(DLL)を作らない
  2. C/C++から外部へモジュールをexportする (== 外部から、C/C++内のモジュールをimportする)
  3. C/C++内から、文字列としてPythonスクリプトを実行する
  4. C/C++内から、外部のモジュールをimportする
  5. Pythonとboost::pythonのみを使用する
  6. C/C++クラスはnoncopyableである
  7. shared_ptr, intrusive_ptrに対応する
  8. factory, addref, releaseを持つクラスに対応する

いろいろやり方はあるようで、模索中ではあります。

"std::move" を使っているのでC++11でやってます。 合わせて "noexcept" や "= delete" 等を使用しています。

shared library(DLL)を作らない

sys.modules に PyInit_<module> で作ったモジュールを登録し、 PyRun_SimpleString("import <module>")を使用すればOKです。

ただし。 今となっては「sharedを作ったほうがいい」という考えに傾いています。 「出来ないことはなさそう」という思考で始めてしまい、 「出来てしまった」というだけの理由で main.cc に全部突っ込んであります。

説明が楽になるとか、実行ファイルの取り回しがよくなるとか一応の利点はあります。

問題は「imp.reload ができない」ので、再定義は独自に実装する必要があります。 ただし、C/C++での記述部分を再読み込みすることは、そうそうないと思います。

C/C++から外部へモジュールをexportする (== 外部から、C/C++内のモジュールをimportする)

boost::pythonのBOOST_PYTHON_MODULEを使用します。 これで、PyInit_<module>が export されますが、 sharedを作らない場合、実行して無理矢理モジュールを作成します。

C/C++内から、文字列としてPythonスクリプトを実行する

PyRun_SimpleString 等の PyRun_*** を使用します。

C/C++内から、外部のモジュールをimportする

PyRun_SimpleString("import <module>")を使用します。

パス回りの環境をセットしておく必要があります。

https://docs.python.org/3.3/using/cmdline.html#environment-variables

Py_SetProgramName
実行ファイルへのパス(== argv[0])
Py_SetPythonHome
Python Home (== prefix == "/usr")
Py_SetPath
ライブラリパス(== sys.path)
Pythonとboost::pythonのみを使用する

Swigを併用すべきところかもしれません。 「shared library(DLL)を作らない」方向に進んでしまったため、 この仕様になってます。 結果、依存度が下がったと喜べばいいのか、管理の手間が掛ると嘆けばよいのか……。

boost::python::objectを外すのは考えにくいので、 「Swigとboost::pythonのexport機能が重複して肥大化するのを避ける」 という意味はありそうです。

C/C++クラスはnoncopyableである

boost::noncopyableをprivate継承するか、 copy constructor と 代入演算子をdeleteします。

shared_ptr, intrusive_ptrに対応する

std::shared_ptr は、boost::python::register_ptr_to_pythonと合わなかったため、 boost::shared_ptrを使用しています。

ComPtr的なインターフェースを持つCライブラリをモジュールにするため、 intrusive_ptrを使用しています。

factory, addref, releaseを持つクラスに対応する

コード内では、Qlassがstatic な create(= factory)を持ち、 privateなconstructorを持っています。 make_sharedのために、helperを使っています。下記を参考にさせていただきました。

std::make_shared から private コンストラクタを呼び出す

XlassがCom的なaddref, releaseを持っていて、参照カウンタは自前で管理しています。 構築はstatic な createでmallocしてplacement newしています。 参照カウンタが0になったら、destructorを呼んで、freeします。

文章にすると、なんのことやら……。"placement new" は今回の本質ではありません。

実装

main.cc

#include <Python.h>
#include <boost/python.hpp>

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/intrusive_ptr.hpp>

#include <iostream>
#include <stdexcept>
#include <memory>


namespace {

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
class PythonInitializer {
  // variable  -----------------------------------------------------------------
  wchar_t m_ProgramName[FILENAME_MAX];

  // constructor  --------------------------------------------------------------
 public:
  inline explicit PythonInitializer(int argc, const char* argv[]);
  inline /* virtual */ ~PythonInitializer() noexcept;

 private:
  PythonInitializer(const PythonInitializer&);
  PythonInitializer& operator = (const PythonInitializer&) noexcept;
};
// =============================================================================
inline PythonInitializer::PythonInitializer(int argc, const char* argv[]) :
    m_ProgramName() {
  if (strlen(argv[0]) > 0) {
    if (mbstowcs(m_ProgramName, argv[0], FILENAME_MAX) <= 0) {
      throw std::runtime_error("ERROR!: PythonInitializer::PythonInitializer: "
                               "faild mbstowcs(...)");
    }
    Py_SetProgramName(m_ProgramName);
  }
  { //  *
    //  *  environment
    //  *  https://docs.python.org/3.3/using/cmdline.html#environment-variables
    //  *

    //  Py_SetPythonHome(a_Home);

    //  Py_SetPath(a_Path);

    //  *
    //  *  see. python<version>/osdef.h
    //  *  DELIM == Linux: ':', Windows: ';'
    //  *
  }
  Py_Initialize();
}
// =============================================================================
inline PythonInitializer::~PythonInitializer() noexcept {
  Py_Finalize();
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
inline char const* greet() {
  return "Hello, World!";
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
inline char const* callback(PyObject* a_Callback) {
  namespace bp = boost::python;
  // bp::object l_Callback(bp::handle<>(bp::borrowed(a_Callback)));
  bp::call<PyObject*>(a_Callback, "call..");
  return "callback";
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
class Klass {
  // function  -----------------------------------------------------------------
 public:
  inline int getNum(int i) const noexcept;

  // constructor  --------------------------------------------------------------
 public:
  inline explicit Klass();
  inline /* virtual */ ~Klass() noexcept;

 private:
  Klass(const Klass&) = delete;
  Klass& operator =(const Klass&) noexcept = delete;
};
// =============================================================================
inline Klass::Klass() {
  std::cout << "Klass::Klass" << std::endl;
}
// =============================================================================
inline Klass::~Klass() noexcept {
  std::cout << "Klass::~Klass" << std::endl;
}
// =============================================================================
int Klass::getNum(int i) const noexcept {
  std::cout << "Klass::getNum" << std::endl;
  return i * 7;
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
class Qlass {
  // function  -----------------------------------------------------------------
 public:
  inline int getNum(int i) const noexcept;

  // constructor  --------------------------------------------------------------
 public:
  static inline boost::shared_ptr<Qlass> create();
 private:
  inline explicit Qlass();
 public:
  inline /* virtual */ ~Qlass() noexcept;

 private:
  Qlass(const Qlass&) = delete;
  Qlass& operator =(const Qlass&) noexcept = delete;
};
// =============================================================================
inline boost::shared_ptr<Qlass> Qlass::create() {
  struct helper : Qlass { helper() : Qlass() {} };
  return std::move(boost::make_shared<helper>());
}
// =============================================================================
inline Qlass::Qlass() {
  std::cout << "Qlass::Qlass" << std::endl;
}
// =============================================================================
inline Qlass::~Qlass() noexcept {
  std::cout << "Qlass::~Qlass" << std::endl;
}
// =============================================================================
int Qlass::getNum(int i) const noexcept {
  std::cout << "Qlass::getNum" << std::endl;
  return i * 70;
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
class Xlass {
  // define  -------------------------------------------------------------------
 public:
  typedef boost::intrusive_ptr<Xlass> ComPtr;

  // function  -----------------------------------------------------------------
 public:
  inline int getNum(int i) const noexcept;

  // variable  -----------------------------------------------------------------
 private:
  int m_Counter;

  // constructor  --------------------------------------------------------------
 public:
  static inline ComPtr create();
  inline void addRef();
  inline void release();

 private:
  inline explicit Xlass();

 public:
  inline /* virtual */ ~Xlass() noexcept;

 private:
  Xlass(const Xlass&) = delete;
  Xlass& operator =(const Xlass&) noexcept = delete;
};
// =============================================================================
inline Xlass::ComPtr Xlass::create() {
  std::cout << "Xlass::create" << std::endl;
  Xlass* this_ = static_cast<Xlass*>(std::malloc(sizeof(Xlass)));
  if (!this_) {
    throw std::runtime_error("Xlass::create: failed malloc");
  }
  try {
    new(this_) Xlass();
  } catch (...) {
    std::free(this_);
    throw;
  }
  return std::move(ComPtr(this_, true /* == this_->addRef(); */ ));
}
// -----------------------------------------------------------------------------
inline void Xlass::addRef() {
  std::cout << "Xlass::addRef" << std::endl;
  ++m_Counter;
}
// -----------------------------------------------------------------------------
inline void Xlass::release() {
  std::cout << "Xlass::release" << std::endl;
  --m_Counter;
  if (0 == m_Counter) {
    this->~Xlass();
    std::free(this);
  }
}
// -----------------------------------------------------------------------------
inline void intrusive_ptr_add_ref(Xlass* p) { p->addRef(); }
inline void intrusive_ptr_release(Xlass* p) { p->release(); }
// =============================================================================
inline Xlass::Xlass() : m_Counter(0) {
  std::cout << "Xlass::Xlass" << std::endl;
}
// =============================================================================
inline Xlass::~Xlass() noexcept {
  std::cout << "Xlass::~Xlass" << std::endl;
}
// =============================================================================
int Xlass::getNum(int i) const noexcept {
  std::cout << "Xlass::getNum" << std::endl;
  return i * 700;
}

// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
BOOST_PYTHON_MODULE(inner) {
  namespace bp = ::boost::python;

  bp::def("greet", greet);
  bp::def("callback", callback);

  bp::register_ptr_to_python< boost::shared_ptr<Klass> >();
  bp::class_<Klass, boost::noncopyable >("Klass")
      .def("getNum", &Klass::getNum);

  bp::register_ptr_to_python< boost::shared_ptr<Qlass> >();
  bp::class_<Qlass, boost::noncopyable >(
      "Qlass", bp::no_init)
      .def("__init__", bp::make_constructor(&Qlass::create))
      .def("getNum", &Qlass::getNum);

  bp::register_ptr_to_python< Xlass::ComPtr >();
  bp::class_<Xlass, boost::noncopyable >(
      "Xlass", bp::no_init)
      .def("__init__", bp::make_constructor(&Xlass::create))
      .def("getNum", &Xlass::getNum);
}

}  // namespace


// /////////////////////////////////////////////////////////////////////////////
// =============================================================================
int main(int argc, const char* argv[]) {
  setlocale(LC_ALL, "");

  PythonInitializer l_PythonInitializer(argc, argv);

  namespace bp = ::boost::python;
  try {
    {  // export
      bp::object
          l_Modules(bp::handle<>(bp::borrowed(PyImport_GetModuleDict())));

      {  // inner
        bp::object l_Inner(bp::handle<>(bp::borrowed(PyInit_inner())));
        l_Modules[PyModule_GetName(l_Inner.ptr())] = l_Inner;
        // PyState_AddModule(l_Inner.ptr(), PyModule_GetDef(l_Inner.ptr()));
      }
    }
    {
      PyRun_SimpleString("print('# #########################################');"
                         "import inner;"
                         "print('dir() = ', dir());"
                         "\n"
                         "for k in dir(inner):"
                         "  print('innner.{} = {}'.format(k,"
                         "                                getattr(inner,k)));");
      PyRun_SimpleString("print('# #########################################');"
                         "print(inner.greet());");
      PyRun_SimpleString("print('# #########################################');"
                         "print(inner.callback(print));");
      PyRun_SimpleString("print('# #########################################');"
                         "print(inner.Klass().getNum(5));");
      PyRun_SimpleString("print('# #########################################');"
                         "print(inner.Qlass().getNum(5));");
      PyRun_SimpleString("print('# #########################################');"
                         "print(inner.Xlass().getNum(5));");
    }
    {  // outer file
      PyRun_SimpleString("import outer;"
                         "outer.outer();");
    }
  } catch(bp::error_already_set) {
    PyErr_Print();
  }

  return 0;
}
    
# ls outer

    .
    ├── outer
    │    ├── __init__.py
    │    ├── outer.py
outer/__init__.py

from . outer import outer

# //////////////////////////////////////////////////////////////////////////////
# ==============================================================================
__all__ = []
    
outer/outer.py

# //////////////////////////////////////////////////////////////////////////////
# ==============================================================================
def outer():
    from inner import greet, callback, Klass, Qlass, Xlass

    def _outer_():
        print("# #########################################")
        print(dir())

        def _greet_():
            print("# -------------------------------------")
            print("greet() = '{0}'".format(greet()))
        _greet_()

        def _callback_():
            print("# -------------------------------------")
            print("callback() = '{0}'".format(callback(print)))
        _callback_()

        def _Klass_():
            print("# -------------------------------------")
            k = Klass()
            print("Klass.getNum(5) = {0}".format(k.getNum(5)))
        _Klass_()

        def _Qlass_():
            print("# --------------------------------------")
            q = Qlass()
            print("Qlass.getNum(5) = {0}".format(q.getNum(5)))
        _Qlass_()

        def _Xlass_():
            print("# --------------------------------------")
            x = Xlass()
            z = x
            print("Xlass.getNum(5) = {0}".format(z.getNum(5)))
        _Xlass_()
    _outer_()
    

実行


# #########################################
dir() =  ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', 'inner']
innner.Klass = <class 'inner.Klass'>
innner.Qlass = <class 'inner.Qlass'>
innner.Xlass = <class 'inner.Xlass'>
innner.__doc__ = None
innner.__name__ = inner
innner.__package__ = None
innner.callback = <Boost.Python.function object at 0x9f139b0>
innner.greet = <Boost.Python.function object at 0x9f14ad8>
# #########################################
Hello, World!
# #########################################
call..
callback
# #########################################
Klass::Klass
Klass::getNum
Klass::~Klass
35
# #########################################
Qlass::Qlass
Qlass::getNum
Qlass::~Qlass
350
# #########################################
Xlass::create
Xlass::Xlass
Xlass::addRef
Xlass::addRef
Xlass::addRef
Xlass::addRef
Xlass::release
Xlass::release
Xlass::release
Xlass::getNum
Xlass::release
Xlass::~Xlass
3500
# #########################################
['Klass', 'Qlass', 'Xlass', 'callback', 'greet']
# -------------------------------------
greet() = 'Hello, World!'
# -------------------------------------
call..
callback() = 'callback'
# -------------------------------------
Klass::Klass
Klass::getNum
Klass.getNum(5) = 35
Klass::~Klass
# --------------------------------------
Qlass::Qlass
Qlass::getNum
Qlass.getNum(5) = 350
Qlass::~Qlass
# --------------------------------------
Xlass::create
Xlass::Xlass
Xlass::addRef
Xlass::addRef
Xlass::addRef
Xlass::addRef
Xlass::release
Xlass::release
Xlass::release
Xlass::getNum
Xlass.getNum(5) = 3500
Xlass::release
Xlass::~Xlass

まとめ

コードを貼っただけですね。 ま、ご参考にでもなれば。

正直、コンパイル手順とかの方が面倒だと思います。 自分はcmakeに任せました。