python利用ctypes調(diào)用C/C++的動(dòng)態(tài)庫(kù)

ctypes 是 Python 的外部函數(shù)庫(kù)。它提供了與 C 兼容的數(shù)據(jù)類型天吓,并允許調(diào)用 DLL 或共享庫(kù)中的函數(shù)摊阀〕诜梗可使用該模塊以純 Python 形式對(duì)這些庫(kù)進(jìn)行封裝芝此。

一回懦、加載

加載庫(kù)的方式

from ctypes import *     #用到類CDLL
mydll =  CDLL("/xx/xx/libxxxx.dll")   # windows平臺(tái)
或者mydll =  cdll.LoadLibrary("/xx/xx/libxxxx.dll")
mydll =  CDLL("/xx/xx/libxxxx.so")   # linux平臺(tái)
#調(diào)用其函數(shù)func
result = mydll.func(arg1)

根據(jù)當(dāng)前平臺(tái)分別加載Windows和Linux上的C的標(biāo)準(zhǔn)動(dòng)態(tài)庫(kù)msvcrt.dll和libc.so.6闽撤。

注意這里我們使用的ctypes.cdll來(lái)load動(dòng)態(tài)庫(kù)端辱,實(shí)際上ctypes中總共有以下四種方式加載動(dòng)態(tài)庫(kù):

  1. class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

    此類的實(shí)例即已加載的動(dòng)態(tài)鏈接庫(kù)梁剔。庫(kù)中的函數(shù)使用標(biāo)準(zhǔn) C 調(diào)用約定,并假定返回 int .在 Windows 上創(chuàng)建 CDLL 實(shí)例可能會(huì)失敗舞蔽,即使 DLL 名稱確實(shí)存在

  2. class ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

    僅 Windows : 此類的實(shí)例即加載好的動(dòng)態(tài)鏈接庫(kù)荣病,其中的函數(shù)使用 stdcall 調(diào)用約定,并且假定返回 windows 指定的 HRESULT 返回碼渗柿。 HRESULT 的值包含的信息說(shuō)明函數(shù)調(diào)用成功還是失敗个盆,以及額外錯(cuò)誤碼。 如果返回值表示失敗朵栖,會(huì)自動(dòng)拋出 OSError 異常颊亮。

  3. class ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

    僅 Windows: 此類的實(shí)例即加載好的動(dòng)態(tài)鏈接庫(kù),其中的函數(shù)使用 stdcall 調(diào)用約定陨溅,并假定默認(rèn)返回 int

  4. class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None)

    這個(gè)類實(shí)例的行為與 CDLL 類似终惑,只不過(guò) 不會(huì) 在調(diào)用函數(shù)的時(shí)候釋放 GIL 鎖,且調(diào)用結(jié)束后會(huì)檢查 Python 錯(cuò)誤碼门扇。 如果錯(cuò)誤碼被設(shè)置雹有,會(huì)拋出一個(gè) Python 異常。所以臼寄,它只在直接調(diào)用 Python C 接口函數(shù)的時(shí)候有用
    通過(guò)使用至少一個(gè)參數(shù)(共享庫(kù)的路徑名)調(diào)用它們霸奕,可以實(shí)例化所有這些類。也可以傳入一個(gè)已加載的動(dòng)態(tài)鏈接庫(kù)作為 handler 參數(shù)吉拳,其他情況會(huì)調(diào)用系統(tǒng)底層的 dlopenLoadLibrary 函數(shù)將庫(kù)加載到進(jìn)程质帅,并獲取其句柄。如cdll.LoadLibrary()、oledll.LoadLibrary()煤惩、windll.LoadLibrary()嫉嘀、pydll.LoadLibrary()

WinDll雖然是可以應(yīng)用于windows平臺(tái)上,但是其只能加載標(biāo)準(zhǔn)函數(shù)調(diào)用約定為_(kāi)_stdcall的動(dòng)態(tài)庫(kù);

msvcrt.dll中函數(shù)調(diào)用約定是C/C++默認(rèn)的調(diào)用約定__cdecl,就不能用WinDll,得用CDLL

其中OleDLL對(duì)數(shù)據(jù)類型比較嚴(yán)格要求, 比如C代碼中,如果讓int跟float相加,返回不能是float,只能是int,而且結(jié)果還是錯(cuò)的.

方法/屬性訪問(wèn)

這些類的實(shí)例沒(méi)有共用方法盟庞。動(dòng)態(tài)鏈接庫(kù)的導(dǎo)出函數(shù)可以通過(guò)屬性或者索引的方式訪問(wèn)吃沪。注意汤善,通過(guò)屬性的方式訪問(wèn)會(huì)緩存這個(gè)函數(shù)什猖,因而每次訪問(wèn)它時(shí)返回的都是同一個(gè)對(duì)象。另一方面红淡,通過(guò)索引訪問(wèn)不狮,每次都會(huì)返回一個(gè)新的對(duì)象:

>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

尋找動(dòng)態(tài)庫(kù)

ctypes.util.find_library(name)

嘗試尋找一個(gè)庫(kù)然后返回其路徑名, name 是庫(kù)名稱, 且去除了 lib 等前綴和 .so 在旱、 .dylib 摇零、版本號(hào)等后綴(這是 posix 連接器 -l 選項(xiàng)使用的格式)。如果沒(méi)有找到對(duì)應(yīng)的庫(kù)桶蝎,則返回 None 驻仅。

在 Linux 上, find_library() 會(huì)嘗試運(yùn)行外部程序(/sbin/ldconfig, gcc, objdump 以及 ld) 來(lái)尋找?guī)煳募窃7祷貛?kù)文件的文件名噪服。

在 3.6 版更改: 在Linux 上,如果其他方式找不到的話胜茧,會(huì)使用環(huán)境變量 LD_LIBRARY_PATH 搜索動(dòng)態(tài)鏈接庫(kù)粘优。

在 Windows 上, find_library() 在系統(tǒng)路徑中搜索呻顽,然后返回全路徑雹顺,但是如果沒(méi)有預(yù)定義的命名方案, find_library("c") 調(diào)用會(huì)返回 None
使用 ctypes 包裝動(dòng)態(tài)鏈接庫(kù)廊遍,更好的方式 可能 是在開(kāi)發(fā)的時(shí)候就確定名稱嬉愧,然后硬編碼到包裝模塊中去,而不是在運(yùn)行時(shí)使用 find_library() 尋找?guī)臁?/p>

把動(dòng)態(tài)庫(kù)設(shè)置環(huán)境變量

windows:只需要把關(guān)聯(lián)動(dòng)態(tài)庫(kù)復(fù)制到加載的動(dòng)態(tài)庫(kù)同級(jí)目錄下;

linux:需要添加環(huán)境變量
sudo echo /home/seetaFace6Python/seetaface/lib/centos  > /etc/ld.so.conf.d/seetaface6.conf
sudo ldconfig

可能遇到ldconfig: /lib64/libstdc++.so.6 不是符號(hào)連接

解決辦法

[root@localhost lib64]# ln -sf /usr/lib64/libstdc++.so.6.0.19 /usr/lib64/libstdc++.so.6
[root@localhost lib64]# sudo ldconfig

容器設(shè)置方法:

  1. 直接在Dockefile中 利用COPY把動(dòng)態(tài)庫(kù)文件復(fù)制進(jìn)鏡像中,并且設(shè)置動(dòng)態(tài)庫(kù)環(huán)境變量.但是這種通用性不好

  2. 在Dockerfile中,新建lib文件夾,可以新建多個(gè)如/home/lib/lib1喉前、/home/lib/lib2.并且設(shè)置環(huán)境變量,然后再docker run中利用-v進(jìn)行掛載即可

    二没酣、數(shù)據(jù)類型

ctypes 類型 C 類型 Python 數(shù)據(jù)類型
c_bool _Bool bool (1)
c_char char 單字符字節(jié)串對(duì)象
c_wchar wchar_t 單字符字符串
c_byte char int
c_ubyte unsigned char int
POINTER(c_ubyte) uchar* int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64long long int
c_ulonglong unsigned __int64unsigned long long int
c_size_t size_t int
c_ssize_t ssize_tPy_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (NUL terminated) 字節(jié)串對(duì)象或 None
c_wchar_p wchar_t * (NUL terminated) 字符串或 None
c_void_p void * int 或 None

該表格列舉了ctypes、c和python之間基本數(shù)據(jù)的對(duì)應(yīng)關(guān)系被饿,在定義函數(shù)的參數(shù)和返回值時(shí)四康,需記住幾點(diǎn):

