Boost.Python C++与Python的互相调用之Python调用c/c++函数

http://hi.baidu.com/zhouhanqing/blog/item/1bf9f55086442c10377abe2e.html

这是件非常有趣并耐人寻味的过程,我们的MT(Mobile Test)课题采用了脚本自动化测试技术,当初选择了Python作为脚本语言,而Python并不能为我们完成所有的工作,我们要用C++扩展Python,用的是Boost库。之后,我们又要在我们的项目的C++代码中调用Python脚本。下面,我们首先看看Python调用C/C++的过程吧。

Python开发效率高,运行效率低。而c/c++恰恰相反。因此在python脚本中调用c/c++的库,对python进行扩展,是很有必要的。使用python api,http://www.python.org/doc/,需要安装python-dev。

不使用Boost的情况:
test.c文件如下

  1. #include //包含python的头文件
  2. // 1 c/cpp中的函数
  3. int my_c_function(const char *arg)
  4. {
  5.   int n = system(arg);
  6.   return n;
  7. }
  8. // 2 python 包装
  9. static PyObject * wrap_my_c_fun(PyObject *self, PyObject *args)
  10. {
  11.   const char * command;
  12.   int n;
  13.   if (!PyArg_ParseTuple(args, "s", &command))//这句是把python的变量args转换成c的变量command
  14.     return NULL;
  15.    n = my_c_function(command);//调用c的函数
  16.   return Py_BuildValue("i", n);//把c的返回值n转换成python的对象
  17. }
  18. // 3 方法列表
  19. static PyMethodDef MyCppMethods[] = {
  20. //MyCppFun1是python中注册的函数名,wrap_my_c_fun是函数指针
  21.    {"MyCppFun1", wrap_my_c_fun, METH_VARARGS, "Execute a shell command."},
  22.    {NULL, NULL, 0, NULL}
  23. };
  24. // 4 模块初始化方法
  25. PyMODINIT_FUNC initMyCppModule(void)
  26. {
  27. //初始模块,把MyCppMethods初始到MyCppModule中
  28.    PyObject *m = Py_InitModule("MyCppModule", MyCppMethods);
  29.   if (m == NULL) return;
  30. }

test.py文件如下

  1. import MyCppModule//导入python的模块(也就是c的模块,注意so文件名是MyCppModule
  2. r = MyCppModule.MyCppFun1("ls -l")//调用
  3. print r
  4. print "OK"

使用Boost的情况:

hello.cpp

char const* greet()
{
   return "hello, world";
}

#include <boost/python.hpp>

BOOST_PYTHON_MODULE(hello_ext)
{
    using namespace boost::python;
    def("greet", greet);
}

采用Boost build编译后

>>> import hello_ext
>>> print hello.greet()
hello, world

整个过程还是比较简单,但是环境配置还是比较麻烦的,请参考我以前的文章——Boost.Python在VS2005下的环境搭建有感而发

 

