前言
朋友的公司是做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
|
from
ctypes
import
*
;
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
|
//初始化
int
DCSPCLIENTDLL InitInterface(
const
char
*pCenterIP,
const
unsigned
short
nUpLinkSvrPort,
const
unsigned
short
nDownLinkSvrPort );
//释放资源
int
DCSPCLIENTDLL FiniInterface(
void
);
//登录
int
DCSPCLIENTDLL Login(
const
unsigned
int
uiBranchPlatformID,
const
unsigned
int
nUserID,
const
char
*pPassword );
//注销
int
DCSPCLIENTDLL Logout(
const
unsigned
int
uiBranchPlatformID,
const
unsigned
int
nUserID,
const
char
*pPassword );
//发车辆实时定位数据
int
DCSPCLIENTDLL SendUPRealLocation(
const
char
*
const
pDeviceId,
const
char
cDeviceColor,
const
unsigned
short
nMsgCode,
const
_stBPDynamicData *
const
pStGpsData );
|
在Python中加载使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from
ctypes
import
*
#加载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
{
// 加密状态
unsigned
char
encrypt;
// GPS 时间
_StructTime gpsTime;
// 经度
unsigned
int
longitude;
// 纬度
unsigned
int
latitude;
// GPS速度
unsigned
short
unGpsSpeed;
// 行驶记录仪速度
unsigned
short
unTachographSpeed;
// 车辆当前总里程数
unsigned
int
uiMileageTotal;
// 角度
unsigned
short
angle;
// 车辆状态
unsigned
short
state;
// 报警状态
unsigned
short
alarm;
};
|
在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
):
return
u
'({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 );
class
gpsData(Structure):
_fields_
=
[(
'strDeviceID'
,c_char_p),
(
'cDeviceColor'
,c_char),
(
'nMsgCode'
,c_ushort),
(
'stBPD'
,_stBPDynamicData)];
def
__str__(
self
):
return
u
'{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)
typedef
void
(*pDownTextInfoFv) (
const
char
*
const
pDeviceID,
const
char
cDeviceColor,
const
char
*
const
pInfo );
typedef
void
(*pDownCommunicateReqFv) (
const
char
*
const
pDeviceID,
const
char
cDeviceColor,
const
char
*
const
pCalledTel );
extern
"C"
{
void
DCSPCLIENTDLL RegDownTextInfoFunc( pDownTextInfoFv pFv );
void
DCSPCLIENTDLL 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 );
if
r==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版。
PS:借用 1号园友的主题和CSS。
Python SIP使用总结(Win&Linux通用)
From: http://topic.csdn.net/u/20120324/22/cd8796f8-c601-492c-992d-3d70d58ba51b.html?07033688857013248#replyachor
本文原先发表与我的博客。这是我做图像处理的时候摸索到的,现分享给大家。在用Python+OpenCV做影像分割、识别以及变换检测时,有时候会遇到处理速度瓶颈,于是想到用C++重写一些模块。在观察各个Python调用C++的方法中,在网上看了许多评价和介绍,最后选择了SIP作为工具。
本着分享的精神,现将使用的方法和在使用中遇到的问题总结出来,供大家参考。欢迎拍砖。本文也参加CSDN技术大分享活动,欢迎大家捧场。
如前所述,Python中使用C/C++模块有许多工具,大名鼎鼎的有SWIG(英文意思为:豪饮)、SIP(英文意思为:啜饮,小口的喝),还有boost.python等。其中SIP是从SWIG发展而来,专为Python调用C/C++模块使用的(看SIP的命名就能看出来,很有意思的)。
或许有人问为什么不用ctypes模块,原因是使用SIP无需编写特定格式的C/C++文件。需要调用的C/C++源代码文件,只是最简单的函数即可。若说SIP有什么需要学习的,那就是sip文件(下文中会介绍到)的编写。不过sip文件是基于C/C++的头文件的,学起来也比较简单。
SIP的使用方法,在官网的SIP Reference Guide中有介绍,不过那是针对至少有点经验的人员,对初学者来说,想十分钟快速上手还是有点难度的。这里就介绍下本人在使用SIP总结的需要注意的地方。
前提:本机上要装了Python、GCC(Windows下可以用MinGW)。并把python.exe和MinGW下的bin文件路径添加到环境变量中。
第一部分:安装SIP
Windows:
将Riverbank上的sip的zip格式的代码包下载,将其解压到C:\Python27中,那么现在sip的文件夹路径为C:\Python27\sip-4.13.2\sip-4.13.2(你也可以将其解压到任何一个文件夹中,但这里为了叙述方便,就解压到我电脑上的Python的文件夹中)。
打开Windows的cmd命令行,输如cd C:\Python27\sip-4.13.2\sip-4.13.2跳转到sip文件夹下。
输入python configure.py --platform win32-g++。进行配置
再完成上一步后,输入mingw32-make,接着输入mingw32-make install。若无意外,SIP就安装完成了。
常见问题:
若是你的电脑上之前安装了Eclipse等其他软件,而将mingw32-make改成了make,那么就要将上面的mingw32-make命令改为make来使用。
有个朋友说他按上面的方法使用make总是报错,仔细一问,才发现他的电脑上安装了Embacardero的RAD开发工具,也就是熟知的Delphi和C++ Builder的开发工具。在安装了这个工具后,在命令行中输入make,使用的总是Embacardero的C++ Builder的make。根据亲测,即使在配置的时候,使用python configure.py --platform win32-borland命令进行配置,对Emcarbadero的make也是无效的。这个配置命令产生的make文件,只是用于老版本的Borland C++ Builder的make命令。
若要使用微软的VC的nmake,就要在配置时使用命令python configure.py --platform win32-msvc,其中win32-msvc使用的是VC6的nmake;win32-msvc.net使用的是VC2003的nmake;win32-msvc2005使用的是VC2005的nmake;win32-msvc2008使用的是VC2008的nmake。根据--show-platforms命令显示的结果,SIP的配置暂不支持VC2010和VS11下的nmake。若想知道SIP支持的所有平台和编译器,可以在命令行中使用python configure.py --show -platforms命令来查看。
Linux:
Linux下的SIP安装与之类似,甚至更简单,Linux的发行版如Ubuntu,都自动安装了python和gcc,无需配置环境变量。
所以只要在官网上下载SIP的Linux下的tar.gz格式的代码包,解压到某个目录中。然后在终端中进入该目录,依次输入python configure.py --platform linux-g++;make;make install即可。
第二部分:在Python中使用C/C++写的函数
1、首先,编写个C文件,功能是将两个数字相加并输出,命名为add.c,这个将成为在Python中的模块名,如下:
- C/C++ code
-
/* File : add.c */ int add( int x, int y) { int g; g = x + y; return g; }
2、接着,手工编写SIP文件,在使用SIP的过程中,一个C/C++的源码文件,必须对应一个同名的sip文件,命名为add.sip,如下:
- C/C++ code
-
/* Define the SIP wrapper to the add library. */ % Module(name = add, language = " C " ) int add( int x, int y);
如果是源程序是用C++写的,那么这里的(name=add, language="C")就可以省去。
这里的C源码文件没有头文件,所以对应的sip文件很简单。如果C/C++的源码是实现部分,在实现部分还包括接口部分,即头文件。那么在相应的sip文件中需要用
- C/C++ code
-
% TypeHeaderCode #include < word.h > % End
来包含相应的头文件。sip文件与正式的C/C++头文件相似,但与之不同的是:sip文件不包含相应的头文件的私有成员变量(private或protected)。更详细的sip文件编写规范,请参考riverbank官方网站上的说明文档SIP Reference Guide。
3、编译C文件。按照官网的说法,是编写configure.py,但别急,先做一些必不可少的工作。在命令行将add.c编译成add.o文件:输入
gcc -c add.c
接着,将这一步生成的add.o文件生产库文件:
ar -r libadd.a add.o
这两步在这里是为一个单独的C模块测试的,如果是大量的C模块,可以用一个makefile一并批量完成,这也是初学者容易模糊的地方。记住,需要将libadd.a文件复制到Python文件夹下的libs文件夹中。也可以将源代码直接编译成dll,命令为gcc --shared add.c -o add.dll,记住要将生成的dll放到python\LIB\site-packages文件夹下。
4、手工编写configure.py文件,同样,这个configure文件的编写也不难,看下规范就会了(要会鸟语。。。)。这里,我们模仿官网的模版写一个自己的configure.py。
- Python code
-
import os import sipconfig # The name of the SIP build file generated by SIP and used by the build # system. build_file = " add.sbf " # Get the SIP configuration information. config = sipconfig.Configuration() # Run SIP to generate the code. os.system( " " .join([config.sip_bin, " -c " , " . " , " -b " , build_file, " add.sip " ])) # Create the Makefile. makefile = sipconfig.SIPModuleMakefile(config, build_file) # Add the library we are wrapping. The name doesn't include any platform # specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the # ".dll" extension on Windows). makefile.extra_libs = [ " add " ] # Generate the Makefile itself. makefile.generate()
5、运行configure.py,会生成一个makefile文件(直接用IDLE打开configure.py,按F5运行;或者命令行用python configure.py运行都可以)。
这里有个诡异的地方,有几个朋友在这一步会报错,说找不到add.sbf文件,而add.sbf文件应该是configure.py运行时调用相关函数自动产生的。若出现这个问题,请重新编译SIP。如果是Windows下,最好是在另一台机器上拷贝一个完整的包含能正常的SIP的Python文件夹,到有问题的机器上,将问题Python文件夹覆盖掉。
6、在命令行输入make(这里会生成一个关于函数的警告,不用管它,我们是用来测试的。。。其他的应该没什么问题,若有问题请检查前面的步骤),生成add.pyd文件。然后再输入make install(将add.pyd文件装入到Python的Lib文件夹下的sit-packages文件夹中)。
7、打开Python 的命令行,进行测试:
- Python code
-
>>> import add >>> add.add( 4 , 7 ) 11 >>>
(原谅我这么烂的模块名。。。)
提示:
(1)、这些文件可以放到Python的文件夹下新建的文件夹中(所有的操作在这个目录下的命令行窗口中使用)。注意,Python的父文件夹名不能有空格,否则会无法读取库文件。