Boost.Python

是 Boost

中的一个组件, 使用它能够大大简化用 C++ 为 Python 写扩展库的步骤, 提高开发效率, 虽然目前它对 Python 嵌入 C++ 的支持还不是很多, 但也能提供很大方便。 另外, 华宇煜

也编写了一份关于 Boost.Python 简明教程

1 Boost 安装简介

在正式开始使用 Boost.Python 之前, 我们必须先编译 Boost。 首先到 Boost 的官方站点

1.1 Linux 下的编译

首先切换到 Boost 源码所在的路径, 执行 ./configure 脚本, 为配置脚本提供 Python 运行环境相应的参数:



./configure --with-python=/usr/bin/python /
            --with-python-version=2.4 /
            --with-python-root=/usr



然后, 和绝大部分 Linux 程序一行, 执行 make 就可以开始编译了。 编译完毕后, 切换到 root 权限后再执行 make install, 把 Boost 相应的头文件和库文件复制到相应的地方, 就可以使用了。

1.2 使用 MinGW + MSys 在 Windows 下的编译

首先需要编译的是 Boost 的编译工具 bjam, 直接到 bjam 所在目录下, 即 Boost 源码包所在目录下的 /tools/build/jam_src, 执行 build.bat mingw, 稍等片刻, bjam.exe 就编译好了。 把编译好的 bjam.exe 复制到你的 %PATH% 路径能够直接找到的地方, 为后续的编译工作做好准备。

接下来, 切换到 Boost 源码所在路径, 执行 bjam 进行编译。 我们需要提供关于 Python 的一些参数, 变量 PYTHON_ROOT 指向 Python 运行环境所在的目录, 变量 PYTHON_VERSION 的值为 Python 的版本号, 如果你的 Python 安装路径与滇狐不同, 请将相应的变量修改为你机器上相应的路径, 编译命令行如下:



bjam.exe "-sTOOLS=mingw" "-sPYTHON_ROOT=E:/Python" "-sPYTHON_VERSION=2.4"



编译完毕后, 你将会在你的 C:/Boost 下找到编译得到的 Boost 相应头文件与库文件, 你可以根据你的需要将它移动到别的地方备用。

2 使用 Boost.Python 嵌入 Python 模块到 C++

Boost.Python 目前并没有提供完整的将 Python 模块嵌入到 C++ 的包装库, 因此许多工作我们还必须通过 Python C API 来进行。 但是, 利用 Boost.Python 中提供的一些模块, 能够给我们的工作带来极大便利。

2.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.2 调用 Python 函数

导入了 Python 模块之后, 调用 Python 函数就非常容易了。 Boost.Python 里封装了一个非常好用的模板函数 boost::python::call_method, 它可以替你处理调用函数时需要处理的种种细节, 将你从 Python C API 中繁琐的“将参数打包为 PyObject *”、 “构造 Tuple”、 “传递 Tuple”、 “解包返回值”等工作中彻底解放出来, 你只需要这样:



boost::python::call_method<返回值类型>(模块指针, "Python 函数名", 参数 1, 参数 2, ...);



模块指针可以通过我们前面得到的 _moduleget 方法获得, 例如:



... bool result; std::string config_file; ... try { return boost::python::call_method<bool>(_module->get(), "initialize", config_file); } catch



2.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);

    ...



3 在嵌入的 Python 模块中调用 C++ 程序

通过动态链接库的方式使用 Boost.Python 导出 C++ 模块到 Python 程序与在 C++ 可执行程序中导出模块给嵌入的 Python 解释器, 编写程序的方式几乎是完全相同的。 因此这里只简单介绍导出普通函数的方法, 想详细了解更多高级功能, 如导出 C++ 类、 导出可被 Python 重载的类等, 可以参看华宇煜的 Boost.Python 简明教程或官方 Boost.Python

3.1 导出 C++ 函数

首先使用 BOOST_PYTHON_MODULE 宏定义需要导出给 Python 的模块, 然后用 boost::python::def 语句定义导出的函数、 参数列表以及 Doc String, 例如在下面的例子中, 我们导出了一个 C++ 函数 yukigettext, 并重命名为 gettext



const char *yukigettext(const char *id);

BOOST_PYTHON_MODULE(yuki)
{
    boost::python::def("gettext", yukigettext,
        boost::python::args("id"), "Translate message.");
}



3.2 为 Python 初始化 C++ 模块

使用 BOOST_PYTHON_MODULE(name) 定义了 Python 模块后, 该宏会自动生成一个函数 initname, 我们需要在 Py_Initialize() 之后调用这个自动生成的函数, 初始化导出到 Python 的模块。 例如我们刚才导出模块用的宏 BOOST_PYTHON_MODULE(yuki), 因此初始化的时候就应该调用 inityuki()



... Py_Initialize(); inityuki(); ...



3.3 在 Python 模块中调用 C++ 模块

此时我们在 Python 模块中只需要像普通的 Python 模块那样, 将导入的 C++ 模块用 import 语句加载进来, 就可以调用了:



import yuki ... print yuki.gettext("This is a test!")






<script type="text/javascript"> google_ad_client = "pub-6027327391667521"; google_ad_width = 468; google_ad_height = 60; google_ad_format = "468x60_as"; google_ad_type = "text_image"; google_ad_channel =""; google_color_border = "FFA500"; google_color_bg = "FFFFFF"; google_color_link = "000000"; google_color_url = "0000FF"; google_color_text = "000000"; </script> <script style="display: none;" type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

<script language="javascript" type="text/javascript" src="http://js.a.s14.51.la/7239.js"> </script><script language="javascript" type="text/javascript" src="http://js.users.51.la/59490.js"></script>

<script>var a9490tf="51la";var a9490pu="";var a9490pf="51la";var a9490su=window.location;var a9490sf=document.referrer;var a9490of="";var a9490op="";var a9490ops=1;var a9490ot=1;var a9490d=new Date();var a9490color="";if (navigator.appName=="Netscape"){a9490color=screen.pixelDepth;} else {a9490color=screen.colorDepth;}</script><script>a9490tf=top.document.referrer;</script><script>a9490pu =window.parent.location;</script><script>a9490pf=window.parent.document.referrer;</script><script>a9490ops=document.cookie.match(new RegExp("(^| )AJSTAT_ok_pages=([^;]*)(;|$)"));a9490ops=(a9490ops==null)?1: (parseInt(unescape((a9490ops)[2]))+1);var a9490oe =new Date();a9490oe.setTime(a9490oe.getTime()+60*60*1000);document.cookie="AJSTAT_ok_pages="+a9490ops+ ";path=/;expires="+a9490oe.toGMTString();a9490ot=document.cookie.match(new RegExp("(^| )AJSTAT_ok_times=([^;]*)(;|$)"));if(a9490ot==null){a9490ot=1;}else{a9490ot=parseInt(unescape((a9490ot)[2])); a9490ot=(a9490ops==1)?(a9490ot+1):(a9490ot);}a9490oe.setTime(a9490oe.getTime()+365*24*60*60*1000);document.cookie="AJSTAT_ok_times="+a9490ot+";path=/;expires="+a9490oe.toGMTString();</script><script>a9490of=a9490sf;if(a9490pf!=="51la"){a9490of=a9490pf;}if(a9490tf!=="51la"){a9490of=a9490tf;}a9490op=a9490pu;try{lainframe}catch(e){a9490op=a9490su;}document.write('


');</script>