跳到正文
EverET.org

C++与Python混合编程

混合语言策略可以汲取各语言之所长,让开发更加敏捷。混合语言策略在在应用得当时可以让程序更加优雅。

在《Unix编程艺术》中,Raymond说道:

混合语言是一种知识密集型(而不是编码密集型)的编程。要让它能够工作,我们不仅应该具备相当数量的多种语言应用知识,并且还需要具备能够判断这些语言在什么地方最适合、以及怎样把他们组合在一起的潜经验。

在混合语言编程中,我们遇到的第一个问题是如何需要让他们可以互相调用。也就是像C++可以调用Python的函数、Python又可以调用C++的函数。

对于C++和Python的混合编程主要有两种方式。

  1. 将C++写的模块编译成动态链接库,然后由Python主程序使用。这种一般是单方向的使用。

  2. 用C++主程序调用Python。然后Python中可以使用C++主程序的函数。

对于第一种方式非常简单,我们在此就不讨论了。我们将着重讨论第二种方式。

Simple Exampe of Mixing C++ and Python Code

我们举个例子。以下是C++主程序,做的事情是创建了一个空白的地图,然后加载Python模块构建地图。


// website: 
#include <iostream>
#include <vector>
#include <boost/python.hpp>

class Map
{
public:
    Map() : m_map(10, std::vector<char>(20, '.'))
    {}

    void SetPixel(int x, int y, int val)
    {
	m_map[y][x] = val;
    }

    void Print()
    {
	for (int i = 0; i < m_map.size(); ++i)
	{
	    for (int j = 0; j < m_map[i].size(); ++j)
	    {
		std::cout << m_map[i][j];
	    }
	    std::cout << std::endl;
	}
    }

private:
    std::vector<std::vector<char> > m_map;
};

void InitPython()
{
    Py_Initialize();

    if(!Py_IsInitialized())
    {
	exit(-1);
    }
}

// Get the instance of the map, Singleton Pattern
// only one map instance exists
Map* GetMapInstance()
{
    static Map* the_map = NULL;
    if (!the_map)
    {
	the_map = new Map();
    }
    return the_map;
}

// export c++ function and class to python
BOOST_PYTHON_MODULE(MyEngine)
{
    using namespace boost::python;
    def("GetMapInstance", GetMapInstance,
	return_value_policy< reference_existing_object >());
    class_<Map>("Map", "Game Map")
    	.def("Print", &Map::Print)
    	.def("SetPixel", &Map::SetPixel,
	    (arg("x"), "y", "val"));
}

int main()
{
    try
    {
	using namespace boost::python;

	InitPython();
	initMyEngine(); // init MyEngine Module

	// Add current path to sys.path. You have to
	// do this in linux. While in Windows,
	// current path is already in sys.path.
	object main_module = import( "__main__" );
	object main_namespace = main_module.attr( "__dict__" );
	object ignored = exec(
	    "import sys\n"
	    "sys.path.append('.')\n", main_namespace );

	Map* map = GetMapInstance();
	std::cout << "Before python\n";
	map->Print();

	// load python to design the map
	object mapMaker = import("mapmaker");
	object makeMap = mapMaker.attr("make_map");
	makeMap();

	std::cout << "\nAfter python\n";
	map->Print();
    }
    catch (...)
    {
	PyErr_Print();
    }

    return 0;
}

Python写的地图生成程序:mapmaker.py。


import MyEngine
import random

def make_map():
    the_map = MyEngine.GetMapInstance()
    n = 10
    for i in range(n):
        for j in range(10 - i, 10 + i - 1):
            the_map.SetPixel(j, i, ord('*'))

我们可以仔细看看C++和Python代码中被高亮的行(代码是js高亮的,如果还没高亮请稍等页面加载完成)。

C++通过Boost库可以方便地和Python交互。当然我们还可以直接是用解释器提供的C API的和Python交互,不过这样会有非常多的累赘的代码。

C++和Python交互的关键之处是通过BOOST_PYTHON_MODULE来导出C++的函数和类,此外还需要执行initMyEngine这个函数来将这个模块注册进Python解释器。具体细节可以见代码。

对于

 def("GetMapInstance", GetMapInstance,
	return_value_policy< reference_existing_object >());

中的return_value_policy< reference_existing_object >()可以参考我以前写的:http://blog.csdn.net/cedricporter/article/details/6828322

编译:

g++ main.cpp -o map -I/usr/include/python2.7 -lboost_python -lpython2.7

在Windows上编译更容易,就不再罗嗦。

最后程序的输出,可以见到Python将地图由空白变成了一个三角形:

Example

对于用合适的语言来做合适的事情,会让开发效率和产品质量有所提高。例如,在Emacs里面,就可以是用lisp来控制emacs,AutoCAD中也可以是用脚本来绘图。这样的用户接口更加灵活。

Other

Scar

对于Scar这款3D太空:/scar.html,是用C++和Python混合编程。在Scar中的2D界面库,有C++写成的基本元件,例如容器和按钮。我们可以在Python中组装C++元件来装配游戏的2D界面,然后返回一棵树的根节点给C++。于是像Scar中的水平仪,提示面板,地图,血条等等都是在Python中组装好的。此外,我们还可以在Python中编写元件的事件处理函数。这样做的好处是,我们在修改界面的时候,不再需要重新编译程序,只要修改脚本就好。

这样的开发会更加便捷而且应对变更的能力会更强。

ImaginationFactory

Imagination Factory是在大一的时候写的一个图像处理程序,/imagination-factory.html

图像处理核心使用C++编写,界面使用C#/WPF编写。用WPF写界面即方便又便捷,可以轻松地实现很酷的效果。

Clover —— Computer Simulation Origami

Clover是一款计算机模拟折纸的程序,主要程序使用C#编写,内嵌Python解释器,可以用Python折纸。