必須使用ctypes的數(shù)據(jù)類型
參數(shù)類型用關(guān)鍵字argtypes定義argtypes必須是一個(gè)序列狭握,如tuple或list闪金,否則會(huì)報(bào)錯(cuò)

返回類型用restype定義,使用 None 表示 void,即不返回任何結(jié)果的函數(shù)

若沒(méi)有顯式定義參數(shù)類型和返回類型,python默認(rèn)為int型
cast() 函數(shù)可以將一個(gè)指針實(shí)例強(qiáng)制轉(zhuǎn)換為另一種 ctypes 類型哎垦。 cast() 接收兩個(gè)參數(shù)囱嫩,一個(gè) ctypes 指針對(duì)象或者可以被轉(zhuǎn)換為指針的其他類型對(duì)象,和一個(gè) ctypes 指針類型漏设。 返回第二個(gè)類型的一個(gè)實(shí)例墨闲,該返回實(shí)例和第一個(gè)參數(shù)指向同一片內(nèi)存空間:

>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>

所以 cast() 可以用來(lái)給結(jié)構(gòu)體 Barvalues 字段賦值:

>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0

cast應(yīng)用:獲取numpy數(shù)組指針

a = np.asarray(range(16), dtype=np.int32).reshape([4,4])
if not a.flags['C_CONTIGUOUS']:
  a = np.ascontiguous(a, dtype=a.dtype) # 如果不是C連續(xù)的內(nèi)存,必須強(qiáng)制轉(zhuǎn)換
a_ctypes_ptr = cast(a.ctypes.data, POINTER(c_int))  #轉(zhuǎn)換為ctypes郑口,這里轉(zhuǎn)換后的可以直接利用ctypes轉(zhuǎn)換為c語(yǔ)言中的int*鸳碧,然后在c中使用
for i in range(16):
  print(a_ctypes_ptr[i])

三、C代碼編譯

如下c代碼,deme.c

/******C端代碼*********/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int add(int a,float b){
    printf("a=%d\n", a);
    printf("b=%f\n", b);
    return a+b;
}

int hello()
{
    printf("Hello world\n");
    return 0;    
}

編譯

gcc -fPIC -shared -o libdeme.so deme.c

四犬性、C++代碼編譯

由于ctypes是與C兼容的數(shù)據(jù)類型,也就是針對(duì)C進(jìn)行編譯后進(jìn)行調(diào)用,所以直接對(duì)C++代碼編譯,在python調(diào)用時(shí),會(huì)提示找不到函數(shù)

Traceback (most recent call last):
  File "d:\AI\C++_study\Test\demo.py", line 7, in <module>
    dll.hello()
  File "D:\Anaconda3\envs\py36\lib\ctypes\__init__.py", line 361, in __getattr__
    func = self.__getitem__(name)
  File "D:\Anaconda3\envs\py36\lib\ctypes\__init__.py", line 366, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: function 'hello' not found

特別注意在調(diào)用C++函數(shù)需要在函數(shù)聲明時(shí)瞻离,加入前綴“ extern "C" ”,這是由于C++支持函數(shù)重載功能乒裆,在編譯時(shí)會(huì)更改函數(shù)名套利。在函數(shù)聲明時(shí),前綴extern "C"則確保按C的方式編譯鹤耍。

c++需要demo.cpp,如下

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define DLLEXPORT extern "C" __declspec(dllexport)  
DLLEXPORT float __stdcall add(int a,float b){
    printf("a=%d\n", a);
    printf("b=%f\n", b);
    return a+b;
}

DLLEXPORT int __stdcall hello()
{
    printf("Hello world\n");
    return 0;    
}

__declspec(dllexport)可以省略,其他都不可以

或者如下簡(jiǎn)單書(shū)寫(xiě):

extern "C" float add(int a,float b){
    printf("a=%d\n", a);
    printf("b=%f\n", b);
    return a+b;
}

四肉迫、python代碼

demo.py

# -*- coding: utf-8 -*-
from ctypes import *

# dll =CDLL("./libdemo.so")
# dll = cdll.LoadLibrary("./libdemo.so")
# dll = windll.LoadLibrary("./libdemo.so")

dll = PyDLL("./libdemo.so")
dll.hello()
dll.add.argtypes=[c_int,c_float]
dll.add.restype=c_float
a=c_int(10)
b=c_float(20.5)
res= dll.add(a,b)
print("res=",res)

結(jié)果如下

Hello world
a=10
b=20.500000
res= 30.5

五、指針的使用

5.1 創(chuàng)建指針

函數(shù) 說(shuō)明
byref(x [, offset]) 返回 x 的地址稿黄,x 必須為 ctypes 類型的一個(gè)實(shí)例喊衫。相當(dāng)于 c 的 &x 。 offset 表示偏移量抛猖。
pointer(x) 創(chuàng)建并返回一個(gè)指向 x 的指針實(shí)例格侯, x 是一個(gè)實(shí)例對(duì)象。
POINTER(type) 返回一個(gè)類型财著,這個(gè)類型是指向 type 類型的指針類型联四, type 是 ctypes 的一個(gè)類型。

byref 很好理解撑教,傳遞參數(shù)的時(shí)候就用這個(gè)朝墩,用 pointer 創(chuàng)建一個(gè)指針變量也行,不過(guò) byref 更快伟姐。
而 pointer 和 POINTER 的區(qū)別是收苏,pointer 返回一個(gè)實(shí)例,POINTER 返回一個(gè)類型愤兵。甚至你可以用 POINTER 來(lái)做 pointer 的工作:

>>> a = c_int(66)         # 創(chuàng)建一個(gè) c_int 實(shí)例
>>> b = pointer(a)        # 創(chuàng)建指針
>>> c = POINTER(c_int)(a) # 創(chuàng)建指針
>>> b
<__main__.LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents            # 輸出 a 的值
c_long(66)
>>> c.contents            # 輸出 a 的值
c_long(66)

可以將 ctypes 類型數(shù)據(jù)傳入 pointer() 函數(shù)創(chuàng)建指針:

>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)

指針實(shí)例擁有 contents 屬性鹿霸,它返回指針指向的真實(shí)對(duì)象,如上面的 i 對(duì)象:

>>> pi.contents
c_long(42)

注意 ctypes 并沒(méi)有 OOR (返回原始對(duì)象), 每次訪問(wèn)這個(gè)屬性時(shí)都會(huì)構(gòu)造返回一個(gè)新的相同對(duì)象:

>>> pi.contents is i
False
>>> pi.contents is pi.contents
False

將這個(gè)指針的 contents 屬性賦值為另一個(gè) c_int 實(shí)例將會(huì)導(dǎo)致該指針指向該實(shí)例的內(nèi)存地址

指針對(duì)象也可以通過(guò)整數(shù)下標(biāo)進(jìn)行訪問(wèn)和賦值,賦值會(huì)把原來(lái)的值內(nèi)容覆蓋

>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)

無(wú)參調(diào)用指針類型可以創(chuàng)建一個(gè) NULL 指針秆乳。 NULL 指針的布爾值是 False

>>> null_ptr = POINTER(c_int)()
>>> print(bool(null_ptr))
False

有時(shí)候 C 函數(shù)接口可能由于要往某個(gè)地址寫(xiě)入值懦鼠,或者數(shù)據(jù)太大不適合作為值傳遞钻哩,從而希望接收一個(gè) 指針 作為數(shù)據(jù)參數(shù)類型。

使用bytef()來(lái)引用傳遞參數(shù)

5.2 指針傳遞值:

用byref()

C/C++:

DLLEXPORT void __stdcall add_point(float* a, float* b, float* c)
{
    *c = *a + *b;
    *a = 129.7;
}

python

x1 = ctypes.c_float(1.9)
x2 = ctypes.c_float(10.1)
x3 = ctypes.c_float(0)
dll.add_point(byref(x1),byref(x2),byref(x3))
print("x1=",x1)
print("x2=",x2)
print("x3=",x3)

結(jié)果為:

x1= 129.6999969482422
x2= 10.100000381469727
x3= 12.0

值隨著指針進(jìn)行改變,另外在小數(shù)位上會(huì)進(jìn)行值變動(dòng).小數(shù)位保留7位的話,基本一致

5.3 接收指針數(shù)據(jù):

利用POINT()來(lái)接收指針數(shù)據(jù),在接收類型中聲明

C/C++

DLLEXPORT int* __stdcall point(int* x)
{
    int* y=NULL;
    y = x;
    return y;
}

