http://bug.codecarver.org/python/boost.html
Boost.Python 是 Boost 的一个组件。而 Boost 是目前很红火的准 C++ 标准库,它提供了很多组件使得人们可以用 C++ 语言更方便地实现更多的功能。 Boost.Python 就是 Boost 众多组件中的一个。但它是个特例,它的目的不是单纯地增强 C++ 语言的功能,而是希望利用 C++ 语言来增强 Python 语言的能力。使用 Boost.Python 库,我可以方便地将 C++ 语言写的程序库用于 Python 语言,可以用 Python 更好地完成更多的任务。
好吧,我承认,我忘了说很重要的一点。那就是,通过 Boost.Python,我们不仅仅可以用 C++ 来扩展 Python,我们还可以将 Python 嵌入 C++。其实 Python 运行环境本身就提供了一套嵌入 Python 到其它语言的 API,不过是 C 语言写的。有了这套 API,我们就完全可以将 Python 嵌入到 C/C++ 语言程序中去。但是,由于 Python 本身是一门面向对象的、动态类型的语言,还带垃圾收集,而 C 是个面向过程的、静态类型的、不带垃圾收集的程序设计语言。因此,直接使用这套 C API 非常痛苦。 Boost.Python 用面向对象 + 模板的方法将这套 C API 重新包装了一下,我们用起来就清爽多了。不过,目前这个包装还不完善,因此可能还是需要直接使用一部分 Python C API。等它长大了我再来介绍它。
1.1 Boost.Python 的特性
目前 Boost.Python 特性包括:
支持 C++ 引用和指针
Globally Registered Type Coercions
自动跨模块类型转换
高效的函数重载
C++ 异常到 Python 异常的转化
参数默认值
关键字参数
在 C++ 程序中访问 Python 语言中的对象
导出 C++ 迭代器为 Python 迭代器
文档字符串
1.2 跟其它工具的比较
目前有多个工具可以实现跟 Boost.Python 类似的功能,如 SWIG,SIP等。但是它们有很大的不同。SWIG 和 SIP 都定义了一种接口描述语言。我需要先写一个接口描述文件,用于描述我要导出的 C++ 函数和类。然后通过一个翻译器,将接口描述文件翻译成 C++ 程序。最后编译连接生成的 C++ 程序来生成扩展库。而 Boost.Python 用于导出 C++ 函数和类的时候,我需要添加的也是 C++ 的代码,这是 Boost.Python 的最大特点之一。
SWIG 比较适合用来包装 C 语言程序,最近也开始增强一些对 C++ 的支持,但是到目前还不支持嵌套类等 C++ 特性。SIP 似乎除了用在包装 Qt 库之外,就没几个人用。而 Boost.Python 可能是这三者之间对 C++ 支持最好的一个。不过 Boost.Python 也有缺点,就是它使用了大量模板技巧,因此当要导出的元素比较多时,编译非常慢。不过幸好作为“胶水”,我并不需要经常修改和重编译,而且如果使用预编译头的话可以进一步提高编译速度。
Boost.Python 的另外一个优点是,它的设计目标就是让 C++ 程序库可以透明地导出到 Python 中去。即在完全不修改原来 C++ 程序的情况下,导出给 Python 用。在这种设计理念下设计出来的 Boost.Python 比同类工具支持了给完善的 C++ 特性,能够最大程度地保证不修改原 C++ 程序。要知道贸然修改别人的程序,往往会带来许多难以察觉的错误。
基于以上几点,我推荐大家在需要的时候使用 Boost.Python,而不是其它。这也是我写这篇文章的最大动力 :-)。
1.3 安装 Boost.Python
这里假定你已经阅读过 Boost 的安装说明,已经会默认安装 Boost 库。
Boost.Python 是 Boost 的一个组件,可以随 Boost 的众多组件一块安装,也可以单独安装。由于 Boost 中组件太多,我通常并不需要所有的组件。只安装我需要的组件可以节省很多时间和空间。安装 Boost.Python 和安装 Boost 的步骤差不多,但是安装 Boost.Python 我还需要:
已经安装了 Python 运行环境
环境变量 PYTHON_ROOT 指向 Python 运行环境所在的目录,
环境变量 PYTHON_VERSION 的值为 Python 的版本号。
例如 Windows 下:PYTHON_ROOT=C:/Python24, PYTHON_VERSION=2.4。而 Linux 下,若我安装了 Python 2.4,其可执行文件为 /usr/bin/python2.4,运行库在 /usr/lib/python2.4,则:PYTHON_ROOT=/usr,PYTHON_VERSION=2.4
默认情况下,使用命令
Linux 下
bjam "-sTOOLS=gcc" install
Windows 下
bjam "-sTOOLS=vc7" install
每个组件会生成八份,分别是调试版/发布版、静态连接库/动态连接库、支持单线程/多线程的不同组合。对于 Boost.Python,最多可能生成十六份,因为还可以选择使用 Python 运行库的调试版/发布版。这么多个版本,我未必都用得着,而且生成还很慢。其实大多数情况下,我只需要用如下命令生成一个版本:
Linux 下
bjam "-sTOOLS=gcc" "-sBUILD=release <runtime-link>dynamic <threading>multi" install
Windows 下
bjam "-sTOOLS=vc7" "-sBUILD=release <runtime-link>dynamic <threading>multi" install
即选择使用发布版、动态连接库和支持多线程的版本。默认情况下会选择使用 Python 运行库的发布版,这也够用了。编译完成后,我会得到相应的库。比如我安装的 Boost 的版本是 1.32,则 Linux 下为 libboost_python-gcc-mt.so, Windows 下为 boost_python-vc7-mt-1_32.lib 和 boost_python-vc7-mt.dll。
除了组件库,我还需要安装头文件。在 Boost 的源码包的 include 目录下有一大堆 .h 文件,全部复制就可以了。值得注意的是使用 bjam 安装的 Boost.Python 经常漏掉一些头文件,导致我使用库写程序的时候经常说“xxx 头文件”找不到。我还是自己动手复制一份头文件比较安全。
在 Linux 下,安装完后还要用 root 用户运行一下
ldconfig
1.4 例子:Hello World
我们现看一个 Hello World 的例子,导出一个 C++ 函数。
[HTML]a52a2a 1 [HTML]0000ff/*
[HTML]a52a2a 2 [HTML]0000ff * filename: helloworld.cpp
[HTML]a52a2a 3 [HTML]0000ff [HTML]0000ff*/
[HTML]a52a2a 4 [HTML]a020f0#include [HTML]ff00ff<cstdio>
[HTML]a52a2a 5 [HTML]a020f0#include [HTML]ff00ff<boost/python.hpp> [HTML]0000ff// 包含 Boost.Python 的头文件
[HTML]a52a2a 6
[HTML]a52a2a 7 [HTML]2e8b57void speak() [HTML]0000ff// 一个将被导出的 C++ 函数
[HTML]a52a2a 8 {
[HTML]a52a2a 9 printf([HTML]ff00ff"Hello World![HTML]6a5acd/n[HTML]ff00ff");
[HTML]a52a2a10 }
[HTML]a52a2a11
[HTML]a52a2a12 [HTML]a52a2ausing [HTML]2e8b57namespace boost::python; [HTML]0000ff// 引入名字空间
[HTML]a52a2a13
[HTML]a52a2a14 BOOST_PYTHON_MODULE(Baby) [HTML]0000ff// 胶水代码,导出一个名为“Baby”的模块
[HTML]a52a2a15 {
[HTML]a52a2a16 def([HTML]ff00ff"speak", &speak); [HTML]0000ff// 该模块中有一个函数,函数名为“speak”,入口地址为 &speak
[HTML]a52a2a17 }
[HTML]a52a2a18
现在我准备将上面的例子编译生成动态连接库,作为 Python 扩展库在 Python 中调用。
在 Linux 下用如下命令生成动态连接库 Baby.so:
g++ helloworld.cpp -o Baby.so -shared -I/usr/include/python2.4 -lboost_python-gcc-mt
在 Windows 下需要先设置一些环境变量 INCLUDE,让编译起能够找到 Boost.Python 的头文件和 Python 的头文件,还要设置环境变量 LIB,让连接器能够找到 Boost.Python 的库文件和 Python 的库文件:
set INCLUDE=%INCLUDE%;"c:/python24/include";"c:/boost/include/boost-1_32" set LIB=%LIB%;"c:/python24/libs";"c:/boost/lib"
然后用如下命令编译连接生成动态连接库 Baby.dll:
cl helloworld.cpp /c /EHsc /LD link helloworld.obj /OUT:"Baby.dll" /DLL python24.lib boost_python-vc71-mt-1_32.lib
如果使用 VC7 等 IDE 来开发,同样需要对 VC 工程做相应的调整。
打开 Python 解释器,验证我是否成功:
python >>> import Baby >>> Baby.speak() Hello World! >>>
这个例子中,我导出了一个函数 speak 到模块 Baby 中。最后生成的动态连接库就是我要的 Python 扩展模块。在 Python 中可以通过模块名 "Baby" 来引入,然后使用该模块下的函数。
提醒一下,这个例子虽然简单,但是第一次用 Boost.Python 写 Python 扩展库的人未必能够一次成功。可能因为我们安装 Python 运行环境和 Boost.Python 组件时选择的安装位置不同,也可能因为生成库的设置未必一致。就算这些都一样,我们还可能安装了其它版本。但道理都是一样的,我们需要注意的只有四点:
在编译时,需要让编译器知道 Boost.Python 和 Python 的头文件所在目录;
在连接时,需要让连接器知道 Boost.Python 和 Python 的库文件所在目录;
在连接时,让连接器知道我们要生成的是动态连接库,并且注意动态连接库的主文件名要跟模块名一致;
在运行 Python 解释器并装入 Baby 模块时,需要在当前目录或系统目录下找得到 Boost.Python 和 Baby 模块对应的动态连接库;
如果使用不同的操作系统、编译器或者 IDE、不同版本的 Python 运行环境或 Boost.Python 库,成功运行上面的例子需要的设置可能不同,但我们只要注意保证上面四点,应该不会有什么大问题。请在能够顺利运行这个例子之后,再接着看后面的内容。