Python:使用ctypes庫調用外部DLL(轉)


前言

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

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

本想用C#給官方API做個包裝,省得再去動用C++,可是看到此API中定義有幾個Struct,而且下行數據都是通過回調函數方式提供,google了一下,似乎C#對調用有回調函數的C DLL不是很順暢,於是放棄了,想到了Python

一、Python之ctypes

ctypesPython的一個外部庫,提供和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的類型對應如下:

image

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

三、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版。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM