Python調用c庫學習
Python模塊ctypes是Python內建的用於調用動態鏈接庫函數的功能模塊,一定程度上可以用於Python與其他語言的混合編程。由於編寫動態鏈接庫,使用C/C++是最常見的方式,故ctypes最常用於Python與C/C++混合編程之中。
一、ctypes原理及優缺點
從ctypes的文檔中可以推斷,在各個平臺上均使用了對應平臺動態加載動態鏈接庫的方法,並通過一套類型映射的方式將Python與二進制動態鏈接庫相連接。在Windows平臺下,最終調用的是Windows API中LoadLibra
ry函數和GetProcAddress函數,在Linux和Mac OS X平臺下,最終調用的是Posix標準中的dlopen和dlsym函數。ctypes 實現了一系列的類型轉換方法,Python的數據類型會包裝或直接推算爲C類型,作爲函數的調用參數;函數的返回值也經過一系列的包裝成爲Python類型。也就是說,PyObject* <-> C types的轉換是由ctypes內部完成的。
ctypes 有以下優點:
· Python內建,不需要單獨安裝
· 可以直接調用二進制的動態鏈接庫
· 在Python一側,不需要了解Python內部的工作方式
· 在C/C++一側,也不需要了解Python內部的工作方式
· 對基本類型的相互映射有良好的支持
ctypes 有以下缺點:
· 平臺兼容性差
· 不能夠直接調用動態鏈接庫中未經導出的函數或變量(理解爲不能直接調用當前so中包含的其他so文件中的函數)
· 對C++的支持差
二、一個簡單的demo
1)、實現test_ctypes.c文件,內容如下:
#include <stdio.h>
int sum(int a,int b)
{
return a+b;
}
int main()
{
print("hello ctypes!sum:%d\n",sum(1,2));
return 0;
}
2)、編譯爲動態so庫:
gcc -fPIC -shared test_ctypes.c -o test_ctypes.so
3)、在python中調用sum函數或者main函數
so相互依賴問題的解決:
so相互依賴時使用RTLD_LAZY標誌加載so庫,先不解析未找到的symbol;後續在調用到這些symbol時會全局搜素,找得到就會調用。
import ctypes
#此處如果出現so之間的相互依賴關係,可以參考是用dlopen的RTLD_*相關的flag,如mode=1|RTLD_GLOBAL
test=ctypes.CDLL("test_ctypes.so")
test.main()
三、結構體的使用
如c結構體:
struct stu{
int a,
char b,
int c,
};
在python中使用需轉換爲類結構,然後再實例爲對象賦值操作:
class stu(Structure):
_fields_=[
("a",c_int),
("b",c_char),
("c",c_int)
]
mstu = stu()
mstu.a = 1
mstu.b = 2
mstu.c = 3
指針變量的傳遞:
sk = c_int()//定義int類型的變量;
將此變量以int *型作爲參數傳遞到函數(int test(int * a))中去:
mlib.test(ctypes.byref(sk))
sk.value 獲取sk的值
四、回調函數
ctypes 允許從Python callables創建C可調用函數指針。這些有時稱爲回調函數。
首先,您必須爲回調函數創建一個類。該類知道調用約定,返回類型以及此函數將接收的參數的數量和類型。
CFUNCTYPE() 工廠函數使用 cdecl
調用約定爲回調函數創建類型。在Windows上, WINFUNCTYPE() factory函數使用 stdcall
調用約定爲回調函數創建類型。
這兩個工廠函數都以結果類型作爲第一個參數調用,而回調函數將期望的參數類型作爲其餘參數
如需要回調的python函數爲:
def test_callback(a,b):
print a,b
return 0
如果要將此函數註冊到c函數中作爲回調函數使用,首先要創建一個類來說明此函數的參數類型及返回值類型。
它們描述一個函數(返回類型,參數類型,調用約定)而不定義實現.
ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
返回的函數原型創建使用標準C調用約定的函數.該功能將在調用期間釋放GIL.
如果use_errno設置爲True,系統errno變量的ctypes私有副本將與調用前後的實際errno值進行交換;
use_last_error對Windows錯誤代碼執行相同操作.
如上python函數創建的類如下:
functype = ctypes.CFUNCTYPE(c_int,c_int,c_int)
c_callback_python = functype(test_callback)
將此回調函數註冊到c庫函數中待調用:
mlibc.register_py_to_c(c_callback_python)
注意
只要從C代碼中使用它們,請確保保留對 CFUNCTYPE() 對象的引用。 ctypes 沒有,如果你不這樣做,它們可能被垃圾收集,在回調時崩潰你的程序。
另外,請注意,如果在Python控件之外創建的線程中調用回調函數(例如,通過調用回調的外部代碼),ctypes會在每次調用時創建一個新的虛擬Python線程。對於大多數用途,此行爲是正確的,但這意味着使用 threading.local 存儲的值將無法在不同的回調中生存,即使這些調用是從同一個C線程進行的。
五、結構體傳遞迴調函數
c中callback數據結構如下:
struct callback_all{
int (*callback1)(int),
int (*callback2)(int),
int (*callback3)(int)
};
python中創建轉換之後的類如下:
class py_callback_all(Structure):
__fields_ = [
("callback1",CFUNCTYPE(c_int,c_int)),
("callback2",CFUNCTYPE(c_int,c_int)),
("callback3",CFUNCTYPE(c_int,c_int))
]
針對此結構體的使用:
callback_all=py_callback_all()
callback_all.callback1 = c_callback_python
callback_all.callback2 = c_callback_python
callback_all.callback3 = c_callback_python
假設mlib庫下有函數int test_callback_struct(struct test_callback_all *callback);
mlibc.test_callback_struct.argtypes = [POINTER(py_callback_all)] #特別注意此處,直接傳遞callback_all也不會報錯,但是會導致callback1、callback2、callback3死活回調不對。
mlibc.test_callback_struct(callback_all)
參考鏈接:
https://www.docs4dev.com/docs/zh/python/3.7.2rc1/all/library-ctypes.html
https://cloud.tencent.com/developer/section/1370537
https://docs.python.org/2.6/library/ctypes.html