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ù):
-
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í)存在 -
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
異常颊亮。 -
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
-
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)底層的dlopen
或LoadLibrary
函數(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è)置方法:
直接在Dockefile中 利用COPY把動(dòng)態(tài)庫(kù)文件復(fù)制進(jìn)鏡像中,并且設(shè)置動(dòng)態(tài)庫(kù)環(huán)境變量.但是這種通用性不好
-
在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 |
__int64 或 long long
|
int |
c_ulonglong |
unsigned __int64 或 unsigned long long
|
int |
c_size_t |
size_t |
int |
c_ssize_t |
ssize_t 或 Py_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)體 Bar
的 values
字段賦值:
>>> 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沸手。 dst 和 src 必須為整數(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_p
的 restype
淘钟,你將總是得到一個(gè) Python 字節(jié)串對(duì)象宦赠,而 不是 一個(gè) c_char_p
實(shí)例。
基本數(shù)據(jù)類型的子類并 沒(méi)有 繼續(xù)此行為米母。 因此勾扭,如果一個(gè)外部函數(shù)的 restype
是 c_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
)。 它的值可以為True
或False
绷雏,并且該構(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
, WPARAM
或 DWORD
早歇。 還定義了一些有用的結(jié)構(gòu)體例如 MSG
或 RECT
倾芝。
結(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