アプリケーションにPythonを組み込むサンプルです。
Python 3.3, boost 1.55で実装しました。
要求
- shared library(DLL)を作らない
- C/C++から外部へモジュールをexportする (== 外部から、C/C++内のモジュールをimportする)
- C/C++内から、文字列としてPythonスクリプトを実行する
- C/C++内から、外部のモジュールをimportする
- Pythonとboost::pythonのみを使用する
- C/C++クラスはnoncopyableである
- shared_ptr, intrusive_ptrに対応する
- 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に任せました。
0 件のコメント:
コメントを投稿