C++ 扩展和嵌入 Python

C++ 扩展和嵌入 Python

本人很久之前发在vckbase的文章,居然没有显示作者。

Python简介

Python是一种简单易学,功能强大的解释型编程语言,它有简洁明了的语法,高效率的高层数据结构,能够简单而有效地实现面向对象编程,特别适用于快速应用程序开发,也可以用来开发大规模的重要的商业应用。Python是一个理想的脚本语言。

Python免费开源,可移植到多种操作系统,只要避免使用依赖于特定操作系统的特性,Python程序无需修改就可以在各种平台上面运行。

Python拥有现代编程语言所具有的一切强大功能,Python标准库十分庞大,可以帮助开发者处理各种工作,如:图形用户界面、文件处理、多媒体、正则表达式、文档生成、单元测试、线程、数据库、网络通讯、网页浏览器、CGI、FTP、电子邮件、XML、HTML、WAV文件、密码系统、Tk和其他与系统有关的操作。只要安装了Python,这些功能都是可用的除了标准库以外,还有许多其他高质量的库,如wxPython、Twisted和Python图形库等等数不胜数。

Python容易扩展和嵌入。Python提供的许多标准模块支持C或者C++接口。Python和C可以一起工作,它可以嵌入到C或者C++的应用程序当中,因此可用Python语言为应用程序提供脚本接口,由于支持跨语言开发,可用Python设计概念化应用程序,并逐步移植到C,使用前不必用C重写应用程序。(Jython使Python可以和Java一起工作,使开发者可以在Python里面调Java的包,也可以在Java里面使用Python的对象。还有更妙的,由于Jython的解释器完全用Java编写,因此可以在支持Java的任何平台上部署Python程序,甚至WEB浏览器也可以直接运行Python脚本。)

提出问题

在某个C++应用程序中,我们用一组插件来实现一些具有统一接口的功能,我们使用Python来代替动态链接库形式的插件,这样可以方便地根据需求的变化改写脚本代码,而不是必须重新编译链接二进制的动态链接库。Python强大的功能足以胜任,但是有一些操作系统特定的功能需要用C++来实现,再由Python调用。所以,最基础地,我们需要做到:

1. 把Python嵌入到C++应用程序中,在C++程序中调用Python函数和获得变量的值;

2. 用C++为Python编写扩展模块(动态链接库),在Python程序中调用C++开发的扩展功能函数。

常用的Python/C API介绍

下面是例子中用到的几个Python/C API的简要介绍及示例代码。注意,这并不是这些函数的详细介绍,而仅仅是我们所用到的功能简介,更详细内容请参考文档[1]、[2]、[3]、[4]。

打开Microsoft Visual Studio .NET 2003,新建一个控制台程序,#include ,并在main函数里加入示例代码。

//先定义一些变量

1
2
3
char*cstr;
PyObject *pstr, *pmod, *pdict;
PyObject *pfunc, *pargs;

1. void Py_Initialize( )

初始化Python解释器,在C++程序中使用其它Python/C API之前,必须调用此函数,如果调用失败,将产生一个致命的错误。例:

1
Py_Initialize();

2. int PyRun_SimpleString( const char *command)

执行一段Python代码,就好象是在__main__ 函数里面执行一样。例:

1
2
PyRun_SimpleString("from time import time,ctime\n"
"print ''Today is'',ctime(time())\n");

3. PyObject* PyImport_ImportModule( char *name)

导入一个Python模块,参数name可以是*.py文件的文件名。相当于Python内建函数__import__()。例:

1
pmod = PyImport_ImportModule("mymod");//mymod.py

4. PyObject* PyModule_GetDict( PyObject *module)

相当于Python模块对象的__dict__ 属性,得到模块名称空间下的字典对象。例:

1
pdict = PyModule_GetDict(pmod);

5. PyObject* PyRun_String( const char *str, int start, PyObject *globals, PyObject *locals)

执行一段Python代码。

1
pstr = PyRun_String("message", Py_eval_input, pdict, pdict);

6. int PyArg_Parse( PyObject *args, char *format, ...)

解构Python数据为C的类型,这样C程序中才可以使用Python里的数据。例:

1
2
3
/* convert to C and print it*/
PyArg_Parse(pstr,"s", &cstr);
printf("%s\n", cstr);

7. PyObject* PyObject_GetAttrString( PyObject *o, char *attr_name)

返回模块对象o中的attr_name 属性或函数,相当于Python中表达式语句:o.attr_name。例:

1
2
/* to call mymod.transform(mymod.message) */
pfunc = PyObject_GetAttrString(pmod, "transform");

8. PyObject* Py_BuildValue( char *format, ...)

构建一个参数列表,把C类型转换为Python对象,使Python可以使用C类型数据,例:

1
2
cstr="this is hjs''s test, to uppercase";
pargs = Py_BuildValue("(s)", cstr);

9. PyEval_CallObject(PyObject* pfunc, PyObject* pargs)

此函数有两个参数,都指向Python对象指针,pfunc是要调用的Python 函数,通常可用PyObject_GetAttrString()获得;pargs是函数的参数列表,通常可用Py_BuildValue()构建。例:

1
2
3
pstr = PyEval_CallObject(pfunc, pargs);
PyArg_Parse(pstr,"s", &cstr);
printf("%s\n", cstr);

10. void Py_Finalize( )

关闭Python解释器,释放解释器所占用的资源。例:

1
Py_Finalize();

Python2.4环境没有提供调试版本的Python24d.lib,所以上述示例在release模式下编译。编译完成后,把可行文件和附2给出的mymod.py文件放在一起,再点击即可运行。为了简化编程,附3 给出了simplepy.h。这样,调用mymod.transform变成如下形式:

1
2
3
4
5
6
//#include”simplepy.h”
CSimplepy py;
py.ImportModule("mymod");
std::string str=py.CallObject("transform",
"this is hjs''s test, to uppercase");
printf("%s\n", str.c_str());

接下来,我们来用C++为Python编写扩展模块(动态链接库),并在Python程序中调用C++开发的扩展功能函数。生成一个取名为pyUtil的Win32 DLL工程,除了pyUtil.cpp文件以外,从工程中移除所有其它文件,并填入如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// pyUtil.cpp
#ifdef PYUTIL_EXPORTS
#define PYUTIL_API __declspec(dllexport)
#else
#define PYUTIL_API __declspec(dllimport)
#endif
 
#include< windows.h >
#include< string >
#include< Python.h >
BOOLAPIENTRY DllMain( HANDLEhModule,
                       DWORD ul_reason_for_call,
                       LPVOIDlpReserved
                    ?)
{
    switch(ul_reason_for_call)
    {
    caseDLL_PROCESS_ATTACH:
    caseDLL_THREAD_ATTACH:
    caseDLL_THREAD_DETACH:
    caseDLL_PROCESS_DETACH:
        break;
    }
    returnTRUE;
}
std::string Recognise_Img(conststd::string url)
{
    //返回结果
    return"从dll中返回的数据... : " +url;
}
staticPyObject* Recognise(PyObject *self, PyObject *args)
{
    constchar*url;
    std::string sts;
    if(!PyArg_ParseTuple(args,"s", &url))
        returnNULL;
    sts = Recognise_Img(url);
    returnPy_BuildValue("s", sts.c_str() );
}
staticPyMethodDef AllMyMethods[] = {
    {"Recognise",  Recognise, METH_VARARGS},//暴露给Python的函数
    {NULL,      NULL}        /* Sentinel */
};
extern"C"PYUTIL_API voidinitpyUtil()
{
    PyObject *m, *d;
    m = Py_InitModule("pyUtil", AllMyMethods); //初始化本模块,并暴露函数
    d = PyModule_GetDict(m);
}

在Python代码中调用这个动态链接库:

1
2
3
import pyUtil
result = pyUtil.Recognise("input url of specific data")
print"the result is: "+ result    

用C++为Python写扩展时,如果您愿意使用Boost.Python库的话,开发过程会变得更开心J,要编写一个与上述pyUtil同样功能的动态链接库,只需把文件内容替换为下面的代码。当然,编译需要boost_python.lib支持,运行需要boost_python.dll支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include< string >
#include < boost/python.hpp >
usingnamespaceboost::python;
#pragma comment(lib, "boost_python.lib")
std::string strtmp;
charconst* Recognise(constchar* url)
{
    strtmp ="从dll中返回的数据... : ";
    strtmp+=url;
    returnstrtmp.c_str();
}
BOOST_PYTHON_MODULE(pyUtil)
{
    def("Recognise", Recognise);
}

所有示例都在Microsoft Windows XP Professional + Microsoft Visual Studio .NET 2003 + Python2.4环境下测试通过,本文所用的Boost库为1.33版本。

参考资料

