使用 Boost.Python 嵌入 Python 模块到 C++
时间:2006-09-05 来源:YuanXU
使用 Boost.Python 嵌入 Python 模块到 C++
Boost.Python 目前并没有提供完整的将 Python 模块嵌入到 C++ 的包装库, 因此许多工作我们还必须通过 Python C API 来进行。 但是, 利用 Boost.Python 中提供的一些模块, 能够给我们的工作带来极大便利。
1 修改模块加载路径,装入 Python 模块
与任何一个其它 Python 嵌入 C/C++ 的程序一样, 我们需要在第一条 #include 语句处含入 Python.h, 并在程序开始时调用 Py_Initialize(), 在程序结束时调用 Py_Finalize()。
接下来, 我们便可以开始准备装入 Python 模块了。 为了让 Python 解释器能够正确地找到 Python 模块所在的位置, 我们需要将 Python 模块所在的路径添加到模块搜索路径中, 添加搜索路径的 Python 语句如下:
import sys
if not '/module/path' in sys.path:
sys.path.append('/module/path')
我们使用 Python C API 执行类似的语句, 就能将模块的搜索路径添加到 Python 解释器中。 添加了搜索路径后, 就可以通过 PyImport_ImportModule 函数加载 Python 模块了。 PyImport_ImportModule 返回值是 PyObject *, 为了避免手工处理繁琐的引用计数等问题, 我们求助于 Boost.Python 提供的 handle 模块, 将 PyObject * 封装起来, 以方便使用, 代码如下:
#include <boost/python.hpp>
...
boost::python::handle<>* _module; // Module handle.
std::string path; // Path of the Python module.
std::string module; // Module name.
...
try
{
PyRun_SimpleString("import sys");
PyRun_SimpleString((std::string("if not '") + path
+ "' in sys.path: sys.path.append('" + path + "')").c_str());
_module = new boost::python::handle<>(
PyImport_ImportModule((char *) module));
...
}
catch (...)
{
PyErr_Print();
PyErr_Clear();
delete _module;
_module = NULL;
return false;
}
...
需要注意的是, 通过 Python C API 加载的 Python 解释器并没有把当前路径列入默认的搜索路径中。 因此, 即使你的 Python 模块就存放在当前路径, 你也必须使用上面的代码将当前路径添加到搜索路径中之后, 才能通过 PyImport_ImportModule 加载到模块。
当 Python 模块使用完毕或程序结束时, 请使用 delete 将 _module 指针释放, handle 被释放的时候会自动释放相应的 Python 模块并回收相应资源。
2 调用 Python 函数
导入了 Python 模块之后, 调用 Python 函数就非常容易了。 Boost.Python 里封装了一个非常好用的模板函数 boost::python::call_method, 它可以替你处理调用函数时需要处理的种种细节, 将你从 Python C API 中繁琐的“将参数打包为 PyObject *”、 “构造 Tuple”、 “传递 Tuple”、 “解包返回值”等工作中彻底解放出来, 你只需要这样:
boost::python::call_method<返回值类型>(模块指针, "Python 函数名",
参数 1, 参数 2, ...);
模块指针可以通过我们前面得到的 _module 的 get 方法获得, 例如:
...
bool result;
std::string config_file;
...
try
{
return boost::python::call_method<bool>(_module->get(), "initialize",
config_file);
}
catch (...)
{
PyErr_Print();
PyErr_Clear();
...
}
...
3 使用 Python 类对象
使用 Python C API 调用 Python 函数和调用 Python 类对象是没有太大区别的, 我们只需要调用类的构造方法, 得到一个类对象, 然后把该类的指针看做模块指针, 按照前面调用普通函数的方法调用类成员方法就可以了。 例如, 下列代码从 _module 中创建了一个 YukiSession 对象, 然后调用了其中的 on_welcome 方法。 除了展示调用类成员方法外, 这段代码还展示了构造 Python list 对象、 从 Python list 对象中获取元素的方式。
...
boost::python::handle<> _yukisession;
...
// Retrieve the module handle and namespace handle.
boost::python::object main_module(*_module);
boost::python::object main_namespace = main_module.attr("__dict__");
// Call the method and get the object handle.
_yukisession = boost::python::handle<>((PyRun_String(
"YukiSession()", Py_eval_input,
main_namespace.ptr(), main_namespace.ptr())));
...
// Compose a list.
boost::python::list param;
param.append(boost::python::str(_addr.get_host_addr()));
param.append(boost::python::str());
// Call the method and retrieve the result.
// Method is equivalent to:
// "bool __thiscall YukiSession::on_welcome(list param);"
result = boost::python::call_method<bool>
(_yukisession.get(), "on_welcome", param);
// Extract an item from a list.
str = boost::python::call_method<std::string>
(param.ptr(), "__getitem__", 1);
...