python調用c庫之ctypes及callback相關問題解決

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

https://gitee.com/explict/codes/zq0nhvlfm6gyeo4ducpsi90

https://python3-cookbook.readthedocs.io/zh_CN/latest/c15/p12_turning_function_pointer_into_callable.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章