Boost.Python 是 Boost 中的一個(gè)組件, 使用它能夠大大簡(jiǎn)化用 C++ 為 Python 寫(xiě)擴(kuò)展庫(kù)的步驟,提高開(kāi)發(fā)效率, 雖然目前它對(duì) Python 嵌入 C++ 的支持還不是很多, 但也能提供很大方便。另外, 華宇煜也編寫(xiě)了一份關(guān)于 Boost.Python 簡(jiǎn)明教程。
1 Boost 安裝簡(jiǎn)介
在正式開(kāi)始使用 Boost.Python 之前, 我們必須先編譯 Boost。 首先到 Boost 的官方站點(diǎn) 下載 Boost 的源碼包, 把它們解壓到你喜歡的目錄,為編譯做好準(zhǔn)備。 另外, 在正式安裝 Boost.Python 之前, 我們必須先正確安裝 Python。
1.1 Linux 下的編譯
首先切換到 Boost 源碼所在的路徑, 執(zhí)行 ./configure
腳本,為配置腳本提供 Python 運(yùn)行環(huán)境相應(yīng)的參數(shù):
./configure --with-python=/usr/bin/python \
--with-python-version=2.4 \
--with-python-root=/usr
然后, 和絕大部分 Linux 程序一行, 執(zhí)行 make
就可以開(kāi)始編譯了。編譯完畢后, 切換到 root 權(quán)限后再執(zhí)行 make install
,把 Boost 相應(yīng)的頭文件和庫(kù)文件復(fù)制到相應(yīng)的地方, 就可以使用了。
1.2 使用 MinGW + MSys 在 Windows 下的編譯
首先需要編譯的是 Boost 的編譯工具 bjam, 直接到 bjam 所在目錄下, 即 Boost 源碼包所在目錄下的 \tools\build\jam_src
, 執(zhí)行 build.bat mingw
,稍等片刻, bjam.exe 就編譯好了。 把編譯好的 bjam.exe 復(fù)制到你的 %PATH%
路徑能夠直接找到的地方, 為后續(xù)的編譯工作做好準(zhǔn)備。
接下來(lái), 切換到 Boost 源碼所在路徑, 執(zhí)行 bjam 進(jìn)行編譯。 我們需要提供關(guān)于 Python 的一些參數(shù), 變量 PYTHON_ROOT 指向 Python 運(yùn)行環(huán)境所在的目錄,變量 PYTHON_VERSION 的值為 Python 的版本號(hào), 如果你的 Python 安裝路徑與滇狐不同,請(qǐng)將相應(yīng)的變量修改為你機(jī)器上相應(yīng)的路徑, 編譯命令行如下:
bjam.exe "-sTOOLS=mingw" "-sPYTHON_ROOT=E:\Python" "-sPYTHON_VERSION=2.4"
編譯完畢后, 你將會(huì)在你的 C:\Boost
下找到編譯得到的 Boost 相應(yīng)頭文件與庫(kù)文件, 你可以根據(jù)你的需要將它移動(dòng)到別的地方備用。
2 使用 Boost.Python 嵌入 Python 模塊到 C++
Boost.Python 目前并沒(méi)有提供完整的將 Python 模塊嵌入到 C++ 的包裝庫(kù),因此許多工作我們還必須通過(guò) Python C API 來(lái)進(jìn)行。 但是, 利用 Boost.Python 中提供的一些模塊, 能夠給我們的工作帶來(lái)極大便利。
2.1 修改模塊加載路徑,裝入 Python 模塊
與任何一個(gè)其它 Python 嵌入 C/C++ 的程序一樣, 我們需要在第一條 #include
語(yǔ)句處含入 Python.h
, 并在程序開(kāi)始時(shí)調(diào)用 Py_Initialize()
,在程序結(jié)束時(shí)調(diào)用 Py_Finalize()
。
接下來(lái), 我們便可以開(kāi)始準(zhǔn)備裝入 Python 模塊了。 為了讓 Python 解釋器能夠正確地找到 Python 模塊所在的位置, 我們需要將 Python 模塊所在的路徑添加到模塊搜索路徑中,添加搜索路徑的 Python 語(yǔ)句如下:
import sys
if not '/module/path' in sys.path:
sys.path.append('/module/path')
我們使用 Python C API 執(zhí)行類似的語(yǔ)句, 就能將模塊的搜索路徑添加到 Python 解釋器中。 添加了搜索路徑后, 就可以通過(guò) PyImport_ImportModule
函數(shù)加載 Python 模塊了。 PyImport_ImportModule
返回值是 PyObject *
, 為了避免手工處理繁瑣的引用計(jì)數(shù)等問(wèn)題,我們求助于 Boost.Python 提供的 handle
模塊, 將 PyObject *
封裝起來(lái), 以方便使用, 代碼如下:
#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;
}
...
需要注意的是, 通過(guò) Python C API 加載的 Python 解釋器并沒(méi)有把當(dāng)前路徑列入默認(rèn)的搜索路徑中。因此, 即使你的 Python 模塊就存放在當(dāng)前路徑, 你也必須使用上面的代碼將當(dāng)前路徑添加到搜索路徑中之后,才能通過(guò) PyImport_ImportModule
加載到模塊。
當(dāng) Python 模塊使用完畢或程序結(jié)束時(shí), 請(qǐng)使用 delete
將 _module
指針釋放, handle
被釋放的時(shí)候會(huì)自動(dòng)釋放相應(yīng)的 Python 模塊并回收相應(yīng)資源。
2.2 調(diào)用 Python 函數(shù)
導(dǎo)入了 Python 模塊之后, 調(diào)用 Python 函數(shù)就非常容易了。 Boost.Python 里封裝了一個(gè)非常好用的模板函數(shù) boost::python::call_method
,它可以替你處理調(diào)用函數(shù)時(shí)需要處理的種種細(xì)節(jié), 將你從 Python C API 中繁瑣的“將參數(shù)打包為 PyObject *
”、 “構(gòu)造 Tuple”、 “傳遞 Tuple”、 “解包返回值”等工作中徹底解放出來(lái), 你只需要這樣:
boost::python::call_method<返回值類型>(模塊指針, "Python 函數(shù)名",
參數(shù) 1, 參數(shù) 2, ...);
模塊指針可以通過(guò)我們前面得到的 _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();
...
}
...
2.3 使用 Python 類對(duì)象
使用 Python C API 調(diào)用 Python 函數(shù)和調(diào)用 Python 類對(duì)象是沒(méi)有太大區(qū)別的,我們只需要調(diào)用類的構(gòu)造方法, 得到一個(gè)類對(duì)象, 然后把該類的指針看做模塊指針,按照前面調(diào)用普通函數(shù)的方法調(diào)用類成員方法就可以了。 例如, 下列代碼從 _module
中創(chuàng)建了一個(gè) YukiSession
對(duì)象, 然后調(diào)用了其中的 on_welcome
方法。 除了展示調(diào)用類成員方法外, 這段代碼還展示了構(gòu)造 Python list 對(duì)象、 從 Python list 對(duì)象中獲取元素的方式。
...
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 模塊中調(diào)用 C++ 程序
通過(guò)動(dòng)態(tài)鏈接庫(kù)的方式使用 Boost.Python 導(dǎo)出 C++ 模塊到 Python 程序與在 C++ 可執(zhí)行程序中導(dǎo)出模塊給嵌入的 Python 解釋器, 編寫(xiě)程序的方式幾乎是完全相同的。因此這里只簡(jiǎn)單介紹導(dǎo)出普通函數(shù)的方法, 想詳細(xì)了解更多高級(jí)功能, 如導(dǎo)出 C++ 類、 導(dǎo)出可被 Python 重載的類等, 可以參看華宇煜的 Boost.Python 簡(jiǎn)明教程或官方 Boost.Python 文檔。
3.1 導(dǎo)出 C++ 函數(shù)
首先使用 BOOST_PYTHON_MODULE
宏定義需要導(dǎo)出給 Python 的模塊,然后用 boost::python::def
語(yǔ)句定義導(dǎo)出的函數(shù)、 參數(shù)列表以及 Doc String, 例如在下面的例子中, 我們導(dǎo)出了一個(gè) C++ 函數(shù) 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 模塊后,該宏會(huì)自動(dòng)生成一個(gè)函數(shù) initname
, 我們需要在 Py_Initialize()
之后調(diào)用這個(gè)自動(dòng)生成的函數(shù), 初始化導(dǎo)出到 Python 的模塊。 例如我們剛才導(dǎo)出模塊用的宏 BOOST_PYTHON_MODULE(yuki)
, 因此初始化的時(shí)候就應(yīng)該調(diào)用 inityuki()
:
...
Py_Initialize();
inityuki();
...
3.3 在 Python 模塊中調(diào)用 C++ 模塊
此時(shí)我們?cè)?Python 模塊中只需要像普通的 Python 模塊那樣, 將導(dǎo)入的 C++ 模塊用 import
語(yǔ)句加載進(jìn)來(lái), 就可以調(diào)用了:
import yuki
...
print yuki.gettext("This is a test!")