ctypes模块扩展python
原文地址1:http://www.cnblogs.com/wuchang/archive/2010/04/04/1704456.html
原文地址2:http://www.cnblogs.com/babykick/archive/2011/04/30/2033636.html
原文地址3:http://www.ibm.com/developerworks/cn/linux/l-pythc/index.html
文章1
前言
朋友的公司是做GPS的,上周联系到我要帮做个程序把他们平台的车辆定位跟踪数据和省里的平台对接。看一下官方提供的三个文档,洋洋洒洒共一百多页,一大堆协议的定义甚是齐全,好在官方的文件中也带有个封装好通信功能的DLL和一个调用此接口的c++ DEMO程序,既然有现成的可用,那就不必去看他的协议了。
说实话,参加工作之后就基本没用过c++,生疏了。特别是要用c++操作数据库,对我来说比割几刀还要痛苦。官方的API中已经很详尽,要做的就是从现有平台的数据库中获取车辆定位信息,通过官方的API发送到省中心平台。
本想用C#给官方API做个包装,省得再去动用C++,可是看到此API中定义有几个Struct,而且下行数据都是通过回调函数方式提供,google了一下,似乎C#对调用有回调函数的C DLL不是很顺畅,于是放弃了,想到了Python。
一、Python之ctypes
ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。在Python2.5官方安装包都带有ctypes 1.1版。ctypes的官方文档在这里。
ctypes的使用非常简明,如调用cdecl方式的DLL只需这样:
|
1
2
3
|
fromctypesimport*;h=CDLL('msvcrt.dll')h.printf('a=%d,b=%d,a+b=%d',1,2,1+2); |
以上代码运行后输出 a=1,b=2,a+b=3。
二、加载库和普通函数的调用
官方API提供的库中有几个主要的函数:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//初始化intDCSPCLIENTDLL InitInterface(constchar*pCenterIP,constunsignedshortnUpLinkSvrPort,constunsignedshortnDownLinkSvrPort
);//释放资源intDCSPCLIENTDLL FiniInterface(void);//登录intDCSPCLIENTDLL Login(constunsignedintuiBranchPlatformID,constunsignedintnUserID,
constchar*pPassword );//注销intDCSPCLIENTDLL Logout(constunsignedintuiBranchPlatformID,constunsignedintnUserID,
constchar*pPassword );//发车辆实时定位数据intDCSPCLIENTDLL SendUPRealLocation(constchar*constpDeviceId,
constcharcDeviceColor,constunsignedshortnMsgCode,const_stBPDynamicData
*constpStGpsData ); |
在Python中加载使用:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
fromctypesimport*#加载API库api =CDLL('DCSPClientDLL.dll');#初始化函数的参数类型api.InitInterface.argtypes=[c_char_p,c_ushort,c_ushort]api.Login.argtypes=[c_uint,c_uint,c_char_p]api.Logout.argtypes=[c_uint,c_uint,c_char_p]#初始化并登录api.InitInterface(u"中心服务器地址", u'上行服务端端口', u'下行客户端端口')api.Login(platformID,userID,password);#.....其它操作api.Logout(platformID,userID,password);#注销 |
参数类型可以像上面的代码一样预先设定好,或者在调用函数时再把参数转成相应的c_***类型。ctypes的类型对应如下:
如此,完成了简单的第一步。
三、C语言中的Struct数据结构
在发送实时定位数据的函数SendUPRealLocation中有一个参数是结构体类型 _stBPDynamicData。python中没有struct这种数据结构,ctypes很周全,对C的struct和union这二种数据类型都提供很好的支持。stBPDynamicData结构的定义如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 车辆动态数据结构体struct_stBPDynamicData{// 加密状态unsignedcharencrypt;// GPS 时间_StructTime gpsTime;// 经度unsignedintlongitude;// 纬度unsignedintlatitude;// GPS速度unsignedshortunGpsSpeed;// 行驶记录仪速度unsignedshortunTachographSpeed;// 车辆当前总里程数unsignedintuiMileageTotal;// 角度unsignedshortangle;// 车辆状态unsignedshortstate;// 报警状态unsignedshortalarm;}; |
在python中,需要定义一个与这兼容的类,继承于ctypes.Structure,其中还用到一个_StructTime结构,这里一并贴出代码:
|
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
|
class_StructTime(Structure):_fields_=[('day',c_ubyte),('month',c_ubyte),('year',c_ushort),('hour',c_ubyte),('minute',c_ubyte),('second',c_ubyte)]; def__str__(self):return'{0}-{1}-{2} {3}:{4}:{5}'.format(self.year,self.month,self.day,self.hour,self.minute,self.second);class_stBPDynamicData(Structure):_fields_=[('encrypt',c_ubyte),('gpsTime',_StructTime), ('longitude',c_uint),('latitude',c_uint),('unGpsSpeed',c_ushort),('unTachographSpeed',c_ushort),('uiMileageTotal',c_uint),('angle',c_ushort),('state',c_ushort),('alarm',c_ushort)];def__str__(self):returnu'({0},{1}),{6},sp:{2},angle:{3},st:{4},al:{5}'.format(self.longitude,self.latitude,self.unGpsSpeed,self.angle ,self.state,self.alarm,self.gpsTime
);classgpsData(Structure):_fields_=[('strDeviceID',c_char_p),('cDeviceColor',c_char), ('nMsgCode',c_ushort),('stBPD',_stBPDynamicData)];def__str__(self):returnu'{0},{1}'.format(self.strDeviceID,self.stBPD
); |
gpsData是我自己加的一个类,用于记录每辆车的信息。
现在就可以使用SendUPRealLocation函数发送车辆实时数据了:
|
1
2
3
4
5
6
7
8
9
|
tm=_StructTime();tm.year=2010;tm.month=4;tm.day=3;tm.hour=11;tm.minute=2;tm.second=11;bpd=_stBPDynamicData();bpd.gpsTime=tm;bpd.longitude=1234567;bpd.latitude=246898;#...其它参数data=gpsData();data.strDeviceID=u'桂Coo007';data.stBPD=bpd;#调用 API发送数据api.SendUPRealLocation( data.strDeviceID, data.cDeviceColor ,data.nMsgCode, addressof( data.stBPD ) ); |
注意SendUPRealLocation第三个参数是_stBPDynamicData * 指针类型,所以要用ctypes.addressof()取参数的地址。
四、回调函数
写到这里就忍不住唠叨几句,这个系统的协议设计的太有 “个性”了。这个系统的功能说起来也不复杂,就是要GPS运营商把指定的车辆位置信息发送到中心平台,同时中心平台可以向各GPS终端发送一些数据和指令,比如传送文字信息到终端,或者要求终端拍张照片反馈到中心。
这个协议流程是这样,运营商端主动连接到中心服务器,然后此连接只用于传输向中心平台主动发送的数据。登录成功了之后呢,中心平台再向运营商的IP建立一个连接,用于中心下发的数据和指令。官方称为“双链路”。
于是,就要求运营商必须要有固定的公网IP(这个不是问题,据了解GPS运营商服务器都有固定IP),而且这个程序必须运行在有公网IP的电脑上或采用端口映射之类的方法。可是俺开发设计时是在大教育局域网中的,搞个端口映射都不可能更别谈公网IP了。于是,在调试下行数据部分功能时就只能远程到运营商服务器上去调试了。
回归正题。
要使用回调函数,需要先用 CFUNCTYPE 定义回调函数的类型,官方API中有十多个回调函数注册,定义摘抄:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#define DCSPCLIENTDLL __declspec(dllexport)typedefvoid(*pDownTextInfoFv) (constchar*constpDeviceID,constcharcDeviceColor,constchar*constpInfo
);typedefvoid(*pDownCommunicateReqFv) (constchar*constpDeviceID,constcharcDeviceColor, constchar*constpCalledTel
);extern"C"{voidDCSPCLIENTDLL RegDownTextInfoFunc( pDownTextInfoFv pFv );voidDCSPCLIENTDLL RegDownCommunicateReqFunc( pDownCommunicateReqFv pFv );}; |
在python中,定义相应的类型和回调处理函数:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
"""下发文字信息"""def downTextInfo(pDeviceID,cDeviceColor,pInfo):print(u'<-[下发文字]:{0},{1}'.format(str(pDeviceID),str(pInfo)) );r=api.SendUpTextInfoAck(pDeviceID, cDeviceColor, True );ifr==0:print(u'->回复下发文字成功。');else:print(u'->回复下发文字失败。');pDownTextInfoFv = CFUNCTYPE(c_void_p,c_char_p, c_char, c_char_p) #回调函数类型定义pDownTextInfoHandle = pDownTextInfoFv(downTextInfo);api.RegDownTextInfoFunc(pDownTextInfoHandle); #注册回调函数 |
其中SendUpCommunicateAck是回应中心,告知已经收到信息。二个参数类型和downTextInfo中的参数类型一到,所以可以不用初始化声明此函数的参数定义。
其余的回调函数用相同的方法处理。
结尾
调试完API对接部分功能后,在想用哪个py库操作数据库比较方便呢,找了一下之后才想到为何不用ironPython而可以直接使用ado.net访问数据库,岂不是更爽。
于是把代码搬到ironPython2.6中试试,让我十分惊喜的是不用做任何个性代码直接运行成功!ironPython 2.6中的ctypes和Python2.6的一样都是1.1.0版。
文章2
Python调用Dll
文章3
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语言实现的一种脚本语言,本身具有优良的开放性和可扩展性,并提供了方便灵活的应用程序接口(API),从而使得C/C++程序员能够在各个级别上对Python解释器的功能进行扩展。在使用C/C++对Python进行功能扩展之前,必须首先掌握Python解释所提供的C语言接口。
Python是一门面向对象的脚本语言,所有的对象在Python解释器中都被表示成PyObject,PyObject结构包含Python对象的所有成员指针,并且对Python对象的类型信息和引用计数进行维护。在进行Python的扩展编程时,一旦要在C或者C++中对Python对象进行处理,就意味着要维护一个PyObject结构。
在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针。
为了简化内存管理,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。
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语言接口后,就可以利用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扩展模块至少应该包含三个部分:导出函数、方法列表和初始化函数。
要在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;
}
|
方法列表中给出了所有可以被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的字典类型在两者之间进行参数传递。
所有的Python扩展模块都必须要有一个初始化函数,以便Python解释器能够对模块进行正确的初始化。Python解释器规定所有的初始化函数的函数名都必须以init开头,并加上模块的名字。对于模块example来说,则相应的初始化函数为:
void initexample()
{
PyObject* m;
m = Py_InitModule("example", exampleMethods);
}
|
当Python解释器需要导入该模块时,将根据该模块的名称查找相应的初始化函数,一旦找到则调用该函数进行相应的初始化工作,初始化函数则通过调用Python的C语言扩展接口所提供的函数Py_InitModule(),来向Python解释器注册该模块中所有可以用到的方法。
要在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
|
当生成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编写的脚本在运行时的速度,从而满足实际需要。
-
可以从Python( http://www.python.org)网站着手了解所有关于Python的内容。
-
可以在Python网站上找到正式的Python C/API文档(http://www.python.org/doc/current/api/api.html)。
-
可以在Python网站上找到正式的编写Python扩展模块的文档(http://www.python.org/doc/current/api/api.html)。

浙公网安备 33010602011771号