PYTHON:

x = ctypes.c_int(2560)
Cfun.point.restype = ctypes.POINTER(ctypes.c_int)  ##聲明函數(shù)返回值為int*
y = Cfun.point(ctypes.byref(x))
print("y is %d" % y[0])

不可接收返回?cái)?shù)組,因?yàn)榉祷氐臑閮?nèi)存地址

5.4 數(shù)組的使用

在C中創(chuàng)建array函數(shù):

DLLEXPORT void __stdcall get_array(int x[])
{
     printf("x[0]= %d x[1]=%d x[2]=%d x[3]=%d \n", *x,x[1],x[2],x[3]);
     *x = 100;
}

python:

Array = ctypes.c_int * 4;  ##聲明一維數(shù)組,數(shù)組長(zhǎng)度為4
a = Array(0, 1, 2, 3)  ##初始化數(shù)組
dll.get_array(a)
print(a[0], a[1], a[2], a[3])   # 數(shù)組沒(méi)辦法打印整體
# 這一把數(shù)組轉(zhuǎn)為列表再打印
a_list=[]
for i in range(4):
    a_list.append(a[i])
print(a_list)

結(jié)果:

x[0]= 0 x[1]=1 x[2]=2 x[3]=3 
 100 1 2 3

還可以初始化一個(gè)空,再賦值

Array = c_int * 4
func_list = Array()
for i in range(4):
    func_list[i] = i

還可以初始化一個(gè)列表再轉(zhuǎn)為數(shù)組

pyarr=[1,2,3,4]
arr = (ctypes.c_int * len(pyarr))(*pyarr)

在C中修改數(shù)組的值肛冶,在python中確實(shí)被修改街氢。

聲明二維數(shù)組的方法:

Array = (ctypes.c_int * 4)*5  ##聲明二維數(shù)組
a=Array()
###使用循環(huán)對(duì)其進(jìn)行賦值
for i in range(5):
    for j in range(4):
        a[i][j]=i*j

三維:

list3d = [
    [[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0]], 
    [[0.2, 1.2, 2.2, 3.2], [4.2, 5.2, 6.2, 7.2]],
    [[0.4, 1.4, 2.4, 3.4], [4.4, 5.4, 6.4, 7.4]],
]

arr = (c_double * 4 * 2 * 3)(*(tuple(tuple(j) for j in i) for i in list3d))

檢查它是否以行優(yōu)先順序正確初始化:

>>> (c_double * 24).from_buffer(arr)[:]
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 
 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 
 0.4, 1.4, 2.4, 3.4, 4.4, 5.4, 6.4, 7.4]

不可接收返回?cái)?shù)組,因?yàn)榉秶臑閮?nèi)存地址

數(shù)組長(zhǎng)度問(wèn)題,python數(shù)組傳到c++后,使用sizeof(arr) / sizeof(arr[0]) 的結(jié)果是錯(cuò)的,所以需要把數(shù)組的長(zhǎng)度當(dāng)做參數(shù)傳入

5.5 字符串傳遞

字符串

C/C++接收的類型為char* ,即byte類型,需要對(duì)字符串進(jìn)行編碼,,利用b"內(nèi)容",或者內(nèi)容.encode()后進(jìn)行傳輸,或者用bytes("nihao", 'utf-8')返回值需要decode,

C++:

DLLEXPORT char* __stdcall get_str(char * path)
{
    cout<<"path:"<<path<<endl;
    char* ret;
    ret = (char *)malloc(10);
    strcpy(ret, "你好hello123,./");
    return ret;
}

python:

dll.get_str.argtypes=[c_char_p]
tex= "你好呀dsf123,./"   #或者b"你好呀dsf123,./"
texd=tex.encode()  #或者bytes("nihao", 'utf-8')
text = c_char_p(texd)
dll.get_str.restype=c_char_p
rt_str=dll.get_str(text)
print(rt_str.decode())

字符串傳輸給C/C++,如果有中文會(huì)亂碼,但返回有中文不會(huì)有亂碼

不用了要free釋放,否則會(huì)造成內(nèi)存泄漏

字符串列表

c/c++

//構(gòu)建字符串?dāng)?shù)組,2個(gè)元素
struct struct_str_arr
{
    char* str_ptr[2];
};
struct_str_arr str_arr;
struct_str_arr* str_arr_ptr = (struct_str_arr*)malloc(sizeof(str_arr));

DLLEXPORT struct_str_arr* __stdcall get_str_list(int n, char *b[2])
{
    for(int i=0;i<n;i++)
    {
        printf("%s", *(b+i));
        printf("\n");
    }
    str_arr_ptr->str_ptr[0]="你好";
    str_arr_ptr->str_ptr[1]="hell";
    return str_arr_ptr;
}

python:

# 返回的數(shù)據(jù)
class StructPointer(ctypes.Structure):  
    _fields_ = [("str_ptr", ctypes.c_char_p * 2)]  


dll.test_str_arr.restype = ctypes.POINTER(StructPointer)
str1 = c_char_p(bytes("nihao", 'utf-8'))
str2 = c_char_p(bytes("shijie", 'utf-8'))
a = (c_char_p*2)(str1, str2)
ret_ptr =lib.get_str_list(2, a)
#ret_ptr =lib.get_str_list(2, pointer(a))
ret_ptr.contents.str_ptr[1].decode()

5.6 結(jié)構(gòu)體傳遞

5.6.1 cvMat傳遞

python中opencv存儲(chǔ)一幅圖像的數(shù)據(jù)類型是array,而在C++中opencv存儲(chǔ)一幅圖像的數(shù)據(jù)類型是Mat睦袖,這兩者之間的轉(zhuǎn)換需要通過(guò)unsigned char * 來(lái)完成珊肃。

unsigned char*等價(jià)于uchar*

數(shù)據(jù)類型對(duì)應(yīng)關(guān)系

python:     ctypes.POINTER(ctypes.c_ubyte) 或者ctypes.c_char_p
C++:        unsigned char *

python中將array轉(zhuǎn)換成ctypes.POINTER(ctypes.c_ubyte)

import ctypes as C
import cv2

img = cv2.imread('ROI0.png')
#將img轉(zhuǎn)換成可被傳入dll的數(shù)據(jù)類型
input = img.ctypes.data_as(C.POINTER(C.c_ubyte))

C++ 中將uchar 轉(zhuǎn)為cvMat*

Mat src = Mat(rows,cols,CV_8UC3,src_data);
//或者分為兩步,利用Mat.data
// Mat src = Mat(Size(cols, rows), CV_8UC3, Scalar(255, 255, 255));  //建立空?qǐng)D
// src.data = src_data;

C++中將uchar 復(fù)制的方法*

ret_data在入?yún)⒅凶鳛橹羔槀鬟f:
memcp(ret_data,src.data,rows*cols*3);
ret_data作為返回結(jié)果傳遞:
    vector<uchar> data_encode;
    imencode(".png", dst, data_encode);  //把圖片dst信息保存到緩存data_encode中
    std::string str_encode(data_encode.begin(), data_encode.end());
    uchar* char_r = new uchar[str_encode.size() + 10];     
    memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
    return char_r;

python中將uchar轉(zhuǎn)為array*

#a為uchar*的數(shù)據(jù)
b =string_at(a,cols*rows*channels)   # 類似于base64
nparr = np.frombuffer(b, np.uint8)
img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR)

完整代碼

C++:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;

//作為返回值返回
extern "C" uchar* mattostring(uchar* src_data,int rows,int cols){
    Mat dst = Mat(Size(cols, rows), CV_8UC3, Scalar(255, 255, 255));  //建立空?qǐng)D
    dst.data = mat_data;
    circle(dst, Point(60, 60), 10, Scalar(255, 0, 0)); //畫(huà)圖

    vector<uchar> data_encode;
    imencode(".png", dst, data_encode);
    std::string str_encode(data_encode.begin(), data_encode.end());
    uchar* char_r = new uchar[str_encode.size() + 10];     
    memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
    return char_r;
}

//作為入?yún)⒅羔槀鬟f
extern "C" void draw_circle(int rows, int cols, unsigned char *src_data , unsigned char *ret_data)
{
    Mat src = Mat(rows, cols, CV_8UC3, src_data);  //uchar* 轉(zhuǎn)cvMat
    circle(src, Point(60, 60), 10, Scalar(255, 0, 0));  //畫(huà)圖
    //將Mat轉(zhuǎn)換成unsigned char
    memcpy(ret_data, src.data, rows*cols*3);
}

python:

from ctypes import *
import cv2
import numpy as np
from PIL import Image
MYDLL= CDLL("./build/libhello.dll")
MYDLL.hello()

image=cv2.imread("./images/ch1.jpg")
rows = image.shape[0]
cols = image.shape[1]
channels =3

MYDLL.mattostring.argtypes = (POINTER(c_ubyte), c_int,c_int) #c_char_p也可以
MYDLL.mattostring.restype = c_void_p   # POINTER(c_ubyte) 跟c_void_p都可以
MYDLL.draw_circle.argtypes=[c_int,c_int,POINTER(c_ubyte),POINTER(c_ubyte)]
MYDLL.draw_circle.restype=c_void_p

ret_img = np.zeros(dtype=np.uint8, shape=(rows, cols, 3))
srcPointer=image.ctypes.data_as(POINTER(c_ubyte))  #方式1.1
#srcPointer=image.ctypes.data_as(c_char_p)     #方式1.2
# srcPointer = image.astype(np.uint8).tostring()     #方式2

a=MYDLL.mattostring(srcPointer,rows,cols)
b =string_at(a,cols*rows*channels)   # 類似于base64

nparr = np.frombuffer(b, np.uint8)  # 轉(zhuǎn)array,但是維度不是圖片
img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR) #轉(zhuǎn)cvMat 
img_decode=Image.fromarray(img_decode[:,:,::-1])  # 由于直接cv2.imshow()顯示出來(lái)的圖是錯(cuò)誤的,保存或者轉(zhuǎn)為Image格式,顯示正確
img_decode.show()

retPoint = ret_img.ctypes.data_as(POINTER(c_ubyte))
MYDLL.draw_circle(rows, cols, srcPointer, retPoint)  
ret_img_out = Image.fromarray(ret_img[:,:,::-1])  # 參數(shù)指針傳遞,不需要從uchar*轉(zhuǎn)換,只需要取他的源頭數(shù)據(jù)即可.
ret_img_out.show()

第二種借助numpy來(lái)進(jìn)行轉(zhuǎn)換馅笙,兩者的區(qū)別就是伦乔,第一種傳的是指針,如果參數(shù)進(jìn)去延蟹,在mattostring函數(shù)內(nèi)對(duì)變量srcPointer進(jìn)行修改則會(huì)影響最終輸出的內(nèi)容评矩,第二種方式不會(huì)有影響。方式2 的argtypes注釋掉如上面的代碼,在最后加上

cv2.imshow("image",image)  #方式1有畫(huà)圈
cv2.waitKey(0)   # 方式2還是原圖,

這里用到了opencv,所以編譯還需要把opencv的頭文件跟庫(kù)文件加入

CMakeLists.txt:

cmake_minimum_required (VERSION 2.6)
project(hello)
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(OpenCV_DIR D:/opencv/opencv-4.5.2)  # 該地址為OpenCVConfig.cmake所在的目錄地址
find_package(OpenCV REQUIRED)
set(SRC_LIST hello.cpp)
# 添加頭文件
include_directories( ${OpenCV_INCLUDE_DIRS} )    # 可省略,在find_package中已經(jīng)實(shí)現(xiàn)
message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
add_library( hello SHARED hello.cpp)
# 鏈接OpenCV庫(kù)
target_link_libraries( hello ${OpenCV_LIBS} )

上述方法把圖片的尺寸寫(xiě)死了,但一般圖片都是動(dòng)態(tài)的,所以需要把尺寸信息也要傳遞回來(lái).這時(shí)采用dtypes的結(jié)構(gòu)體方式

C++結(jié)構(gòu)體及使用代碼:

struct CvMatImage{
    //cv圖片結(jié)構(gòu)體
    int rows;
    int cols;
    int channels;
    uchar *data;
};

extern "C" CvMatImage mattostring(uchar* src_data,int rows,int cols){
    Mat dst = Mat(rows, cols, CV_8UC3, src_data);
    circle(dst, Point(60, 60), 10, Scalar(255, 0, 0)); //畫(huà)圖
    vector<uchar> data_encode;
    imencode(".png", dst, data_encode);
    std::string str_encode(data_encode.begin(), data_encode.end());
    uchar* char_r = new uchar[str_encode.size() + 10];     
    memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
    CvMatImage cvimage{310,310,3,char_r};
    return cvimage;

}

python結(jié)構(gòu)體及使用代碼:(名稱阱飘、屬性名要一致,類型要對(duì)應(yīng)上)

from ctypes import *
import cv2
import numpy as np
from PIL import Image
from typing import List

class CvMatImage(Structure):
    # cvMatImage的結(jié)構(gòu)體
    rows:int
    cols:int
    channels:int
    data:(List[c_ubyte])
    _fields_ = [("rows",c_int32),("cols",c_int32),("channels",c_int32),("data",POINTER(c_ubyte))]
    def __str__(self):
        return "CvImageData(rows={},cols={},channels={},data:{})".format(self.rows,self.cols,self.channels,List[c_ubyte])

def get_numpy_by_cvImage(cvimage):
    """
    結(jié)構(gòu)體轉(zhuǎn)為numpy圖片
    param  cvimage:cvimage的結(jié)構(gòu)體,包含rows,cols,channels,data
    return  :numpy圖片
    """
    data = cvimage.data
    cv_rows = cvimage.rows
    cv_cols =cvimage.cols
    cv_channels = cvimage.channels
    b =string_at(data,cv_cols*cv_rows*cv_channels)   # 類似于base64
    nparr = np.frombuffer(b, np.uint8)
    img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR)
    return img_decode

MYDLL = CDLL("./build/libhello.dll")
image = cv2.imread("./images/ch1.jpg")
(rows,cols,channels) = image.shape
MYDLL.mattostring.argtypes = (POINTER(c_ubyte), c_int, c_int)
MYDLL.mattostring.restype = CvMatImage   # todo 這里設(shè)置非常重要
srcPointer = image.ctypes.data_as(POINTER(c_ubyte))  
cvimage = MYDLL.mattostring(srcPointer, rows, cols)
img_decode = get_numpy_by_cvImage(cvimage)
img_decode = Image.fromarray(img_decode[:, :, ::-1])
img_decode.show()

5.6.2 seetaImageData傳遞

seetaImageData為seetaface的圖片數(shù)據(jù)格式,本身有頭文件#include <seeta/Common/Struct.h>

struct SeetaImageData
{
    int width;              // 圖像寬度
    int height;             // 圖像高度
    int channels;           // 圖像通道
    unsigned char *data;    // 圖像數(shù)據(jù)
};

故在python中定義一樣的結(jié)構(gòu)體

class SeetaImageData(Structure):
    width: int
    height: int
    channels: int
    data:List[c_ubyte]
    _fields_=[('width',c_int32),('height',c_int32),('channels',c_int32),("data",POINTER(c_ubyte))]

    def __str__(self):
        return "SeetaImageData(width={},height={},channels={},data:{})".format(self.width,self.height,self.channels,List[c_ubyte])

C++代碼(函數(shù)部分):

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <seeta/Common/Struct.h>
#include <seeta/FaceDetector.h>

using namespace std;
using namespace cv;

seeta::FaceDetector* new_fd() {
    seeta::ModelSetting setting;
    setting.device = SEETA_DEVICE_GPU;  //GPU CPU AUTO
     setting.id = 0;
    setting.append("./models/face_detector.csta");
  //按原始cpp文件所在的路徑為參考,而不是動(dòng)態(tài)庫(kù)所在路徑.但如果是執(zhí)行文件,那么按執(zhí)行文件的位置
    return new seeta::FaceDetector(setting);
}

extern "C" SeetaFaceInfoArray Detect(SeetaImageData simage){
    seeta::FaceDetector *faceDetector = new_fd();
    SeetaFaceInfoArray faces = faceDetector->detect(simage);
    return faces;
 }

注意: C++代碼所涉及的文件的相對(duì)地址:

  • 生成動(dòng)態(tài)庫(kù)時(shí),,參考為原始cpp文件,
  • 生成執(zhí)行文件,參考編譯后的執(zhí)行文件

python代碼(調(diào)用部分):

def get_seetaImageData_by_numpy(image_np: np.array) -> SeetaImageData:
    """
    
    param  image_np:numpy數(shù)組
    return  :seetaImageData結(jié)構(gòu)體
    """
    
    seetaImageData = SeetaImageData()
    height, width, channels = image_np.shape
    seetaImageData.height = int(height)
    seetaImageData.width = int(width)
    seetaImageData.channels = int(channels)
    seetaImageData.data = image_np.ctypes.data_as(POINTER(c_ubyte))
    return seetaImageData

