首頁  >  文章  >  後端開發  >  python與C互相調用的詳細介紹

python與C互相調用的詳細介紹

零下一度
零下一度原創
2017-07-16 12:03:481721瀏覽

 雖然python開發效率很高,但作為腳本語言,其性能不高,所以為了兼顧開發效率和性能,通常把性能要求高的模組用c或c++來實現或者在c或c++中運行python腳本來處理邏輯,前者通常是python中一些模組的實現方式,後者服務端程式(實現業務擴展或是Plugin功能)和遊戲開發(腳本只處理邏輯)中比較常見。本文主要介紹透過在c中運行python腳本來實現python與c的相互調用,並透過c和python腳本設定同一段記憶體區域為例子來講解。

前言

最近因為工作的需要,在考慮基於udp做一個用於網遊戰鬥中的資料同步協議,為了前期測試數據,決定先做一個外部的代理tunnel,原理是在server端和client端分別建立網絡轉發proxy,即原來的C/S連接改為兩個proxy之間數據快速傳輸。因為udp函式庫是用C++寫的程式碼,在測試資料的時候需要不斷地修改​​參數,重新編譯,修改輸出統計資料製表等,不勝其煩,最後決定導出介面由python腳本來進行邏輯呼叫。下面話不多說,來一起看看詳細的介紹:

準備工作

#  為了在c中運行python腳本,需要在程式連結的時候將python虛擬機庫連結進去,python虛擬機庫是python安裝目錄下libs中的python27.lib文件,至於怎樣將庫鏈接進程序中可以自己google下。由於在c中使用了python的一些方法和資料結構,所以需要將python安裝目錄下的include目錄加入到專案include目錄中。好了,需要準備的就是這些,然後就可以開始實作一個設定記憶體區域的例子了。

C/C++匯出到python有多種方法,根據不同的需求,可以使用下面不同的方式:

1、ctypes綁定。 ctypes就包含在萬能的python標準庫模組裡面,它可以運行時載入動態連結庫(dll,so),在CPython 2.x/3.x和PyPy上都支援。這種方式好處就是不用針對性地用python api寫導出函數,可以直接載入動態連結函式庫的符號表,在python中就可以直接呼叫了。

2、第三方的python binding。例子有boost-python,實作方式是工具自動化用Python/C api產生一系列C++ wrapper函數。特別適用於大型的函式庫或引擎匯出到python。

3、手動寫python binding函數。如果對Python C api熟悉的話,這種方式應該是最靈活的,讀一遍API文件就可以使用。理論上效率應該是最好的,但對於我這種python初學者,可能需要花上不少時間。

以之前折騰C函數匯出到Lua腳本的經歷,本來以為要先研究一番python c api,再搞上半天才能搞定。後面發現python標準函式庫模組的ctypes已經非常強大,雖然效能應該是三種方式裡面最差的,但在這個最高60fps的tunnel裡面,C/Python介面邊界呼叫的損耗先忽略。跟其他兩種方式設計不一樣的是,ctypes採用的是非入侵式呼叫介面的方式,不需要修改原來的C介面或是寫一些綁定程式碼,直接對編譯出來的動態函式庫進行呼叫。 ctypes使用過程也是非常愉悅的。

下面介紹下ctypes的使用:

1、載入DLL動態連結函式庫

這裡要注意區分動態連結函式庫函數是使用cdecl還是stdcall的呼叫約定,分別使用cdll或windll載入動態函式庫。

例如:

# 加载udp库函数 
udp_server = cdll.LoadLibrary("./udp_server.so") 
init_udp_server = udp_server.init_udp_server 
destroy_udp_server = udp_server.destroy_udp_server 
update_udp_server = udp_server.update_udp_server 
SendMsg = udp_server.SendMsg 

SetConnectCallback = udp_server.SetConnectCallback 
SetDisconnectCallback = udp_server.SetDisconnectCallback 
SetTimeoutCallback = udp_server.SetTimeoutCallback 
SetRecvCallback = udp_server.SetRecvCallback

2、資料類型映射

除了ctypes定義的基本數據類型(c_char, c_int, c_double等),也能使用pointer函數轉換成指標型別。對於要導出的網路庫,設定回呼函數是必不可少的,在C++庫裡面,回呼函數是透過設定一個函數指標完成的,ctypes同樣支援函數指標的聲明。如:recv_cb = CFUNCTYPE( None, c_char_p, c_int ) ,表示一個回傳值為void,參數為char*和int型別的回呼函數。


def init(self, port, ip="127.0.0.1"): 
  self._port = port 
  self._ip = ip 

  self._clients = {} 

  self.c_connect_cb = connect_cb(self.server_connect) 
  self.c_disconnect_cb = disconnect_cb(self.server_disconnect) 
  self.c_timeout_cb = timeout_cb(self.server_timeout) 
  self.c_recv_cb = recv_cb(self.server_recv) 

def create(self): 
  if self._port: 
   if init_udp_server(self._ip, self._port) == 0: 
    print "server listen %s:%d" % (self._ip, self._port) 
    SetConnectCallback( self.c_connect_cb ) 
    SetDisconnectCallback( self.c_disconnect_cb ) 
    SetTimeoutCallback( self.c_timeout_cb ) 
    SetRecvCallback( self.c_recv_cb ) 
    return True 
  print "[error] init_udp_server error", self._ip, self._port 
  return False

綁定回呼參數需要注意的是,綁定的回呼函數需要保存為成員變數(上面的寫法),目的是避免python垃圾回收導致回呼函數變成野指針。這算是一個小小的坑吧。基本上一個小小的函式庫也就用到這些功能。

以上是python與C互相調用的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn