Python开发效率高,运行效率低。而c/c++恰恰相反。因此在python脚本中调用c/c++的库,对python进行扩展,是很有必要的。
使用python api,http://www.python.org/doc/,需要安装python-dev。
test.cpp文件如下:
#include <python2.6/Python.h> //包含python的头文件
// 1 c/cpp中的函数
int my_c_function(const char *arg) {
int n = system(arg);
return n;
}
// 2 python 包装
static PyObject * wrap_my_c_fun(PyObject *self, PyObject *args) {
const char * command;
int n;
if (!PyArg_ParseTuple(args, "s", &command))//这句是把python的变量args转换成c的变量command
return NULL;
n = my_c_function(command);//调用c的函数
return Py_BuildValue("i", n);//把c的返回值n转换成python的对象
}
// 3 方法列表
static PyMethodDef MyCppMethods[] = {
//MyCppFun1是python中注册的函数名,wrap_my_c_fun是函数指针
{ "MyCppFun1", wrap_my_c_fun, METH_VARARGS, "Execute a shell command." },
{ NULL, NULL, 0, NULL }
};
// 4 模块初始化方法
PyMODINIT_FUNC initMyCppModule(void) {
//初始模块,把MyCppMethods初始到MyCppModule中
PyObject *m = Py_InitModule("MyCppModule", MyCppMethods);
if (m == NULL)
return;
}
make:
g++ -shared -fpic test.cpp -o MyCppModule.so
编译完毕后,目录下会有一个MyCppModule.so文件
test.py文件如下:
# -*- coding: utf-8 -*-
import MyCppModule
#导入python的模块(也就是c的模块,注意so文件名是MyCppModule
r = MyCppModule.MyCppFun1("ls -l")
print r
print "OK"
执行:
lhb@localhost:~/maplib/clib/pyc/invokec$ python test.py
总计 20
-rwxr-xr-x 1 lhb lhb 45 2010-08-11 17:45 make
-rwxr-xr-x 1 lhb lhb 7361 2010-08-12 10:14 MyCppModule.so
-rw-r--r-- 1 lhb lhb 979 2010-08-11 17:45 test.cpp
-rw-r--r-- 1 lhb lhb 181 2010-08-11 17:45 test.py
0
OK
[ps]============================================================
上次我写了利用Python提供的API封装c函数,并调用。但是由于利用API的方式过于原始,对于类或者结构极度麻烦。
因此,我选择了Boost的Python的来封装类,类似的工具还有SWIG等,选择Boost的原因是它不需要引入其他的接口描述语言,封装也是c++代码;另外,它支持的c++特性比较全。
Boost Python的文档,我推荐:http://www.maycode.com/boostdoc/boost-doc/libs/python/doc/。基本上,你遇到的问题,都可以找到答案。
下面贴一个例子,这个例子覆盖的面很全,需要好好理解。这个例子,是研究怎么封装C++。下一节,我会写一些高级的用法。
01.#include <boost/python.hpp>
02.#include <boost/python/module.hpp>
03.#include <boost/python/def.hpp>
04.#include <boost/python/to_python_converter.hpp>
05.#include
06.using namespace std;
07.using namespace boost::python;
08.
09.namespace HelloPython{
10. // 简单函数
11. char const* sayHello(){
12. return "Hello from boost::python";
13. }
14.
15.
16. // 简单类
17. class HelloClass{
18. public:
19. HelloClass(const string& name):name(name){
20. }
21. public:
22. string sayHello(){
23. return "Hello from HelloClass by : " + name;
24. }
25. private:
26. string name;
27. };
28. // 接受该类的简单函数
29. string sayHelloClass(HelloClass& hello){
30. return hello.sayHello() + " in function sayHelloClass";
31. }
32.
33.
34. //STL容器
35. typedef vector<int> ivector;
36.
37.
38. //有默认参数值的函数
39. void showPerson(string name,int age=30,string nationality="China"){
40. cout << name << " " << age << " " << nationality << endl;
41. }
42.
43. // 封装带有默认参数值的函数
44. BOOST_PYTHON_FUNCTION_OVERLOADS(showPerson_overloads,showPerson,1,3) //1:最少参数个数,3最大参数个数
45.
46. // 封装模块
47. BOOST_PYTHON_MODULE(HelloPython){
48. // 封装简单函数
49. def("sayHello",sayHello);
50.
51. // 封装简单类,并定义__init__函数
52. class_("HelloClass",init())
53. .def("sayHello",&HelloClass::sayHello)//Add a regular member function
54. ;
55. def("sayHelloClass",sayHelloClass); // sayHelloClass can be made a member of module!!!
56.
57. // STL的简单封装方法
58. class_("ivector")
59. .def(vector_indexing_suite());
60. class_ >("ivector_vector")
61. .def(vector_indexing_suite >());
62.
63. // 带有默认参数值的封装方法
64. def("showPerson",showPerson,showPerson_overloads());
65. }
[ps]============================================================
前两篇都是介绍Python调用C++的,换句话说,就是需要把C++封装成Python可以“理解”的类型。
这篇,我打算说一下,C++怎么去调用Python脚本。其实这两者之间是相通的,就是需要可以互操作。
按照惯例,先贴代码。
test.cpp文件如下:
01./*
02. * test.cpp
03. * Created on: 2010-8-12
04. * Author: lihaibo
05. */
06.#include <python2.6/Python.h>
07.#include <iostream>
08.#include <string>
09.void printDict(PyObject* obj) {
10. if (!PyDict_Check(obj))
11. return;
12. PyObject *k, *keys;
13. keys = PyDict_Keys(obj);
14. for (int i = 0; i < PyList_GET_SIZE(keys); i++) {
15. k = PyList_GET_ITEM(keys, i);
16. char* c_name = PyString_AsString(k);
17. printf("%s/n", c_name);
18. }
19.}
20.int main() {
21. Py_Initialize();
22. if (!Py_IsInitialized())
23. return -1;
24. PyRun_SimpleString("import sys");
25. PyRun_SimpleString("sys.path.append('./')");
26. //导入模块
27. PyObject* pModule = PyImport_ImportModule("testpy");
28. if (!pModule) {
29. printf("Cant open python file!/n");
30. return -1;
31. }
32. //模块的字典列表
33. PyObject* pDict = PyModule_GetDict(pModule);
34. if (!pDict) {
35. printf("Cant find dictionary./n");
36. return -1;
37. }
38. //打印出来看一下
39. printDict(pDict);
40. //演示函数调用
41. PyObject* pFunHi = PyDict_GetItemString(pDict, "sayhi");
42. PyObject_CallFunction(pFunHi, "s", "lhb");
43. Py_DECREF(pFunHi);
44. //演示构造一个Python对象,并调用Class的方法
45. //获取Second类
46. PyObject* pClassSecond = PyDict_GetItemString(pDict, "Second");
47. if (!pClassSecond) {
48. printf("Cant find second class./n");
49. return -1;
50. }
51. //获取Person类
52. PyObject* pClassPerson = PyDict_GetItemString(pDict, "Person");
53. if (!pClassPerson) {
54. printf("Cant find person class./n");
55. return -1;
56. }
57. //构造Second的实例
58. PyObject* pInstanceSecond = PyInstance_New(pClassSecond, NULL, NULL);
59. if (!pInstanceSecond) {
60. printf("Cant create second instance./n");
61. return -1;
62. }
63. //构造Person的实例
64. PyObject* pInstancePerson = PyInstance_New(pClassPerson, NULL, NULL);
65. if (!pInstancePerson) {
66. printf("Cant find person instance./n");
67. return -1;
68. }
69. //把person实例传入second的invoke方法
70. PyObject_CallMethod(pInstanceSecond, "invoke", "O", pInstancePerson);
71. //释放
72. Py_DECREF(pInstanceSecond);
73. Py_DECREF(pInstancePerson);
74. Py_DECREF(pClassSecond);
75. Py_DECREF(pClassPerson);
76. Py_DECREF(pModule);
77. Py_Finalize();
78. return 0;
79.}
编译:
g++ test.cpp -o test -lpython2.6
testpy.py文件如下:
01.#!/usr/bin/python
02.# Filename: test.py
03.class Person:
04. def sayHi(self):
05. print 'hi'
06.class Second:
07. def invoke(self,obj):
08. obj.sayHi()
09.def sayhi(name):
10. print 'hi',name;
执行:
lhb@localhost:~/maplib/clib/pyc/invokepy$ ./test
sayhi
__builtins__
__file__
__package__
Person
Second
__name__
__doc__
hi lhb
hi
我简单解释一下
•这个例子演示了,创建python中Person类的实例,并作为参数调用Second的方法。
•Py_Initialize()和 Py_Finalize()是初始和销毁Python解释器
•PyRun_SimpleString( "import sys" )导入sys,接着设置py文件的路径PyRun_SimpleString( "sys.path.append('./')" )
•导入模块PyImport_ImportModule( "testpy" ),就是testpy.py模块。
•获取模块字典列表,PyModule_GetDict(pModule),可以打印出来看一下如 void printDict(PyObject* obj)函数
•从字典中获取类的类型 PyDict_GetItemString(pDict, "Second" ),如函数也是这样获取的
•创造类的实例 PyInstance_New(pClassSecond,NULL,NULL)
•调用实例的方法PyObject_CallMethod(pInstanceSecond, "invoke" , "O" ,pInstancePerson)
整个流程就是这样的,并不复杂,如果要进一步研究可以参考:http://www.python.org/doc/
•Extending and Embedding
•Python/C API
比较特殊的是调用Python函数时,参数的传递,就是c++的类型,怎么转换成Python的类型;另外一个问题是,Python函数的返回值,怎么转换成C++中的类型。
C++转换成Python类型,Py_BuildValue()
http://www.python.org/doc/1.5.2p2/ext/buildValue.html
01.PyObject* pArgs=PyTuple_New( 1 ); //有几个参数,就是几
02.PyTuple_SetItem(pArgs, 0 ,Py_BuildValue( "i" , 3 )); //初始第一个参数,数据类型是i,就是int,值是 3
返回值转换如,PyArg_ParseTuple,请参考
http://www.python.org/doc/1.5.2p2/ext/parseTuple.html
[ps]============================================================
其实,C++调用Python有两种方式,我前面介绍了第一种方式:通过找到Python模块,类,方法,构造参数来调用。第二中方式,就是通过构造出一个Python的脚本,用python引擎来执行。
第一种方式可能更为优雅,符合大多数的反射调用的特点。(我在以前的一个项目中,实现了c#的反射机制,c#调用Com+,c#调用javascript脚本等)。
还有一个问题,两种语言互相调用的时候,需要做数据结构(如基本类型,字符串,整数类型等,以及自定义的类等类型)间的转换,共享内存中的一个对象。
比如,如何将C++的对象实例传入python中,并在python中使用。c++和python并不在一个进程中,因此可以使用boost的shared_ptr来实现。
下面这个例子,主要是演示了,c++调用python,可以在c++中形成一个python脚本,然后利用PyRun_SimpleString调用;并且,构造一个c++的对象,传入到python中,并在python的脚本中调用其函数。
01.#include <boost/python.hpp>
02.#include <iostream>
03.using namespace boost::python;
04.
05.class World
06.{
07.public:
08. void set(std::string msg) { this->msg = msg; }
09. std::string greet() { return msg; }
10. std::string msg;
11.};
12.
13.typedef boost::shared_ptr < World > world_ptr;
14.
15.BOOST_PYTHON_MODULE(hello)
16.{
17. class_ <World>("World")
18. .def("greet", &World::greet)
19. .def("set", &World::set)
20. ;
21.
22. register_ptr_to_python <world_ptr>();
23.}
24.
25.int main(int argc, char *argv[])
26.{
27.
28. Py_Initialize();
29.
30. world_ptr worldObjectPtr (new World);
31. worldObjectPtr->set("Hello from C++!");
32.
33. try
34. {
35. inithello();
36. PyRun_SimpleString("import hello");
37.
38. object module(handle <>(borrowed(PyImport_AddModule("__main__"))));
39. object dictionary = module.attr("__dict__");
40.
41. dictionary["pyWorldObjectPtr"] = worldObjectPtr;
42. PyRun_SimpleString("pyWorldObjectPtr.set('Hello from Python!')");
43.
44. }
45. catch (error_already_set)
46. {
47. PyErr_Print();
48. }
49.
50. std::cout << "worldObjectPtr->greet(): " << worldObjectPtr->greet() <<std::endl;
51.
52. Py_Finalize();
53.
54. return 0;
55.}
[ps]============================================================
把我在实际过程中遇到的问题,总结一下:
1.如果封装的c++类没有拷贝构造函数怎么办?
定义class的时候,加入模板参数boost::noncopyable,同时指定no_init
01. class_<ExpandEmitter,boost::noncopyable>("ExpandEmitter",no_init);
拷贝构造的目的是,c++对象实例传递给python时,可以通过拷贝构造重新构造一个python中使用的对象实例。一般如果没有拷贝构造,需要boost的share_ptr来传递共享指针。
2.封装的c++函数如何返回指针或者引用?
return_value_policy<return_by_reference>(),返回值策略设置为return_by_reference即可
01. .def("getPostData",&Request::getPostData,return_value_policy<return_by_reference>())
3.自定义的String怎么和python进行自动转换?
01.struct template_string_to_python_str
02.{
03. static PyObject* convert(TemplateString const& s)
04. {
05. return boost::python::incref(boost::python::object(s.ptr()).ptr());
06. }
07.};
08.
09.struct template_string_from_python_str
10.{
11. template_string_from_python_str()
12. {
13. boost::python::converter::registry::push_back(
14. &convertible,
15. &construct,
16. boost::python::type_id<TemplateString>());
17. }
18.
19. static void* convertible(PyObject* obj_ptr)
20. {
21. if (!PyString_Check(obj_ptr))
22. {
23. return 0;
24. }
25. return obj_ptr;
26. }
27.
28. static void construct(
29. PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
30. {
31. const char* value = PyString_AsString(obj_ptr);
32. if (value == 0)
33. boost::python::throw_error_already_set();
34. void* storage = (
35. (boost::python::converter::rvalue_from_python_storage<TemplateString>*)
36. data)->storage.bytes;
37. new (storage) TemplateString(value);
38. data->convertible = storage;
39. }
40. };
41.
42.BOOST_PYTHON_MODULE(ctemplate)
43.{
44. boost::python::to_python_converter<
45. TemplateString,
46. template_string_to_python_str>();
47.
48. template_string_from_python_str();
49.}
4.对象释放的问题
假如在c++中创建的对象,在python中怎么释放?或者在python中创建的对象,在c++怎么释放?
我的看法是,在一种语言中创建并释放。如果你想在python中创建对象,可以调用c++的函数,比如newClass()来创建,返回一个指针或者引用,使用完毕,调用c++的deleteClass()来释放。否则,python引用计数会特别麻烦,很容易导致内存泄漏等问题。
5.共享指针,怎么手工释放?
共享指针,默认是自动释放的,但是有时候,我们并不需要自动释放,想自己手工释放,可以定义一个释放函数,在创建共享指针的时候,传入释放函数指针。
01.//定义
02.typedef boost::shared_ptr < World > world_ptr;
03.//定义一个释放函数
04.void deleteWorld(World* w);
05.//共享指针,传入释放函数指针
06.world_ptr worldObjectPtr (new World,deleteWorld);
6.c++封装模块,多个文件include,怎么会有多重定义的问题?
01.BOOST_PYTHON_MODULE(hello)
02.{
03. class_ <World>("World")
04. .def("greet", &World::greet)
05. .def("set", &World::set)
06. ;
07.
08. register_ptr_to_python <world_ptr>();
09.}
如果上面这段在.h文件中,多个cpp文件引用是不行的。这个时候,可以定义一个头文件如下
01.extern "C" void inithello();
你在头文件中首先声明inithello(),在把上面的移到cpp中,BOOST_PYTHON_MODULE会实现一个函数inithello(),这样就可以了。
7.如何封装c++容器?
boost/python/suite/indexing目录下的头文件
01.//include
02.#include <boost/python/suite/indexing/map_indexing_suite.hpp>
03.
04.//定义模块时,可以定义map
05. //String map
06. boost::python::class_<std::map<std::String,std::String> >("StrMap")
07. .def(map_indexing_suite<std::map<std::String,std::String> >())
本人研究过一段时间并在实际项目中应用,写出这个系列希望对大家有帮助。