此文于2009-09-16被推荐到首页 如何被推荐?
关于 C++ 和 Python 之间互相调用的问题,可以查找到很多资料。本文将主要从解决实际问题的角度看如何构建一个 Python 和 C++ 混合系统。
力为 2009
一、概念
混合系统 :采用多种语言构建的系统。比如 Native C++ 和 dotNet 的混合系统, Python/Lua 和 C++ 的混合系统。
Python 和 C++ 各有各的优缺点,构建两者混合系统的主要目的就是利用 Python 的灵活性和 C++ 高效性,增加程序的扩展性。当然还有其他好处,此处就不展开谈了。脚本语言有很多,至于为什么不采用其他的(如 Lua 、 Ruby 等),这里也不考虑。脚本语言的采用主要是个人喜好的问题。采用 Lua 可以查到很多资料,以前也曾用 LuaBind实现过 。
二、系统架构
混合系统可以简化成如下模型:
系统的运行过程为:
首先在应用程序中嵌入脚本语言解析器,然后用脚本语言提供的 C/C++ API 封装已有的 C++ 功能库,再用脚本调用封装的服务。此后,应用层便可以执行脚本里的内容,其效果与应用层直接调用服务层类似。
三、实现
本例中采用脚本语言 Python ,因此主要工作为在应用层嵌入 Python 解析器,用 Python 封装服务层。为了说明问题,应用层的实现为 PythonConsole.ex ,服务层的实现为 Mythma.dll ,封装层为 MythmaPy.dll 。
1 、假定 Mythma.dll 中的一个类为 CHelloWorld :
class MythmaAPI CHelloWorld { public: CHelloWorld(void); ~ CHelloWorld(void); void SetId( int nId) { m_Id = nId;} int GetId() const { return m_Id; } private: int m_Id; } ;
2 、现在用 Boost.Python 封装该类:
#include " HelloWorld.h " #include < boost / python.hpp > using namespace boost::python; BOOST_PYTHON_MODULE(MythmaPy) { class_ < CHelloWorld > ( " CHelloWorld " ) .def( " SetId " , & CHelloWorld::SetId) .def( " GetId " , & CHelloWorld::GetId) ; }
3 、在 Python 脚本中调用该封装
# import MythmaPy from MythmaPy import * world = CHelloWorld() world.SetId( 100 ) print world.GetId()
4 、在 PythonConsole.exe 中嵌入 Python 解析器
int main( int argc, char ** argv) { // Initialize the interpreter Py_Initialize(); if (python::handle_exception(exec_mythma)) { if (PyErr_Occurred()) { BOOST_ERROR( " Python Error detected " ); PyErr_Print(); } else { BOOST_ERROR( " A C++ exception was thrown for which " " there was no exception translator registered. " ); } } char ch; std::cin >> ch; // Boost.Python doesn't support Py_Finalize yet, so don't call it! return boost::report_errors(); }
在 PythonConsole 中调用 Python 脚本
void exec_mythma() { std::cout << " exec extension module Mythma " << std::endl; python::dict global; python:: object result = python::exec_file( " .//axxscript.py " , global, global); python:: object world = global[ " world " ]; CHelloWorld & py = python::extract < CHelloWorld &> (world) BOOST_EXTRACT_WORKAROUND; std::cout << py.GetId() << std::endl; std::cout << " success! " << std::endl; }
四、结果分析
到此为止,一切感觉都很良好。但假如现在就运行程序,为得到如下的错误:
假如直接用 Python.exe 运行上面的 Python 脚本,并不会产生错误,切运行结果正确。这是什么原因引起的呢?
从错误提示可以看出加载 MythmaPy 模块失败。这是因为使用 Boost.Python 需要注意一个问题,即在执行脚本之前,需要用 init<ModuleName> 注册封装模块。
加上次限制后,我们就需要对上面的架构做些调整, MythmaPy.dll 需要暴露接口供 PythonConsole.exe 在恰当的时机调用注册该模块。
五、小结
搭建混合系统很简单,麻烦的地方在于 API 的封装。即使采用 dotNet 封装 Native C++ 也是麻烦在这个地方。如果采用一种统一的 API 描述方法,使用工具自动生成封装,这样就可以大大简化封装的复杂度,若可以按需生成不同语言的封装,那更是锦上添花了。
本例的搭建环境为 VS2008 , Python2.5 , Boost1.38 。完整工程下载 在这里 。
六、广告
