ctypes筆記
概述
之前要調用海康的sdk,嘗試過使用ctypes,但是自己沒有潛心學習,以爲這東西不好用。等最近看arcface的pythondemo的時候,發現都是用ctypes做的接口,感動的淚流滿面。。。。
數據基礎
先講數據類型的問題。大多數不知道怎麼處理的問題其實都在這裏。
具體的數據類型在linux和windows上可能有不同,比如c_int在不同的系統上可能長度都不同。
如果遇到不知道是啥的數據類型,先百度,比如DWORD類型,百度一下就知道其實是unsigned int32,所以我們使用c_uint32就ok。
基礎數據類型
ctypes類型 | C類型 | Python類型 | 備註 |
---|---|---|---|
c_bool | _Bool | bool | - |
c_char | char | 1個字符的字節對象 | - |
c_wchar | wchar_t | 1個字符的字符串 | - |
c_byte | char | int | - |
c_ubyte | unsigned char | 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)) | bytes對象或 None | - |
c_wchar_p | wchar_t * ((NUL terminated)) | 字符串或 None | - |
c_void_p | void * | int或 None | 通用指針,不指定數據類型,使用指針內容的時候需要強制轉換。 |
日常中,除了以上類型,還有一些值得注意的地方:一般情況下數據傳遞通過結構體。而結構體很多時候都使用的是指針進行傳遞數據。數據傳遞主要分爲兩種:文本數據和二進制數據。
文本數據很簡單,一般不會有指針, 即使有,使用的方式也是POINTER(c_int)大概這樣的結構。如果是結構體指針,先定義結構體,再使用指針。
二進制數據:包括從內存讀取數據和通過指針傳遞二進制數據。
- 知道文件地址,從內存讀取數據。
假設,我們知道某個指針指向一段內存,也知道這段內存是什麼數據結構,可以通過如下方式將數據結構化:
#從二進制獲取數據
import ctypes
#x_id爲某個指定的地址,該數據爲結構體BASE_STRUCT
base=BASE_STRUCT()
ctypes.memmove(base,x_id,len(base))#從內存讀取結構體數據
通過如上方法就可從內存地址加載該結構體了.這裏要注意一個很重要的問題,結構體的創建者。結構體在c++或python裏面創建的是不一樣的。python對結構體進行了翻譯,但並不是創建了c++的結構體。所以:該方法僅適用於python創建的結構體。(c++創建的結構體字段長度比python的更多,因爲python創建的結構體只有數據,沒有基礎的struct包含的其他東西,當然不知道c語言裏面是不是也是這樣。。)
#include <iostream>
#define DLLEXPORT extern "C" __declspec(dllexport)
typedef struct Sa {
char x;
int y;
char *z[10];
}TEST_STRUCT;
DLLEXPORT void *w(TEST_STRUCT *tt){
tt->y = -101;
std::cout<<"c:"<< sizeof(tt)<<"\n";
//std::cout << "cs:" << sizeof(test_struct) << "\n";
return (void *)tt;
}
對應的python代碼
from ctypes import *
dll = CDLL('Project1.dll')
dllc=cdll.msvcrt#c語言自帶的一些方法
class TEST_STRUCT(Structure):
_fields_=[
("x",c_char),
("y",c_int),
("z",c_char*10)
]
www=TEST_STRUCT()
id=dll.w
id.restype=c_void_p
id.argtypes=(POINTER(TEST_STRUCT),)
w2=TEST_STRUCT()
w2.y=-101
print("w2-size:",sizeof(w2))
res=id(byref(www))
print(res)
print("p:",sizeof(w2))
memmove(addressof(w2),res,sizeof(w2))
print("w2:",w2.y)
print(www.y)
數據回收問題
在ctypes裏面,不要對ctypes的數據進行地址比對判斷是否爲同一對象,要比較也要比較指針的值。因爲ctypes每次調用返回的對象的地址都是不一樣的。
函數調用
函數調用根據垃圾回收機制主要分爲4種調用方式:CDLL,OleDLL,WinDLL,PyDLL。
CDLL:這些庫中的函數使用標準C調用約定,並假定返回 int。(即垃圾回收由c++端進行)
OleDLL:僅限Windows。此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall調用約定,並假定返回特定於Windows的HRESULT代碼。 HRESULT values包含指定函數調用是否失敗或成功的信息,以及其他錯誤代碼。如果返回值表示失敗,OSError則自動引發a。
對c++的要求
由於種種原因,很多東西還是不能直接使用的,ctypes只支持c語言的結構,不支持c++,所以編譯之前,需要對要使用的函數使用如下的前綴,才能調用成功:
//必不可少的東西
#define DLLEXPORT extern "C" __declspec(dllexport)
typedef void (*lpFunc)(char, int, TEST_STRUCT*);
DLLEXPORT int a() {
return 1;
}
//所有要導出的方法均需要加這個,
DLLEXPORT void *w(TEST_STRUCT *tt){
tt->y = -101;
std::cout<<"c:"<< sizeof(tt)<<"\n";
//std::cout << "cs:" << sizeof(test_struct) << "\n";
return (void *)tt;
}
一般情況下,給的sdk均爲一個頭文件(.h)和一堆的dll文件,這裏使用的時候,注意可能需要重新編譯。