MYDLL = CDLL("./lib/centos/libFaceAPI.so")
image = cv2.imread("./images/ch1.jpg")
(rows,cols,channels) = image.shape
simage = get_seetaImageData_by_numpy(image)
MYDLL.Detect.argtypes = (SeetaImageData,)
MYDLL.Detect.restype = SeetaFaceInfoArray
detect_result = MYDLL.Detect(simage)
#打印結(jié)果為SeetaFaceInfoArray(data:[SeetaFaceInfo(pos=SeetaRect(x=101,y=50,width=96,height=126),score=0.9995892643928528)],size:1)
rect_list = detect_result.data

注:關(guān)于入?yún)⒄f(shuō)明

  • MYDLL.Detect.argtypes = (SeetaImageData,) 傳的為數(shù)據(jù),則在C++中,入?yún)镾eetaImageData
  • MYDLL.Detect.argtypes = (POINT(SeetaImageData),) 傳的為地址,則C++中,入?yún)镾eetaImageData&

5.7 結(jié)構(gòu)體數(shù)組傳遞

SeetaPointF *5的數(shù)組傳遞

SeetaPointF 的結(jié)構(gòu)體為:

# python
class SeetaPointF(Structure):
    x: int
    y: int
    _fields_=[('x',c_double),('y',c_double)]
// c++
struct SeetaPointF
{
    double x;
    double y;
};

作為參數(shù)指針傳入:

C++:

extern "C" int mark5(SeetaImageData &simage, SeetaRect &box, SeetaPointF points5[5])
{
    std::vector<SeetaPointF> points = landDetector5->mark(simage, box);
    int size = points.size();
    for (int i = 0; i < size; i++)
        points5[i] = points[i];  //由于points為vecter,需要轉(zhuǎn)化為數(shù)組.
    return 1;
}

python:

MYDLL.mark5.argtypes = (POINTER(SeetaImageData),POINTER(SeetaRect),POINTER(SeetaPointF))
# 或者
# MYDLL.mark5.argtypes = (POINTER(SeetaImageData),POINTER(SeetaRect),SeetaPointF*5) 
detect_result = MYDLL.Detect(simage)
rect_list = detect_result.data
if detect_result.size > 0:
    _face = detect_result.data[0].pos
    points = (SeetaPointF * 5)()  #初始化一個(gè)長(zhǎng)度為5的空數(shù)組
    MYDLL.mark5(simage, _face,points) #作為參數(shù)以地址傳遞,那么在c代碼中points改變了,python中的point也會(huì)改變

關(guān)于結(jié)構(gòu)體數(shù)組傳入后,隊(duì)員原始函數(shù)的入?yún)榻Y(jié)構(gòu)體* 的使用方法.

說(shuō)明:入?yún)⒔Y(jié)構(gòu)體* 即傳入結(jié)構(gòu)體數(shù)組的起始也就是第一個(gè)數(shù)據(jù)的地址. 

方法一: 把數(shù)組轉(zhuǎn)為vector,那么入?yún)⒕涂梢愿臑?.data()
extern "C" int Predict(SeetaImageData &simage, const SeetaRect &box, SeetaPointF points5[5])
{
    std::vector<SeetaPointF> points;
    for (int i = 0; i < 5; i++)
    {
        points.push_back(points5[i]);
    }
    auto status = liveDetector->Predict(simage, box, points.data());
    
}
方法二: 直接提取結(jié)構(gòu)體數(shù)組的第一個(gè)數(shù)據(jù)的地址作為入?yún)⒓纯?
extern "C" int Predict(SeetaImageData &simage, const SeetaRect &box, SeetaPointF points5[5])
{
 auto status = liveDetector->Predict(simage, box, &points5[0]);  //&poinst5[0]為提取第一個(gè)元素的地址
 }
 

返回結(jié)構(gòu)體數(shù)組

<font color='red'>放棄吧,因?yàn)镃代碼返回的為數(shù)組地址.C可以調(diào)用的時(shí)候取值,但python沒(méi)辦法去通過(guò)內(nèi)存取值</font>

六、指導(dǎo)說(shuō)明