[1] Python Documentation Release 2.4.1. 2005.3.30,如果您以默认方式安装了Python2.4,那么该文档的位置在C:\Program Files\Python24\Doc\Python24.chm;

[2] Michael Dawson. Python Programming for the Absolute Beginner. Premier Press. 2003;

[3] Mark Lutz. Programming Python, 2nd Edition. O''Reilly. 2001.3 ;

[4] Mark Hammond, Andy Robinson. Python Programming on Win32. O''Reilly. 2000.1 ;

Python主页:http://www.python.org;

Boost库主面:www.boost.org;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
附1 text.txt
thisis test text in text.txt.
附2 mymod.py
import string
message = ''original string''
message =message+message
msg_error=""
try:
       text_file = open("text.txt","r")
       whole_thing = text_file.read()
       print whole_thing
       text_file.close()
except IOError, (errno,strerror):
       print"I/O error(%s): %s" % (errno,strerror)
def transform(input):
    #input = string.replace(input, ''life'', ''Python'')
    returnstring.upper(input)
def change_msg(nul):  
    global message #如果没有此行,message是函数里头的局部变量
    message=''string changed''
    returnmessage
def r_file(nul):
    returnwhole_thing
def get_msg(nul):
returnmessage
 
附3 simplepy.h
#ifndef _SIMPLEPY_H_
#define _SIMPLEPY_H_
// simplepy.h v1.0
// Purpose: facilities for Embedded Python.
// by hujinshan @2005年9月2日9:13:02
#include
usingstd::string;
#include
//--------------------------------------------------------------------
// Purpose: ease the job to embed Python into C++ applications
// by hujinshan @2005年9月2日9:13:18
//--------------------------------------------------------------------
classCSimplepy// : private noncopyable
{
public:
    ///constructor
    CSimplepy()
    {
        Py_Initialize();
        pstr=NULL, pmod=NULL, pdict=NULL;
        pfunc=NULL, pargs=NULL;
    }
    ///destructor
    virtual~CSimplepy()   
    {  
        Py_Finalize();
    }
    ///import the user module
    boolImportModule(constchar* mod_name)
    {
        try{
            pmod  = PyImport_ImportModule(const_cast(mod_name));
            if(pmod==NULL)
                returnfalse;
            pdict = PyModule_GetDict(pmod);
        }
        catch(...)
        {
            returnfalse;
        }
        if(pmod!=NULL && pdict!=NULL)
            returntrue;
        else
            returnfalse;
    }
    ///Executes the Python source code from command in the __main__ module.
    ///If __main__ does not already exist, it is created.
    ///Returns 0 on success or -1 if an exception was raised.
    ///If there was an error, there is no way to get the exception information.
    intRun_SimpleString(constchar* str)
    {
        returnPyRun_SimpleString(const_cast(str) );
    }
    ///PyRun_String("message", Py_eval_input, pdict, pdict);
    ///Execute Python source code from str in the context specified by the dictionaries globals.
    ///The parameter start specifies the start token that should be used to parse the source code.
    ///Returns the result of executing the code as a Python object, or NULL if an exception was raised.
    string Run_String(constchar* str)
    {
        char*cstr;
        pstr  = PyRun_String(str, Py_eval_input, pdict, pdict);
        if(pstr==NULL)
            throw("when Run_String, there is an exception was raised by Python environment.");
        PyArg_Parse(pstr,"s", &cstr);
        returnstring(cstr);
    }
    ///support olny one parameter for python function, I think it''s just enough.
    string CallObject(constchar* func_name, constchar* parameter)
    {
        pfunc=NULL;
        pfunc = PyObject_GetAttrString(pmod, const_cast(func_name));
        if(pfunc==NULL)
            throw(string("do not found in Python module for: ")
+func_name).c_str();
        char* cstr;
        pargs = Py_BuildValue("(s)",const_cast(parameter));
        pstr  = PyEval_CallObject(pfunc, pargs);
        if(pstr==NULL)
            throw("when PyEval_CallObject, there is an exception was raised by Python environment");
        PyArg_Parse(pstr,"s", &cstr);     
        returnstring(cstr);
    }
    //PyObject *args;
    //args = Py_BuildValue("(si)", label, count);   /* make arg-list */
    //pres = PyEval_CallObject(Handler, args);     
protected:
    PyObject *pstr, *pmod, *pdict;
    PyObject *pfunc, *pargs;
};

#endif // _SIMPLEPY_H_

// end of file

展开阅读全文

没有更多推荐了,返回首页