登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: 用C语言扩展Python的功能     [回主站]     [分站链接]
标题
用C语言扩展Python的功能
clq
浏览(0) + 2010-05-05 13:18:08 发表 编辑

关键字:

用C语言扩展Python的功能

http://oss.org.cn/?action-viewnews-itemid-3932

clq
2010-5-5 13:18:57 发表 编辑

用C语言扩展Python的功能

Pyton和C分别有着各自的优缺点,用Python开发程序速度快,可靠性高,并且有许多现成模块可供使用,但执行速度相对较 慢;C语言则正好相反,其执行速度快,但开发效率低。为了充分利用两种语言各自的优点,比较好的做法是用Python开发整个软件框架,而用C语言实现其 关键模块。本文介绍如何利用C语言来扩展Python的功能,并辅以具体的实例讲述如何编写Python的扩展模块。

一、简介

Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始 受到越来越多人的青睐,并且被屡屡成功地应用于各类大型软件系统的开发过程中。

与其它普通脚本语言有所不同,Python程序员可以借助Python语言提供的API,使用C或者C++来对Python进行功能性扩展,从而即 可以利用Python方便灵活的语法和功能,又可以获得与C或者C++几乎相同的执行性能。执行速度慢是几乎所有脚本语言都具有的共性,也是倍受人们指责 的一个重要因素,Python则通过与C语言的有机结合巧妙地解决了这一问题,从而使脚本语言的应用范围得到了很大扩展。

在用Python开发实际软件系统时,很多时候都需要使用C/C++来对Python进行扩展。最常见的情况是目前已经存在一个用C编写的库,需要 在Python语言中使用该库的某些功能,此时就可以借助Python提供的扩展功能来实现。此外,由于Python从本质上讲还是一种脚本语言,某些功 能用Python实现可能很难满足实际软件系统对执行效率的要求,此时也可以借助Python提供的扩展功能,将这些关键代码段用C或者C++实现,从而 提供程序的执行性能。

本文主要介绍Python提供的C语言扩展接口,以及如何使用这些接口和C/C++语言来对Python进行功能性扩展,并辅以具体的实例讲述如何 实现Python的功能扩展。


二、Python的C语言接口

Python是用C语言实现的一种脚本语言,本身具有优良的开放性和可扩展性,并提供了方便灵活的应用程序接口(API),从而使得C/C++程序 员能够在各个级别上对Python解释器的功能进行扩展。在使用C/C++对Python进行功能扩展之前,必须首先掌握Python解释所提供的C语言 接口。

2.1 Python对象(PyObject)

Python是一门面向对象的脚本语言,所有的对象在Python解释器中都被表示成PyObject,PyObject结构包含Python对象 的所有成员指针,并且对Python对象的类型信息和引用计数进行维护。在进行Python的扩展编程时,一旦要在C或者C++中对Python对象进行 处理,就意味着要维护一个PyObject结构。

在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针。

2.2 引用计数

为了简化内存管理,Python通过引用计数机制实现了自动的垃圾回收功能,Python中的每个对象都有一个引用计数,用来计数该对象在不同场所 分别被引用了多少次。每当引用一次Python对象,相应的引用计数就增1,每当消毁一次Python对象,则相应的引用就减1,只有当引用计数为零时, 才真正从内存中删除Python对象。

下面的例子说明了Python解释器如何利用引用计数来对Pyhon对象进行管理:

例1:refcount.py
class refcount:
# etc.
r1 = refcount() # 引用计数为1
r2 = r1 # 引用计数为2
del(r1) # 引用计数为1
del(r2) # 引用计数为0,删除对象

在C/C++中处理Python对象时,对引用计数进行正确的维护是一个关键问题,处理不好将很容易产生内存泄漏。Python的C语言接口提供了 一些宏来对引用计数进行维护,最常见的是用Py_INCREF()来增加使Python对象的引用计数增1,用Py_DECREF()来使Python对 象的引用计数减1。

2.3 数据类型

Python定义了六种数据类型:整型、浮点型、字符串、元组、列表和字典,在使用C语言对Python进行功能扩展时,首先要了解如何在C和 Python的数据类型间进行转化。

2.3.1 整型、浮点型和字符串

在Python的C语言扩展中要用到整型、浮点型和字符串这三种数据类型时相对比较简单,只需要知道如何生成和维护它们就可以了。下面的例子给出了 如何在C语言中使用Python的这三种数据类型:

例2:typeifs.c
// build an integer
PyObject* pInt = Py_BuildValue("i", 2003);
assert(PyInt_Check(pInt));
int i = PyInt_AsLong(pInt);
Py_DECREF(pInt);
// build a float
PyObject* pFloat = Py_BuildValue("f", 3.14f);
assert(PyFloat_Check(pFloat));
float f = PyFloat_AsDouble(pFloat);
Py_DECREF(pFloat);
// build a string
PyObject* pString = Py_BuildValue("s", "Python");
assert(PyString_Check(pString);
int nLen = PyString_Size(pString);
char* s = PyString_AsString(pString);
Py_DECREF(pString);

2.3.2 元组

Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以 元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:

例3:typetuple.c
// create the tuple
PyObject* pTuple = PyTuple_New(3);
assert(PyTuple_Check(pTuple));
assert(PyTuple_Size(pTuple) == 3);
// set the item
PyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));
PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));
PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));
// parse tuple items
int i;
float f;
char *s;
if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
PyErr_SetString(PyExc_TypeError, "invalid parameter");
// cleanup
Py_DECREF(pTuple);

2.3.3 列表

Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何 在C语言中使用Python的列表类型:

例4:typelist.c
// create the list
PyObject* pList = PyList_New(3); // new reference
assert(PyList_Check(pList));
// set some initial values
for(int i = 0; i < 3; ++i)
PyList_SetItem(pList, i, Py_BuildValue("i", i));
// insert an item
PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));
// append an item
PyList_Append(pList, Py_BuildValue("s", "appended"));
// sort the list
PyList_Sort(pList);
// reverse the list
PyList_Reverse(pList);
// fetch and manipulate a list slice
PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
for(int j = 0; j < PyList_Size(pSlice); ++j) {
PyObject *pValue = PyList_GetItem(pList, j);
assert(pValue);
}
Py_DECREF(pSlice);
// cleanup
Py_DECREF(pList);

2.3.4 字典

Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:

例5:typedic.c
// create the dictionary
PyObject* pDict = PyDict_New(); // new reference
assert(PyDict_Check(pDict));
// add a few named values
PyDict_SetItemString(pDict, "first",
Py_BuildValue("i", 2003));
PyDict_SetItemString(pDict, "second",
Py_BuildValue("f", 3.14f));
// enumerate all named values
PyObject* pKeys = PyDict_Keys(); // new reference
for(int i = 0; i < PyList_Size(pKeys); ++i) {
PyObject *pKey = PyList_GetItem(pKeys, i);
PyObject *pValue = PyDict_GetItem(pDict, pKey);
assert(pValue);
}
Py_DECREF(pKeys);
// remove a named value
PyDict_DelItemString(pDict, "second");
// cleanup
Py_DECREF(pDict);


三、Python的C语言扩展

3.1 模块封装

在了解了Python的C语言接口后,就可以利用Python解释器提供的这些接口来编写Python的C语言扩展,假设有如下一个C语言函数:

例6:example.c
int fact(int n)
{
if (n <= 1)
return 1;
else
return n * fact(n - 1);
}

该函数的功能是计算某个给定自然数的阶乘,如果想在Python解释器中调用该函数,则应该首先将其实现为Python中的一个模块,这需要编写相 应的封装接口,如下所示:

例7: wrap.c
#include <Python.h>
PyObject* wrap_fact(PyObject* self, PyObject* args)
{
int n, result;

if (! PyArg_ParseTuple(args, "i:fact", &n))
return NULL;
result = fact(n);
return Py_BuildValue("i", result);
}
static PyMethodDef exampleMethods[] =
{
{"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
{NULL, NULL}
};
void initexample()
{
PyObject* m;
m = Py_InitModule("example", exampleMethods);
}

一个典型的Python扩展模块至少应该包含三个部分:导出函数、方法列表和初始化函数。

3.2 导出函数

要在Python解释器中使用C语言中的某个函数,首先要为其编写相应的导出函数,上述例子中的导出函数为wrap_fact。在Python的C 语言扩展中,所有的导出函数都具有相同的函数原型:

PyObject* method(PyObject* self, PyObject* args);

该函数是Python解释器和C函数进行交互的接口,带有两个参数:self和args。参数self只在C函数被实现为内联方法(built- in method)时才被用到,通常该参数的值为空(NULL)。参数args中包含了Python解释器要传递给C函数的所有参数,通常使用Python的 C语言扩展接口提供的函数PyArg_ParseTuple()来获得这些参数值。

所有的导出函数都返回一个PyObject指针,如果对应的C函数没有真正的返回值(即返回值类型为void),则应返回一个全局的None对象 (Py_None),并将其引用计数增1,如下所示:

PyObject* method(PyObject *self, PyObject *args) 
{
Py_INCREF(Py_None);
return Py_None;
}

3.3 方法列表

方法列表中给出了所有可以被Python解释器使用的方法,上述例子对应的方法列表为:

static PyMethodDef exampleMethods[] = 
{
{"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
{NULL, NULL}
};

方法列表中的每项由四个部分组成:方法名、导出函数、参数传递方式和方法描述。方法名是从Python解释器中调用该方法时所使用的名字。参数传递 方式则规定了Python向C函数传递参数的具体形式,可选的两种方式是METH_VARARGS和METH_KEYWORDS,其中 METH_VARARGS是参数传递的标准形式,它通过Python的元组在Python解释器和C函数之间传递参数,若采用METH_KEYWORD方 式,则Python解释器和C函数之间将通过Python的字典类型在两者之间进行参数传递。

3.4 初始化函数

所有的Python扩展模块都必须要有一个初始化函数,以便Python解释器能够对模块进行正确的初始化。Python解释器规定所有的初始化函 数的函数名都必须以init开头,并加上模块的名字。对于模块example来说,则相应的初始化函数为:

void initexample() 
{
PyObject* m;
m = Py_InitModule("example", exampleMethods);
}

当Python解释器需要导入该模块时,将根据该模块的名称查找相应的初始化函数,一旦找到则调用该函数进行相应的初始化工作,初始化函数则通过调 用Python的C语言扩展接口所提供的函数Py_InitModule(),来向Python解释器注册该模块中所有可以用到的方法。

3.5 编译链接

要在Python解释器中使用C语言编写的扩展模块,必须将其编译成动态链接库的形式。下面以RedHat Linux 8.0为例,介绍如何将C编写的Python扩展模块编译成动态链接库:

[xiaowp@gary code]$ gcc -fpic -c -I/usr/include/python2.2 \
-I /usr/lib/python2.2/config \
example.c wrapper.c
[xiaowp@gary code]$ gcc -shared -o example.so example.o wrapper.o

3.6 引入Python解释器

当生成Python扩展模块的动态链接库后,就可以在Python解释器中使用该扩展模块了,与Python自带的模块一样,扩展模块也是通过 import命令引入后再使用的,如下所示:

[xiaowp@gary code]$ python
Python 2.2.1 (#1, Aug 30 2002, 12:15:30)
[GCC 3.2 20020822 (Red Hat Linux Rawhide 3.2-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.fact(4)
24
>>>


四、结束语

作为一门功能强大的脚本语言,Python将被更加广泛地应用于各个领域。为了克服脚本语言执行速度慢的问题,Python提供了相应的C语言扩展 接口,通过将影响执行性能的关键代码用C语言实现,可以很大程度上提高用Python编写的脚本在运行时的速度,从而满足实际需要。


clq
2010-6-23 11:37:49 发表 编辑

http://blog.csdn.net/magictong/archive/2008/10/14/3075478.aspx
--------------------------------------------------

Python调用windows下DLL详解 - ctypes库的使用


在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分数据的交互。使用python中的ctypes模块可以很方便的调用windows的dll(也包括linux下的so等文件),下面将详细的讲 解这个模块(以windows平台为例子),当然我假设你们已经对windows下怎么写一 个DLL是没有问题的。

引入ctypes库

from ctypes import *

假设你有了一个符合cdecl(这里强调调用约定是因为,stdcall调用约定和cdecl调用约定声明的导出函数,在用 python加载使用的加载函数是不同的,后面会说明)调用约定的DLL(名字是add.dll),且有一个导出函数Add。

建立一个Python文件DllCall.py测试:

from ctypes import *

dll = CDLL("add.dll")

print dll.Add(1, 102)

结果:103

上面是一个简单的例子。

1、加载DLL

上面已经说过,加载的时候要根据你将要调用的函数是符合什么调用约定的。

stdcall调用约定:两种加载方式

Objdll = ctypes.windll.LoadLibrary("dllpath")

Objdll = ctypes.WinDLL("dllpath")

cdecl调用约定:也有两种加载方式

Objdll = ctypes.cdll.LoadLibrary("dllpath")

Objdll = ctypes.CDLL("dllpath")

其实windll和cdll分别是WinDLL类和CDll类的对象。

2、调用dll中的方法

在1中加载dll的时候会返回一个DLL对象(假设名字叫Objdll),利用该对象就可以调用dll 中的方法。

e.g.如果dll中有个方法名字叫Add(注意如果经过stdcall声明的方法,如果不是用def 文件声明的导出函数的话,编译器会对函数名进行修改,这个要注意)

调用:nRet = Objdll.Add(12, 15) 即完成一次调用。

看起来调用似乎很简单,不要只看表象,呵呵,这是因为Add这个函数太简单了,现在假设函数需要你传入 一个int类型的指针(int*),可以通过库中的byref关键字来实现,假设现在调用的函数的第三个参数是个int类型的指针。

intPara = c_int(9)

dll.sub(23, 102, byref(intPara))

print intPara.value

如果是要传入一个char缓冲区指针,和缓冲区长度,方法至少有四种:

# char* -- 1

szPara = create_string_buffer('\0'*100)

dll.PrintInfo(byref(szPara), 100);

print szPara.value

# char* -- 2

sBuf = 'aaaaaaaaaabbbbbbbbbbbbbb'

pStr = c_char_p( )

pStr.value = sBuf

#pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value

dll.PrintInfo(pStr, len(pStr.value))

print pStr.value

# char* -- 3

strMa = "\0"*20

FunPrint  = dll.PrintInfo

FunPrint.argtypes = [c_char_p, c_int]

#FunPrint.restypes = c_void_p

nRst = FunPrint(strMa, len(strMa))

print strMa,len(strMa)

# char* -- 4

pStr2 = c_char_p("\0")

print pStr2.value

#pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value

dll.PrintInfo(pStr2, len(pStr.value))

print pStr2.value

3、C基本类型和ctypes中实现的类型映射表

ctypes数据类型  C数据类型

c_char           char

c_short          short

c_int int

c_long long

c_ulong unsign long

c_float float

c_double double

c_void_p void

对应的指针类型是在后面加上"_p",如int* 是c_int_p等等。

在python中要实现c语言中的结构,需要用到类。

4、DLL中的函数返回一个指针。

虽然这不是个好的编程方法,不过这种情况的处理方法也很简单,其实返回的都是地址,把他们转换相应的 python类型,在通过value属性访问。

pchar = dll.getbuffer()

szbuffer = c_char_p(pchar)

print szbuffer.value

5、处理C中的结构体类 型

为什么把这个单独提出来说呢,因为这个是最麻烦也是最复杂的,在python里面申明一个类似c的结构 体,要用到类,并且这个类必须继承自Structure。

先看一个简单的例子:

C里面dll的定义如下:

typedef struct _SimpleStruct

{

    int    nNo;

    float  fVirus; 

    char   szBuffer[512];

} SimpleStruct, *PSimpleStruct;

typedef const SimpleStruct*  PCSimpleStruct;

extern "C"int  __declspec(dllexport) PrintStruct(PSimpleStruct simp);

int PrintStruct(PSimpleStruct simp)

    printf ("nMaxNum=%f, szContent=%s", simp->fVirus, simp->szBuffer);

return simp->nNo;

}

Python的定义:

from ctypes import *

class SimpStruct(Structure):

    _fields_ = [ ("nNo", c_int),

              ("fVirus", c_float),

              ("szBuffer", c_char * 512)]

dll = CDLL("AddDll.dll")

simple = SimpStruct();

simple.nNo = 16

simple.fVirus = 3.1415926

simple.szBuffer = "magicTong\0"

print dll.PrintStruct(byref(simple))

上面例子结构体很简单,如果结构体里面有指针,甚至是指向结构体的指针,python里面也有相应的处 理方法。

下面这个例子来自网上,本来想自己写个,懒得写了,能说明问题就行:

C代码如下:

typedef struct 

{

char words[10];

}keywords;

typedef struct 

{

keywords *kws;

unsigned int len;

}outStruct;

extern "C"int __declspec(dllexport) test(outStruct *o);

int test(outStruct *o)

{

unsigned int i = 4;

o->kws = (keywords *)malloc(sizeof(unsigned char) * 10 * i);

strcpy(o->kws[0].words, "The First Data");

strcpy(o->kws[1].words, "The Second Data");

o->len = i;

return 1;

}

Python代码如下:

class keywords(Structure):

        _fields_ = [('words', c_char *10),]

class outStruct(Structure):

        _fields_ = [('kws', POINTER(keywords)),

                    ('len', c_int),]

o = outStruct()

dll.test(byref(o))

print o.kws[0].words;

print o.kws[1].words;

print o.len

6、例子

说得天花乱坠,嘿嘿,还是看两个实际的例子。

例子一:

这是一个GUID生成器,其实很多第三方的python库已经有封装好的库可以调用,不过这得装了那个 库才行,如果想直接调用一些API,对于python来说,也要借助一个第三方库才行,这个例子比较简单,就是用C++调用win32 API来产生 GUID,然后python通过调用c++写的dll来获得这个GUID

C++代码如下:

 

  1. extern "C"__declspec(dllexportchar* newGUID();

  2. char* newGUID()

  3. {

  4.      static char buf[64] = {0};

  5.      statc GUID guid; 

  6.      if (S_OK == ::CoCreateGuid(&guid))

  7.      {

  8.  // "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"

  9.        _snprintf(buf, sizeof(buf), 

  10. "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",

  11. guid.Data1,

  12. guid.Data2,

  13. guid.Data3, 

  14. guid.Data4[0], guid.Data4[1], 

  15. guid.Data4[2], guid.Data4[3], 

  16. guid.Data4[4], guid.Data4[5], 

  17. guid.Data4[6], guid.Data4[7]

  18.              );

  19.    ::MessageBox(NULL, buf, "GUID", MB_OK);

  20.       }

  21.    return (char*)buf;

  22. }


Python代码如下:
  1. def CreateGUID():

  2.     """

  3.     创建一个全局唯一标识符

  4.     类似:E06093E2-699A-4BF2-A325-4F1EADB50E18

  5.     NewVersion

  6.     """

  7.     try:

  8.         # dll path

  9.         strDllPath = sys.path[0] + str(os.sep) + "createguid.dll"

  10.         dll = CDLL(strDllPath)

  11.         b = dll.newGUID()

  12.         a = c_char_p(b)                    

  13.     except Exception, error:

  14.         print error

  15.         return "" 

  16.     return a.value


例子二:

这个例子是调用kernel32.dll中的createprocessA函数来启动一个记事本进程。

  1. #  -*- coding:utf-8 -*-


  2. from ctypes import *


  3. # 定义_PROCESS_INFORMATION结构体

  4. class _PROCESS_INFORMATION(Structure):

  5.     _fields_ = [('hProcess', c_void_p),

  6.                 ('hThread', c_void_p),

  7.                 ('dwProcessId', c_ulong),

  8.                 ('dwThreadId', c_ulong)]


  9. # 定义_STARTUPINFO结构体

  10. class _STARTUPINFO(Structure):

  11.     _fields_ = [('cb',c_ulong),

  12.                 ('lpReserved', c_char_p),

  13.                 ('lpDesktop', c_char_p),

  14.                 ('lpTitle', c_char_p),

  15.                 ('dwX', c_ulong),

  16.                 ('dwY', c_ulong),

  17.                 ('dwXSize', c_ulong),

  18.                 ('dwYSize', c_ulong),

  19.                 ('dwXCountChars', c_ulong),

  20.                 ('dwYCountChars', c_ulong),

  21.                 ('dwFillAttribute', c_ulong),

  22.                 ('dwFlags', c_ulong),

  23.                 ('wShowWindow', c_ushort),

  24.                 ('cbReserved2', c_ushort),

  25.                 ('lpReserved2', c_char_p),

  26.                 ('hStdInput', c_ulong),

  27.                 ('hStdOutput', c_ulong),

  28.                 ('hStdError', c_ulong)]

  29.     

  30. NORMAL_PRIORITY_CLASS = 0x00000020     # 定 义NORMAL_PRIORITY_CLASS

  31. kernel32 = windll.LoadLibrary("kernel32.dll")  # 加载kernel32.dll

  32. CreateProcess = kernel32.CreateProcessA   # 获得CreateProcess函数地址

  33. ReadProcessMemory = kernel32.ReadProcessMemory # 获得ReadProcessMemory函数地址

  34. WriteProcessMemory = kernel32.WriteProcessMemory # 获得WriteProcessMemory函数地址

  35. TerminateProcess = kernel32.TerminateProcess


  36. # 声明结构体

  37. ProcessInfo = _PROCESS_INFORMATION()

  38. StartupInfo = _STARTUPINFO()

  39. fileName = 'c:/windows/notepad.exe'       # 要进行修改的文件

  40. address = 0x0040103c        # 要修改的内存地址

  41. strbuf = c_char_p("_")        # 缓冲区地址

  42. bytesRead = c_ulong(0)       # 读入的字节数

  43. bufferSize =  len(strbuf.value)     # 缓冲区大小


  44. # 创建进程

  45. CreateProcess(fileName, 0000, NORMAL_PRIORITY_CLASS,00, byref(StartupInfo), byref(ProcessInfo))

发表于 @ 2008年10月14日 19:19:00




总数:2 页次:1/1 首页 尾页  
总数:2 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1