工具函數(shù)

  • ctypes.addressof(obj)

    以整數(shù)形式返回內(nèi)存緩沖區(qū)地址虱颗。 obj 必須為一個(gè) ctypes 類型的實(shí)例沥匈。引發(fā)一個(gè) 審計(jì)事件 ctypes.addressof,附帶參數(shù) obj忘渔。

  • ctypes.alignment(obj_or_type)

    返回一個(gè) ctypes 類型的對(duì)齊要求高帖。 obj_or_type 必須為一個(gè) ctypes 類型或?qū)嵗?/p>

  • ctypes.byref(obj[, offset])

    返回指向 obj 的輕量指針,該對(duì)象必須為一個(gè) ctypes 類型的實(shí)例畦粮。 offset 默認(rèn)值為零散址,且必須為一個(gè)將被添加到內(nèi)部指針值的整數(shù)。byref(obj, offset) 對(duì)應(yīng)于這段 C 代碼:(((char *)&obj) + offset)返回的對(duì)象只能被用作外部函數(shù)調(diào)用形參宣赔。 它的行為類似于 pointer(obj)预麸,但構(gòu)造起來(lái)要快很多。

  • ctypes.cast(obj, type)

    此函數(shù)類似于 C 的強(qiáng)制轉(zhuǎn)換運(yùn)算符儒将。 它返回一個(gè) type 的新實(shí)例吏祸,該實(shí)例指向與 obj 相同的內(nèi)存塊。 type 必須為指針類型钩蚊,而 obj 必須為可以被作為指針來(lái)解讀的對(duì)象贡翘。

  • ctypes.create_string_buffer(init_or_size, size=None)

    此函數(shù)會(huì)創(chuàng)建一個(gè)可變的字符緩沖區(qū)。 返回的對(duì)象是一個(gè) c_char 的 ctypes 數(shù)組砰逻。init_or_size 必須是一個(gè)指明數(shù)組大小的整數(shù)鸣驱,或者是一個(gè)將被用來(lái)初始化數(shù)組條目的字節(jié)串對(duì)象。如果將一個(gè)字節(jié)串對(duì)象指定為第一個(gè)參數(shù)蝠咆,則將使緩沖區(qū)大小比其長(zhǎng)度多一項(xiàng)以便數(shù)組的最后一項(xiàng)為一個(gè) NUL 終結(jié)符踊东。 可以傳入一個(gè)整數(shù)作為第二個(gè)參數(shù)以允許在不使用字節(jié)串長(zhǎng)度的情況下指定數(shù)組大小。引發(fā)一個(gè) 審計(jì)事件 ctypes.create_string_buffer,附帶參數(shù) init, size递胧。

  • ctypes.create_unicode_buffer(init_or_size, size=None)

    此函數(shù)會(huì)創(chuàng)建一個(gè)可變的 unicode 字符緩沖區(qū)碑韵。 返回的對(duì)象是一個(gè) c_wchar 的 ctypes 數(shù)組。init_or_size 必須是一個(gè)指明數(shù)組大小的整數(shù)缎脾,或者是一個(gè)將被用來(lái)初始化數(shù)組條目的字符串祝闻。如果將一個(gè)字符串指定為第一個(gè)參數(shù),則將使緩沖區(qū)大小比其長(zhǎng)度多一項(xiàng)以便數(shù)組的最后一項(xiàng)為一個(gè) NUL 終結(jié)符遗菠。 可以傳入一個(gè)整數(shù)作為第二個(gè)參數(shù)以允許在不使用字符串長(zhǎng)度的情況下指定數(shù)組大小联喘。引發(fā)一個(gè) 審計(jì)事件 ctypes.create_unicode_buffer,附帶參數(shù) init, size辙纬。

  • ctypes.DllCanUnloadNow()

    僅限 Windows:此函數(shù)是一個(gè)允許使用 ctypes 實(shí)現(xiàn)進(jìn)程內(nèi) COM 服務(wù)的鉤子豁遭。 它將由 _ctypes 擴(kuò)展 dll 所導(dǎo)出的 DllCanUnloadNow 函數(shù)來(lái)調(diào)用。

  • ctypes.DllGetClassObject()

    僅限 Windows:此函數(shù)是一個(gè)允許使用 ctypes 實(shí)現(xiàn)進(jìn)程內(nèi) COM 服務(wù)的鉤子贺拣。 它將由 _ctypes 擴(kuò)展 dll 所導(dǎo)出的 DllGetClassObject 函數(shù)來(lái)調(diào)用蓖谢。

  • ctypes.util.find_library(name)

    嘗試尋找一個(gè)庫(kù)并返回路徑名稱。 name 是庫(kù)名稱并且不帶任何前綴如 lib 以及后綴如 .so譬涡,.dylib 或版本號(hào)(形式與 posix 鏈接器選項(xiàng) -l 所用的一致)闪幽。 如果找不到庫(kù),則返回 None涡匀。確切的功能取決于系統(tǒng)盯腌。

  • ctypes.util.find_msvcrt()

    僅限 Windows:返回 Python 以及擴(kuò)展模塊所使用的 VC 運(yùn)行時(shí)庫(kù)的文件名。 如果無(wú)法確定庫(kù)名稱陨瘩,則返回 None腕够。如果你需要通過(guò)調(diào)用 free(void *) 來(lái)釋放內(nèi)存,例如某個(gè)擴(kuò)展模塊所分配的內(nèi)存舌劳,重要的一點(diǎn)是你應(yīng)當(dāng)使用分配內(nèi)存的庫(kù)中的函數(shù)帚湘。

  • ctypes.FormatError([code])

    僅限 Windows:返回錯(cuò)誤碼 code 的文本描述。 如果未指定錯(cuò)誤碼蒿囤,則會(huì)通過(guò)調(diào)用 Windows api 函數(shù) GetLastError 來(lái)獲得最新的錯(cuò)誤碼客们。

  • ctypes.GetLastError()

    僅限 Windows:返回 Windows 在調(diào)用線程中設(shè)置的最新錯(cuò)誤碼。 此函數(shù)會(huì)直接調(diào)用 Windows GetLastError() 函數(shù)材诽,它并不返回錯(cuò)誤碼的 ctypes 私有副本底挫。

  • ctypes.get_errno()

    返回調(diào)用線程中系統(tǒng) errno 變量的 ctypes 私有副本的當(dāng)前值。引發(fā)一個(gè) 審計(jì)事件 ctypes.get_errno脸侥,不附帶任何參數(shù)建邓。

  • ctypes.get_last_error()

    僅限 Windows:返回調(diào)用線程中系統(tǒng) LastError 變量的 ctypes 私有副本的當(dāng)前值。引發(fā)一個(gè) 審計(jì)事件 ctypes.get_last_error睁枕,不附帶任何參數(shù)官边。

  • ctypes.memmove(dst, src, count)

    與標(biāo)準(zhǔn) C memmove 庫(kù)函數(shù)相同:將 count 個(gè)字節(jié)從 src 拷貝到 dst沸手。 dstsrc 必須為整數(shù)或可被轉(zhuǎn)換為指針的 ctypes 實(shí)例。

  • ctypes.memset(dst, c, count)

    與標(biāo)準(zhǔn) C memset 庫(kù)函數(shù)相同:將位于地址 dst 的內(nèi)存塊用 count 個(gè)字節(jié)的 c 值填充注簿。 dst 必須為指定地址的整數(shù)或 ctypes 實(shí)例契吉。

  • ctypes.POINTER(type)

    這個(gè)工廠函數(shù)創(chuàng)建并返回一個(gè)新的 ctypes 指針類型。 指針類型會(huì)被緩存并在內(nèi)部重用诡渴,因此重復(fù)調(diào)用此函數(shù)耗費(fèi)不大捐晶。 type 必須為 ctypes 類型。

  • ctypes.pointer(obj)

    此函數(shù)會(huì)創(chuàng)建一個(gè)新的指向 obj 的指針實(shí)例妄辩。 返回的對(duì)象類型為 POINTER(type(obj))惑灵。注意:如果你只是想向外部函數(shù)調(diào)用傳遞一個(gè)對(duì)象指針,你應(yīng)當(dāng)使用更為快速的 byref(obj)眼耀。

  • ctypes.``resize(obj, size)

    此函數(shù)可改變 obj 的內(nèi)部?jī)?nèi)存緩沖區(qū)大小英支,其參數(shù)必須為 ctypes 類型的實(shí)例。 沒(méi)有可能將緩沖區(qū)設(shè)為小于對(duì)象類型的本機(jī)大小值哮伟,該值由 sizeof(type(obj)) 給出干花,但將緩沖區(qū)加大則是可能的。

  • ctypes.``set_errno(value)

    設(shè)置調(diào)用線程中系統(tǒng) errno 變量的 ctypes 私有副本的當(dāng)前值為 value 并返回原來(lái)的值澈吨。引發(fā)一個(gè) 審計(jì)事件 ctypes.set_errno 附帶參數(shù) errno把敢。

  • ctypes.set_last_error(value)

    僅限 Windows:設(shè)置調(diào)用線程中系統(tǒng) LastError 變量的 ctypes 私有副本的當(dāng)前值為 value 并返回原來(lái)的值。引發(fā)一個(gè) 審計(jì)事件 ctypes.set_last_error谅辣,附帶參數(shù) error

  • ctypes.sizeof(obj_or_type)

    返回 ctypes 類型或?qū)嵗膬?nèi)存緩沖區(qū)以字節(jié)表示的大小婶恼。 其功能與 C sizeof 運(yùn)算符相同桑阶。

  • ctypes.string_at(address, size=-1)

    此函數(shù)返回從內(nèi)存地址 address 開(kāi)始的以字節(jié)串表示的 C 字符串。 如果指定了 size勾邦,則將其用作長(zhǎng)度蚣录,否則將假定字符串以零值結(jié)尾。引發(fā)一個(gè) 審計(jì)事件 ctypes.string_at眷篇,附帶參數(shù) address, size萎河。

  • ctypes.WinError(code=None, descr=None)

    僅限 Windows:此函數(shù)可能是 ctypes 中名字起得最差的函數(shù)。 它會(huì)創(chuàng)建一個(gè) OSError 的實(shí)例蕉饼。 如果未指定 code虐杯,則會(huì)調(diào)用 GetLastError 來(lái)確定錯(cuò)誤碼。 如果未指定 descr昧港,則會(huì)調(diào)用 FormatError() 來(lái)獲取錯(cuò)誤的文本描述擎椰。在 3.3 版更改: 以前是會(huì)創(chuàng)建一個(gè) WindowsError 的實(shí)例契讲。

  • ctypes.wstring_at(address, size=-1)

    此函數(shù)返回從內(nèi)存地址 address 開(kāi)始的以字符串表示的寬字節(jié)字符串呕寝。 如果指定了 size,則將其用作字符串中的字符數(shù)量腻贰,否則將假定字符串以零值結(jié)尾。引發(fā)一個(gè) 審計(jì)事件 ctypes.wstring_at巩搏,附帶參數(shù) address, size昨登。

