python:使用ctypes調用外部DLL

前言

朋友的公司是作GPS的,上週聯繫到我要幫作個程序把他們平臺的車輛定位跟蹤數據和省裏的平臺對接。看一下官方提供的三個文檔,洋洋灑灑共一百多 頁,一大堆協議的定義甚是齊全,好在官方的文件中也帶有個封裝好通訊功能的DLL和一個調用此接口的c++ DEMO程序,既然有現成的可用,那就沒必要去看他的協議了。html

說實話,參加工做以後就基本沒用過c++,生疏了。特別是要用c++操做數據庫,對我來講比割幾刀還要痛苦。官方的API中已經很詳盡,要作的就是從現有平臺的數據庫中獲取車輛定位信息,經過官方的API發送到省中心平臺。python

本想用C#給官方API作個包裝,免得再去動用C++,但是看到此API中定義有幾個Struct,並且下行數據都是經過回調函數方式提供,google了一下,彷佛C#對調用有回調函數的C DLL不是很順暢,因而放棄了,想到了Pythonc++

1、Python之ctypes

ctypesPython的一個外部庫,提供和C語言兼容的數據類型,能夠很方便地調用C DLL中的函數。在Python2.5官方安裝包都帶有ctypes 1.1版。ctypes的官方文檔在這裏git

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

2、加載庫和普通函數的調用

官方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的類型對應以下:函數

image

如此,完成了簡單的第一步。ui

3、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()取參數的地址。

4、回調函數

寫到這裏就忍不住嘮叨幾句,這個系統的協議設計的太有 「個性」了。這個系統的功能提及來也不復雜,就是要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。

 

出處:無常筆記 http://wuchang.cnblogs.com

相關文章
相關標籤/搜索