上次我写了利用Python提供的API封装c函数,并调用。但是由于利用API的方式过于原始,对于类或者结构极度麻烦。因此,我选择了BoostPython的来封装类,类似的工具还有SWIG等,选择Boost的原因是它不需要引入其他的接口描述语言,封装也是c++代码;另外,它支持的c++特性比较全。

    Boost Python的文档,我推荐:http://www.maycode.com/boostdoc/boost-doc/libs/python/doc/。基本上,你遇到的问题,都可以找到答案。

   下面贴一个例子,这个例子覆盖的面很全,需要好好理解。这个例子,是研究怎么封装C++。下一节,我会写一些高级的用法。

  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. using namespace std;
  7. using namespace boost::python;
  8. namespace HelloPython{
  9.   // 简单函数
  10.   char const* sayHello(){
  11.     return "Hello from boost::python";
  12.    }
  13.   // 简单类
  14.   class HelloClass{
  15.   public:
  16.      HelloClass(const string& name):name(name){
  17.      }
  18.   public:
  19.      string sayHello(){
  20.       return "Hello from HelloClass by : " + name;
  21.      }
  22.   private:
  23.      string name;
  24.    };
  25.   // 接受该类的简单函数
  26.    string sayHelloClass(HelloClass& hello){
  27.     return hello.sayHello() + " in function sayHelloClass";
  28.    }
  29.   //STL容器
  30.   typedef vector<int> ivector;
  31.   //有默认参数值的函数
  32.   void showPerson(string name,int age=30,string nationality="China"){
  33.      cout << name << " " << age << " " << nationality << endl;
  34.    }
  35.   // 封装带有默认参数值的函数
  36.    BOOST_PYTHON_FUNCTION_OVERLOADS(showPerson_overloads,showPerson,1,3) //1:最少参数个数,3最大参数个数
  37.   // 封装模块
  38.    BOOST_PYTHON_MODULE(HelloPython){
  39.     // 封装简单函数
  40.      def("sayHello",sayHello);
  41.     // 封装简单类,并定义__init__函数
  42.      class_("HelloClass",init())
  43.        .def("sayHello",&HelloClass::sayHello)//Add a regular member function
  44.        ;
  45.      def("sayHelloClass",sayHelloClass); // sayHelloClass can be made a member of module!!!
  46.     // STL的简单封装方法
  47.      class_("ivector")
  48.        .def(vector_indexing_suite());
  49.      class_ >("ivector_vector")
  50.        .def(vector_indexing_suite >());
  51.     // 带有默认参数值的封装方法
  52.      def("showPerson",showPerson,showPerson_overloads());
  53.    } 前两篇都是介绍Python调用C++的,换句话说,就是需要把C++封装成Python可以“理解”的类型。这篇,我打算说一下,C++怎么去调用Python脚本。其实这两者之间是相通的,就是需要可以互操作。按照惯例,先贴代码。
    second.cpp
    1. #include
    2. #include
    3. #include
    4. void printDict(PyObject* obj)
    5. {
    6.     if( !PyDict_Check(obj))
    7.         return;
    8.      PyObject *k,*keys;
    9.      keys = PyDict_Keys(obj);
    10.     for(int i = 0; i < PyList_GET_SIZE(keys); i++)
    11.      {
    12.          k = PyList_GET_ITEM(keys, i);
    13.         char* c_name = PyString_AsString(k);
    14.          printf("%s/n",c_name);
    15.      }
    16. }
    17. int main()
    18. {
    19.      Py_Initialize();
    20.     if(!Py_IsInitialized())
    21.        return -1;
    22.      PyRun_SimpleString("import sys");
    23.      PyRun_SimpleString("sys.path.append('./')");
    24.     //导入模块
    25.      PyObject* pModule=PyImport_ImportModule("second");
    26.     if(!pModule)
    27.      {
    28.            printf("Cant open python file!/n");
    29.         return -1;
    30.      }
    31.     //模块的字典列表
    32.      PyObject* pDict = PyModule_GetDict(pModule);
    33.     if(!pDict)
    34.      {
    35.          printf("Cant find dictionary./n");
    36.         return -1;
    37.      }
    38.     //打印出来看一下
    39.      printDict(pDict);
    40.     //获取Second类
    41.      PyObject* pClassSecond = PyDict_GetItemString(pDict,"Second");
    42.     if( !pClassSecond )
    43.      {
    44.          printf("Cant find second class./n");
    45.         return -1;
    46.      }
    47.     //构造Second的实例
    48.      PyObject* pInstanceSecond = PyInstance_New(pClassSecond,NULL,NULL);
    49.     if( !pInstanceSecond)
    50.      {
    51.          printf("Cant create second instance./n");
    52.         return -1;
    53.      }
    54.     //获取Person类
    55.      PyObject* pClassPerson = PyDict_GetItemString(pDict,"Person");
    56.     if( !pClassPerson )
    57.      {
    58.          printf("Cant find person class./n");
    59.         return -1;
    60.      }
    61.     //构造Person的实例
    62.      PyObject* pInstancePerson = PyInstance_New(pClassPerson,NULL,NULL);
    63.     if( !pInstancePerson )
    64.      {
    65.          printf("Cant find person instance./n");
    66.         return -1;
    67.      }
    68.      PyObject_CallMethod(pInstanceSecond,"invoke","O",pInstancePerson);
    69.      Py_DECREF(pModule); //都需要释放,例子就不写了
    70.      Py_Finalize();
    71.      getchar();
    72.     return 0;
    73. }
    second.py
    1. #!/usr/bin/python
    2. # Filename: second.py
    3. class Person:
    4.     def sayHi(self):
    5.         print 'hi'
    6. class Second:
    7.     def sayHello(self):
    8.         print 'hello'
    9.     def invoke(self,obj):
    10.          obj.sayHi()
    我简单解释一下
    • 这个例子演示了,创建python中Person类的实例,并作为参数调用Second的方法。
    • Py_Initialize()和Py_Finalize()是初始和销毁Python解释器
    • PyRun_SimpleString("import sys")导入sys,接着设置py文件的路径PyRun_SimpleString("sys.path.append('./')")
    • 导入模块PyImport_ImportModule("second"),就是second.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
    1. PyObject* pArgs=PyTuple_New(1); //有几个参数,就是几
    2. PyTuple_SetItem(pArgs,0,Py_BuildValue("i",3));   //初始第一个参数,数据类型是i,就是int,值是3
    返回值转换如,PyArg_ParseTuple,请参考
    http://www.python.org/doc/1.5.2p2/ext/parseTuple.html
    其实,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的脚本中调用其函数。
    1. #include
    2. #include
    3. using namespace boost::python;
    4. class World
    5. {
    6. public:
    7.       void set(std::string msg) { this->msg = msg; }
    8.        std::string greet() { return msg; }
    9.        std::string msg;
    10. };
    11. typedef boost::shared_ptr < World > world_ptr;
    12. BOOST_PYTHON_MODULE(hello)
    13. {
    14.        class_ ("World")
    15.            .def("greet", &World::greet)
    16.            .def("set", &World::set)
    17.        ;
    18.        register_ptr_to_python ();
    19. }
    20. int main(int argc, char *argv[])
    21. {
    22.     
    23.      Py_Initialize();
    24.      world_ptr worldObjectPtr (new World);
    25.      worldObjectPtr->set("Hello from C++!");
    26.     try
    27.      {        
    28.          inithello();
    29.          PyRun_SimpleString("import hello");
    30.         
    31.          object module(handle <>(borrowed(PyImport_AddModule("__main__"))));
    32.          object dictionary = module.attr("__dict__");
    33.          dictionary["pyWorldObjectPtr"] = worldObjectPtr;
    34.          PyRun_SimpleString("pyWorldObjectPtr.set('Hello from Python!')");
    35.      }
    36.     catch (error_already_set)
    37.      {
    38.          PyErr_Print();
    39.      }
    40.      std::cout << "worldObjectPtr->greet(): " << worldObjectPtr->greet() <
    41.      Py_Finalize();
    42.     return 0;
    43. }
    44. 把我在实际过程中遇到的问题,总结一下,请先阅读:python教程python FAQ

      1.如果封装的c++类没有拷贝构造函数怎么办?
      定义class的时候,加入模板参数boost::noncopyable,同时指定no_init
      1. class_("ExpandEmitter",no_init);
      拷贝构造的目的是,c++对象实例传递给python时,可以通过拷贝构造重新构造一个python中使用的对象实例。一般如果没有拷贝构造,需要boost的share_ptr来传递共享指针。

      2.封装的c++函数如何返回指针或者引用?
      return_value_policy(),返回值策略设置为return_by_reference即可
      1. .def("getPostData",&Request::getPostData,return_value_policy())
      3.自定义的String怎么和python进行自动转换?
      http://www.maycode.com/boostdoc/boost-doc/libs/python/doc/v2/faq.html#custom_string

      4.对象释放的问题
      假如在c++中创建的对象,在python中怎么释放?或者在python中创建的对象,在c++怎么释放?
      我的看法是,在一种语言中创建并释放。如果你想在python中创建对象,可以调用c++的函数,比如newClass()来创建,返回一个指针或者引用,使用完毕,调用c++的deleteClass()来释放。否则,python引用计数会特别麻烦,很容易导致内存泄漏等问题。

      5.共享指针,怎么手工释放?
      共享指针,默认是自动释放的,但是有时候,我们并不需要自动释放,想自己手工释放,可以定义一个释放函数,在创建共享指针的时候,传入释放函数指针。
      1. //定义
      2. typedef boost::shared_ptr < World > world_ptr;
      3. //定义一个释放函数
      4. void deleteWorld(World* w);
      5. //共享指针,传入释放函数指针
      6. world_ptr worldObjectPtr (new World,deleteWorld);
      6.c++封装模块,多个文件include,怎么会有多重定义的问题?
      1. BOOST_PYTHON_MODULE(hello)
      2. {
      3.        class_ ("World")
      4.            .def("greet", &World::greet)
      5.            .def("set", &World::set)
      6.        ;
      7.        register_ptr_to_python ();
      8. }
      如果上面这段在.h文件中,多个cpp文件引用是不行的。这个时候,可以定义一个头文件如下
      1. extern "C" void inithello();
      你在头文件中首先声明inithello(),在把上面的移到cpp中,BOOST_PYTHON_MODULE会实现一个函数inithello(),这样就可以了。

      7.如何封装c++容器?
      boost/python/suite/indexing目录下的头文件
      1. //include
      2. #include
      3. //定义模块时,可以定义map
      4.    //String map
      5.      boost::python::class_ >("StrMap")
      6.          .def(map_indexing_suite >())

      本人研究Python扩展并在实际项目中应用,写出这个系列希望对大家有帮助。

 

 

 

你可能感兴趣的:(Boost.Python C++与Python的互相调用之Python调用c/c++函数)