Python与C++相互调用

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> >())
 
本人研究过一段时间并在实际项目中应用,写出这个系列希望对大家有帮助。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值