數(shù)據(jù)類型

  • class ctypes._CData

    這個(gè)非公有類是所有 ctypes 數(shù)據(jù)類型的共同基類。 另外贯底,所有 ctypes 類型的實(shí)例都包含一個(gè)存放 C 兼容數(shù)據(jù)的內(nèi)存塊丰辣;該內(nèi)存塊的地址可由 addressof() 輔助函數(shù)返回。 還有一個(gè)實(shí)例變量被公開(kāi)為 _objects丈甸;此變量包含其他在內(nèi)存塊包含指針的情況下需要保持存活的 Python 對(duì)象糯俗。ctypes 數(shù)據(jù)類型的通用方法,它們都是類方法(嚴(yán)謹(jǐn)?shù)卣f(shuō)睦擂,它們是 metaclass 的方法):

    from_buffer(source[, offset])此方法返回一個(gè)共享 source 對(duì)象緩沖區(qū)的 ctypes 實(shí)例得湘。 source 對(duì)象必須支持可寫(xiě)緩沖區(qū)接口。 可選的 offset 形參指定以字節(jié)表示的源緩沖區(qū)內(nèi)偏移量顿仇;默認(rèn)值為零淘正。 如果源緩沖區(qū)不夠大則會(huì)引發(fā) ValueError。引發(fā)一個(gè) 審計(jì)事件 ctypes.cdata/buffer 附帶參數(shù) pointer, size, offset臼闻。

    from_buffer_copy(source[, offset])此方法創(chuàng)建一個(gè) ctypes 實(shí)例鸿吆,從 source 對(duì)象緩沖區(qū)拷貝緩沖區(qū),該對(duì)象必須是可讀的述呐。 可選的 offset 形參指定以字節(jié)表示的源緩沖區(qū)內(nèi)偏移量惩淳;默認(rèn)值為零。 如果源緩沖區(qū)不夠大則會(huì)引發(fā) ValueError乓搬。引發(fā)一個(gè) 審計(jì)事件 ctypes.cdata/buffer 附帶參數(shù) pointer, size, offset思犁。

    from_address(address)此方法會(huì)使用 address 所指定的內(nèi)存返回一個(gè) ctypes 類型的實(shí)例,該參數(shù)必須為一個(gè)整數(shù)进肯。引發(fā)一個(gè) 審計(jì)事件 ctypes.cdata激蹲,附帶參數(shù) address

    from_param(obj)此方法會(huì)將 obj 適配為一個(gè) ctypes 類型江掩。 它調(diào)用時(shí)會(huì)在當(dāng)該類型存在于外部函數(shù)的 argtypes 元組時(shí)傳入外部函數(shù)調(diào)用所使用的實(shí)際對(duì)象学辱;它必須返回一個(gè)可被用作函數(shù)調(diào)用參數(shù)的對(duì)象。所有 ctypes 數(shù)據(jù)類型都帶有這個(gè)類方法的默認(rèn)實(shí)現(xiàn)环形,它通常會(huì)返回 obj策泣,如果該對(duì)象是此類型的實(shí)例的話。 某些類型也能接受其他對(duì)象斟赚。

    in_dll(library, name)此方法返回一個(gè)由共享庫(kù)導(dǎo)出的 ctypes 類型着降。 name 為導(dǎo)出數(shù)據(jù)的符號(hào)名稱,library 為所加載的共享庫(kù)拗军。

    ctypes 數(shù)據(jù)類型的通用實(shí)例變量:

    _b_base_有時(shí) ctypes 數(shù)據(jù)實(shí)例并不擁有它們所包含的內(nèi)存塊任洞,它們只是共享了某個(gè)基對(duì)象的部分內(nèi)存塊蓄喇。 _b_base_ 只讀成員是擁有內(nèi)存塊的根 ctypes 對(duì)象。

    _b_needsfree_這個(gè)只讀變量在 ctypes 數(shù)據(jù)實(shí)例自身已分配了內(nèi)存塊時(shí)為真值交掏,否則為假值妆偏。

    _objects這個(gè)成員或者為 None,或者為一個(gè)包含需要保持存活以使內(nèi)存塊的內(nèi)存保持有效的 Python 對(duì)象的字典盅弛。 這個(gè)對(duì)象只是出于調(diào)試目的而對(duì)外公開(kāi)钱骂;絕對(duì)不要修改此字典的內(nèi)容。

基礎(chǔ)數(shù)據(jù)類型

  • class ctypes.``_SimpleCData

    這個(gè)非公有類是所有基本 ctypes 數(shù)據(jù)類型的基類挪鹏。 它在這里被提及是因?yàn)樗?ctypes 數(shù)據(jù)類型共有的屬性见秽。 _SimpleCData_CData 的子類,因此繼承了其方法和屬性讨盒。 非指針及不包含指針的 ctypes 數(shù)據(jù)類型現(xiàn)在將可以被封存解取。實(shí)例擁有一個(gè)屬性:value這個(gè)屬性包含實(shí)例的實(shí)際值。 對(duì)于整數(shù)和指針類型返顺,它是一個(gè)整數(shù)禀苦,對(duì)于字符類型,它是一個(gè)單字符字符串對(duì)象或字符串遂鹊,對(duì)于字符指針類型振乏,它是一個(gè) Python 字節(jié)串對(duì)象或字符串。當(dāng)從 ctypes 實(shí)例提取 value 屬性時(shí)秉扑,通常每次會(huì)返回一個(gè)新的對(duì)象慧邮。 ctypes沒(méi)有 實(shí)現(xiàn)原始對(duì)象返回,它總是會(huì)構(gòu)造一個(gè)新的對(duì)象舟陆。 所有其他 ctypes 對(duì)象實(shí)例也同樣如此赋咽。

基本數(shù)據(jù)類型當(dāng)作為外部函數(shù)調(diào)用結(jié)果被返回或者作為結(jié)構(gòu)字段成員或數(shù)組項(xiàng)被提取時(shí),會(huì)透明地轉(zhuǎn)換為原生 Python 類型吨娜。 換句話說(shuō),如果某個(gè)外部函數(shù)具有 c_char_prestype淘钟,你將總是得到一個(gè) Python 字節(jié)串對(duì)象宦赠,而 不是 一個(gè) c_char_p 實(shí)例。

基本數(shù)據(jù)類型的子類并 沒(méi)有 繼續(xù)此行為米母。 因此勾扭,如果一個(gè)外部函數(shù)的 restypec_void_p 的一個(gè)子類,你將從函數(shù)調(diào)用得到一個(gè)該子類的實(shí)例铁瞒。 當(dāng)然妙色,你可以通過(guò)訪問(wèn) value 屬性來(lái)獲取指針的值。

這些是基本 ctypes 數(shù)據(jù)類型:

  • class ctypes.c_byte

    代表 C signed char 數(shù)據(jù)類型慧耍,并將值解讀為一個(gè)小整數(shù)身辨。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器丐谋;不會(huì)執(zhí)行溢出檢查。

  • class ctypes.c_char

    代表 C char 數(shù)據(jù)類型煌珊,并將值解讀為單個(gè)字符号俐。 該構(gòu)造器接受一個(gè)可選的字符串初始化器,字符串的長(zhǎng)度必須恰好為一個(gè)字符定庵。

  • class ctypes.c_char_p

    當(dāng)指向一個(gè)以零為結(jié)束符的字符串時(shí)代表 C char * 數(shù)據(jù)類型吏饿。 對(duì)于通用字符指針來(lái)說(shuō)也可能指向二進(jìn)制數(shù)據(jù),必須要使用 POINTER(c_char)蔬浙。 該構(gòu)造器接受一個(gè)整數(shù)地址猪落,或者一個(gè)字節(jié)串對(duì)象。

  • class ctypes.c_double

    代表 C double 數(shù)據(jù)類型畴博。 該構(gòu)造器接受一個(gè)可選的浮點(diǎn)數(shù)初始化器笨忌。

  • class ctypes.c_longdouble

    代表 C long double 數(shù)據(jù)類型。 該構(gòu)造器接受一個(gè)可選的浮點(diǎn)數(shù)初始化器绎晃。 在 sizeof(long double) == sizeof(double) 的平臺(tái)上它是 c_double 的一個(gè)別名蜜唾。

  • class ctypes.c_float

    代表 C float 數(shù)據(jù)類型。 該構(gòu)造器接受一個(gè)可選的浮點(diǎn)數(shù)初始化器庶艾。

  • class ctypes.c_int

    代表 C signed int 數(shù)據(jù)類型袁余。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器;不會(huì)執(zhí)行溢出檢查咱揍。 在 sizeof(int) == sizeof(long) 的平臺(tái)上它是 c_long 的一個(gè)別名颖榜。

  • class ctypes.c_int8

    代表 C 8 位 signed int 數(shù)據(jù)類型。 通常是 c_byte 的一個(gè)別名煤裙。

  • class ctypes.c_int16

    代表 C 16 位 signed int 數(shù)據(jù)類型掩完。 通常是 c_short 的一個(gè)別名。

  • class ctypes.c_int32

    代表 C 32 位 signed int 數(shù)據(jù)類型硼砰。 通常是 c_int 的一個(gè)別名且蓬。

  • class ctypes.c_int64

    代表 C 64 位 signed int 數(shù)據(jù)類型。 通常是 c_longlong 的一個(gè)別名题翰。

  • class ctypes.c_long

    代表 C signed long 數(shù)據(jù)類型恶阴。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器;不會(huì)執(zhí)行溢出檢查豹障。

  • class ctypes.c_longlong

    代表 C signed long long 數(shù)據(jù)類型冯事。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器;不會(huì)執(zhí)行溢出檢查血公。

  • class ctypes.c_short

    代表 C signed short 數(shù)據(jù)類型昵仅。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器;不會(huì)執(zhí)行溢出檢查累魔。

  • class ctypes.c_size_t

    代表 C size_t 數(shù)據(jù)類型摔笤。

  • class ctypes.c_ssize_t

    代表 C ssize_t 數(shù)據(jù)類型够滑。3.2 新版功能.

  • class ctypes.c_ubyte

    代表 C unsigned char 數(shù)據(jù)類型,它將值解讀為一個(gè)小整數(shù)籍茧。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器版述;不會(huì)執(zhí)行溢出檢查。

  • class ctypes.c_uint

    代表 C unsigned int 數(shù)據(jù)類型寞冯。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器渴析;不會(huì)執(zhí)行溢出檢查。 在 sizeof(int) == sizeof(long) 的平臺(tái)上它是 c_ulong 的一個(gè)別名吮龄。

  • class ctypes.c_uint8

    代表 C 8 位 unsigned int 數(shù)據(jù)類型俭茧。 通常是 c_ubyte 的一個(gè)別名。

  • class ctypes.c_uint16

    代表 C 16 位 unsigned int 數(shù)據(jù)類型漓帚。 通常是 c_ushort 的一個(gè)別名母债。

  • class ctypes.c_uint32

    代表 C 32 位 unsigned int 數(shù)據(jù)類型。 通常是 c_uint 的一個(gè)別名尝抖。

  • class ctypes.c_uint64

    代表 C 64 位 unsigned int 數(shù)據(jù)類型毡们。 通常是 c_ulonglong 的一個(gè)別名。

  • class ctypes.c_ulong

    代表 C unsigned long 數(shù)據(jù)類型昧辽。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器衙熔;不會(huì)執(zhí)行溢出檢查。

  • class ctypes.c_ulonglong

    代表 C unsigned long long 數(shù)據(jù)類型搅荞。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器红氯;不會(huì)執(zhí)行溢出檢查。

  • class ctypes.c_ushort

    代表 C unsigned short 數(shù)據(jù)類型咕痛。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器痢甘;不會(huì)執(zhí)行溢出檢查。

  • class ctypes.c_void_p

    代表 C void * 類型茉贡。 該值被表示為整數(shù)形式塞栅。 該構(gòu)造器接受一個(gè)可選的整數(shù)初始化器。

  • class ctypes.c_wchar

    代表 C wchar_t 數(shù)據(jù)類型腔丧,并將值解讀為一單個(gè)字符的 unicode 字符串构蹬。 該構(gòu)造器接受一個(gè)可選的字符串初始化器,字符串的長(zhǎng)度必須恰好為一個(gè)字符悔据。

  • class ctypes.c_wchar_p

    代表 C wchar_t * 數(shù)據(jù)類型,它必須為指向以零為結(jié)束符的寬字符串的指針俗壹。 該構(gòu)造器接受一個(gè)整數(shù)地址或者一個(gè)字符串科汗。

  • class ctypes.c_bool

    代表 C bool 數(shù)據(jù)類型 (更準(zhǔn)確地說(shuō)是 C99 _Bool)。 它的值可以為 TrueFalse绷雏,并且該構(gòu)造器接受任何具有邏輯值的對(duì)象头滔。

  • class ctypes.HRESULT

    Windows 專屬:代表一個(gè) HRESULT 值怖亭,它包含某個(gè)函數(shù)或方法調(diào)用的成功或錯(cuò)誤信息。

  • class ctypes.py_object

    代表 C PyObject * 數(shù)據(jù)類型坤检。 不帶參數(shù)地調(diào)用此構(gòu)造器將創(chuàng)建一個(gè) NULL PyObject * 指針兴猩。

ctypes.wintypes 模塊提供了其他許多 Windows 專屬的數(shù)據(jù)類型,例如 HWND, WPARAMDWORD早歇。 還定義了一些有用的結(jié)構(gòu)體例如 MSGRECT倾芝。

結(jié)構(gòu)化數(shù)據(jù)類型

class ctypes.Structure(*args, **kw)

_fields_
一個(gè)定義結(jié)構(gòu)體字段的序列。 其中的條目必須為 2 元組或 3 元組箭跳。 元組的第一項(xiàng)是字段名稱,第二項(xiàng)指明字段類型谱姓;它可以是任何 ctypes 數(shù)據(jù)類型借尿。

對(duì)于整數(shù)類型字段例如 c_int,可以給定第三個(gè)可選項(xiàng)屉来。 它必須是一個(gè)定義字段比特位寬度的小正整數(shù)路翻。

字段名稱在一個(gè)結(jié)構(gòu)體或聯(lián)合中必須唯一。 不會(huì)檢查這個(gè)唯一性茄靠,但當(dāng)名稱出現(xiàn)重復(fù)時(shí)將只有一個(gè)字段可被訪問(wèn)茂契。

可以在定義 Structure 子類的類語(yǔ)句 之后 再定義 _fields_ 類變量,這將允許創(chuàng)建直接或間接引用其自身的數(shù)據(jù)類型:

class List(Structure):
    pass
List._fields_ = [("pnext", POINTER(List)),
                 ...
                ]

數(shù)組與指針

  • class ctypes.Array(*args)

    數(shù)組的抽象基類嘹黔。

    創(chuàng)建實(shí)際數(shù)組類型的推薦方式是通過(guò)將任意 ctypes 類型與一個(gè)正整數(shù)相乘账嚎。 作為替代方式,你也可以子類化這個(gè)類型并定義 _length__type_ 類變量儡蔓。 數(shù)組元素可使用標(biāo)準(zhǔn)的抽取和切片方式來(lái)讀寫(xiě)郭蕉;對(duì)于切片讀取,結(jié)果對(duì)象本身 并非 一個(gè) Array喂江。

    _length_一個(gè)指明數(shù)組中元素?cái)?shù)量的正整數(shù)召锈。 超出范圍的抽取會(huì)導(dǎo)致 IndexError。 該值將由 len() 返回获询。

    _type_指明數(shù)組中每個(gè)元素的類型涨岁。Array 子類構(gòu)造器可接受位置參數(shù),用來(lái)按順序初始化元素吉嚣。

  • class ctypes._Pointer

    私有對(duì)象梢薪,指針的抽象基類。

    實(shí)際的指針類型是通過(guò)調(diào)用 POINTER() 并附帶其將指向的類型來(lái)創(chuàng)建的尝哆;這會(huì)由 pointer() 自動(dòng)完成秉撇。

    如果一個(gè)指針指向的是數(shù)組,則其元素可使用標(biāo)準(zhǔn)的抽取和切片方式來(lái)讀寫(xiě)。 指針對(duì)象沒(méi)有長(zhǎng)度琐馆,因此 len() 將引發(fā) TypeError规阀。 抽取負(fù)值將會(huì)從指針 之前 的內(nèi)存中讀取(與 C 一樣)瘦麸,并且超出范圍的抽取將可能因非法訪問(wèn)而導(dǎo)致崩潰(視你的運(yùn)氣而定)谁撼。

    _type_指明所指向的類型。

    contents返回指針?biāo)赶虻膶?duì)象滋饲。 對(duì)此屬性賦值會(huì)使指針改為指向所賦值的對(duì)象厉碟。
    [參考1]https://docs.python.org/zh-cn/3.9/library/ctypes.html#pointers

[參考2] https://zhuanlan.zhihu.com/p/36772947

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市了赌,隨后出現(xiàn)的幾起案子墨榄,更是在濱河造成了極大的恐慌,老刑警劉巖勿她,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袄秩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逢并,警方通過(guò)查閱死者的電腦和手機(jī)之剧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)砍聊,“玉大人背稼,你說(shuō)我怎么就攤上這事〔r颍” “怎么了蟹肘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)俯树。 經(jīng)常有香客問(wèn)我帘腹,道長(zhǎng),這世上最難降的妖魔是什么许饿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任阳欲,我火速辦了婚禮,結(jié)果婚禮上陋率,老公的妹妹穿的比我還像新娘球化。我一直安慰自己,他們只是感情好瓦糟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布筒愚。 她就那樣靜靜地躺著,像睡著了一般菩浙。 火紅的嫁衣襯著肌膚如雪锨能。 梳的紋絲不亂的頭發(fā)上扯再,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音址遇,去河邊找鬼。 笑死斋竞,一個(gè)胖子當(dāng)著我的面吹牛倔约,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坝初,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浸剩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鳄袍?” 一聲冷哼從身側(cè)響起绢要,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拗小,沒(méi)想到半個(gè)月后重罪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哀九,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年剿配,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阅束。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呼胚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出息裸,到底是詐尸還是另有隱情蝇更,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布呼盆,位于F島的核電站年扩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宿亡。R本人自食惡果不足惜常遂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挽荠。 院中可真熱鬧克胳,春花似錦、人聲如沸圈匆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)跃赚。三九已至笆搓,卻和暖如春性湿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背满败。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工肤频, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人算墨。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓宵荒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親净嘀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子报咳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容