注:本文全部示例介紹基於linux平臺html
在底層開發中,通常是使用C或者C++,可是有時候爲了開發效率或者在寫測試腳本的時候,會常用到python,因此這就涉及到一個問題,用C/C++寫的底層庫,怎麼樣直接被python來調用?python
python做爲一門膠水語言,固然有辦法來處理這個問題,python提供的方案就是ctypes庫。linux
ctypes是python的外部函數庫,它提供了C語言的兼容類型,並且能夠直接調用用C語言封裝的動態庫。
若是各位有較好的英語水平,能夠參考ctypes官方文檔,可是我會給出更詳細的示例,以便各位更好地理解。程序員
C代碼若是要可以被python調用,首先咱們先得把被調用C接口封裝成庫,通常是封裝成動態庫。編譯動態庫的指令是這樣的:編程
gcc --shared -fPIC -o target.c libtarget.so
在這裏,windows
--shared -fPIC 是編譯動態庫的選項。數組
-o 是指定生成動態庫的名稱框架
在linux下,通常的命名規則是:靜態庫爲lib.a,動態庫爲lib.so模塊化
target.c爲目標文件,在編譯時常有更復雜的調用關係和依賴,這裏就不詳說,有興趣的朋友能夠去了解了解gcc編譯規則。函數
既然庫已經封裝好了,那確定是就想把它用起來。咱們能夠在python中導入這個庫,以導入libtarget.so爲例:
import ctypes target = cdll.LoadLibrary("./libtarget.so")
順帶提一下,若是在windows環境下,動態庫文件是.dll文件,例如導入libtarget.dll:
import ctypes target = windll.LoadLibrary("./libtarget.dll")
在這裏,能夠將target當作是動態庫的示例,直接能夠以變量target來訪問動態庫中的內容。
LoadLibrary("./libtarget.so")表示導入同目錄下的libtarget.so文件。
細心的朋友已經發現了,在導入時,linux環境下使用的是cdll,而windows環境下使用的是windll。
這裏涉及到C語言的調用約定,gcc使用的調用約定是cdecl,windows動態庫通常使用stdcall調用約定,既然是調用約定,就確定是關於調用時的規則,他們之間的主要區別就是cdecl調用時由調用者清除被調用函數棧,而stdcall規定由被調用者清除被調用函數棧。
關於這個就不在這裏贅述了,有興趣的朋友能夠看看我另一篇博客:棧幀結構以及函數調用約定
學會了封裝動態庫,學會了導入庫,接下來咱們就要動手寫一個hello_world,畢竟學會了hello_world就算是入門了。
代碼以下:
target.c:
#include <stdio.h> void hello_world(void) { printf("hello downey!!\r\n"); }
編譯動態庫:
gcc -fPIC --shared target.c -o libtarget.so
test.py:
from ctypes import * test = cdll.LoadLibrary("./libtarget.so") test.hello_world()
執行python腳本:
python test.py
輸出結果:
hello downey!!
雖然這些代碼都是很是簡單,可是我仍是準備梳理一下流程:
是否是很是簡單,是的,python調用C程序就是這麼簡單,可是可別忘了,入門簡單可並不表明真正使用起來簡單!
咱們能夠想想,上面的hello_world()函數沒有參數和返回值,若是是一個帶參數或者帶返回值的C函數呢,python該怎麼調用?
python的內建類型中可沒有C語言那麼多花裏胡哨的類型,在python中怎麼去區分int,short,char這些類型呢?
針對上面的問題,python定義了一系列兼容C語言的類型
如圖所示,這個圖算是很清晰地將python與C類型對應關係展示了出來。咱們將要使用的就是最左邊一列的ctypes type,以替代C庫中的各類類型。
對於程序員而言,看圖片看文檔永遠沒有看代碼來得直接,因此在這裏先上一段演示代碼,看看在C庫中的類型是怎麼被替換的,可是凡事講究個按部就班,咱們先來一個簡單的,普通變量版的,代碼以下:
target.c:
#include <stdio.h> char hello_world(int num) { printf("hello %d!!\r\n",num); return (char)num+1; }
test.py:
1 from ctypes import * 2 test = cdll.LoadLibrary("./libtarget.so") 3 test.hello_world.restype = c_char 4 c = test.hello_world(48) 5 print(type(c)) 6 print(c)
輸出:
hello 48!! <type 'str'> 1
C語言代碼我就很少解釋,咱們主要來關注python部分:
咱們再來看輸出部分:
若是是須要轉換,那會遵循什麼規則呢?咱們只好從官方文檔中找答案,原文是這樣的:
Represents the C char datatype, and interprets the value as a single character. The constructor accepts an optional string initializer, the length of the string must be exactly one character.爲何輸出1而不是49,這個就很簡單了,十進制的49就是字符1,既然是被視爲字符,固然以字符顯示
其實在這裏,博主選取了一個比較特殊的例子,就是char在python中轉換的特殊性,各位朋友能夠思考下面兩個問題:
若是你看完了上面那個簡單版的函數參數轉換,咱們進入進階版的。在這個進階版的示例中,將引入數組,指針,結構體。不說了,直接上碼:
target.c:
#include <stdio.h> #include <string.h> typedef struct{ char *ptr; float f; char array[10]; }target_struct; target_struct* hello_world(target_struct* target) { // printf("hello %s.%d!!\r\n",name,num[0]); static char temp = 0x30; target->ptr = &temp; target->f = 3.1; memset(target->array,1,sizeof(target->array)); return target; }
test.py:
1 from ctypes import * 2 test = cdll.LoadLibrary("./libtarget.so") 3 class test_struct(Structure): 4 _fields_ = [('ptr',c_char_p), 5 ('c',c_float), 6 ('array',c_char*10)] 7 struct = test_struct(c = 0.5) 8 test.hello_world.restype =POINTER(test_struct) 9 ret_struct = test.hello_world(pointer(struct)) 10 print ret_struct.contents.ptr 11 print ret_struct.contents.c
輸出:
0 3.09999990463
對於target.c很少說,你們確定看得懂,咱們仍是主要來對照分析一下test.py的內容:
通過這兩個示例,我相信你們對ctypes的使用有了一個大概的認識,可是我建議你們看過以後本身多嘗試嘗試,這樣纔有更深的體會,這裏再作一個總結:
POINTER()和pointer(),這兩個方法,一個大寫一個小寫,你們在上面的例子中有看到,博主剛接觸的時候也是一臉懵逼,後來查了一下官方文檔,而後本身嘗試了一遍,終於理解了它們之間的區別,這裏貼上官方說明:
POINTER():This factory function creates and returns a new ctypes pointer type. Pointer types are cached and reused internally, so calling this function repeatedly is cheap. type must be a ctypes type.
pointer():This function creates a new pointer instance, pointing to obj. The returned object is of the type POINTER(type(obj)).
Note: If you just want to pass a pointer to an object to a foreign function call, you should use byref(obj) which is much faster.
對於數組,其實也挺簡單,你們能夠參考上面示例,我相信你們能看懂。
在參數類型中,還有一種很是特殊的存在——函數指針,在C語言中,咱們常常將函數指針做爲參數來實現回調函數,這種作法在各類標準化框架中常常見到,在模塊化編程中也是很是實用。
那麼問題來了,C庫中的函數實現了回調函數,python調用時該怎麼作?
按照咱們對C語言的理解,其實函數指針也是指針的一種,咱們能夠將一個指針強制轉換成函數指針類型而後執行,而後博主就在python中嘗試了一下,結果不論是我試圖將什麼類型的指針轉換成函數執行,結果都是這樣的:
TypeError: XXXX object is not callable
好吧,我仍是老老實實地使用官方提供的接口,仍是直接上碼:
target.c:
#include <stdio.h> typedef void (*callback)(int); void func(callback c1,callback c2,int p1,int p2) { c1(p1); c2(p2); }
test.py:
1 from ctypes import * 2 test = cdll.LoadLibrary("./libtarget.so") 3 def test_callback1(val): 4 print "I'm callback1" 5 print val 6 def test_callback2(val): 7 print "I'm callback1" 8 print val 9 CMPFUNC = CFUNCTYPE(None, c_int) 10 cbk1 = CMPFUNC(test_callback1) 11 cbk2 = CMPFUNC(test_callback2) 12 test.func(cbk1,cbk2,1,2)
輸出:
I'm callback1 1 I'm callback1 2
能夠看到,在target.c中func函數傳入了兩個函數指針參數,而後在函數中調用這兩個函數。
咱們再來仔細分析python中的調用:
好了,關於python ctypes調用C代碼的問題就到此爲止了,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
我的郵箱:linux_downey@sina.com
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身. (完)