ctypes
--- Python 的外部函數庫¶
ctypes
是 Python 的外部函數庫。它提供了與 C 兼容的數據類型,並允許調用 DLL 或共享庫中的函數。可使用該模塊以純 Python 形式對這些庫進行封裝。
ctypes 教程
注意:在本教程中的示例代碼使用 doctest
進行過測試,保證其正確運行。由於有些代碼在 Linux,Windows 或 Mac OS X 下的表現不同,這些代碼會在 doctest 中包含相關的指令注解。
注意:部分示例代碼引用了 ctypes c_int
類型。在 sizeof(long) == sizeof(int)
的平台上此類型是 c_long
的一個別名。所以,在程序輸出 c_long
而不是你期望的 c_int
時不必感到迷惑 --- 它們實際上是同一種類型。
載入動態連接庫
ctypes
導出了 cdll 對象,在 Windows 系統中還導出了 windll 和 oledll 對象用於載入動態連接庫。
通過操作這些對象的屬性,你可以載入外部的動態鏈接庫。cdll 載入按標准的 cdecl
調用協議導出的函數,而 windll 導入的庫按 stdcall
調用協議調用其中的函數。 oledll 也按 stdcall
調用協議調用其中的函數,並假定該函數返回的是 Windows HRESULT
錯誤代碼,並當函數調用失敗時,自動根據該代碼甩出一個 OSError
異常。
在 3.3 版更改: 原來在 Windows 下拋出的異常類型 WindowsError
現在是 OSError
的一個別名。
這是一些 Windows 下的例子。注意:msvcrt
是微軟 C 標准庫,包含了大部分 C 標准函數,這些函數都是以 cdecl 調用協議進行調用的。
>>> from ctypes import * >>> print(windll.kernel32) <WinDLL 'kernel32', handle ... at ...> >>> print(cdll.msvcrt) <CDLL 'msvcrt', handle ... at ...> >>> libc = cdll.msvcrt >>>
Windows 會自動添加通常的 .dll
文件擴展名。
注解
通過 cdll.msvcrt
調用的標准 C 函數,可能會導致調用一個過時的,與當前 Python 所不兼容的函數。因此,請盡量使用標准的 Python 函數,而不要使用 msvcrt
模塊。
在 Linux 下,必須使用 包含 文件擴展名的文件名來導入共享庫。因此不能簡單使用對象屬性的方式來導入庫。因此,你可以使用方法 LoadLibrary()
,或構造 CDLL 對象來導入庫。
>>> cdll.LoadLibrary("libc.so.6") <CDLL 'libc.so.6', handle ... at ...> >>> libc = CDLL("libc.so.6") >>> libc <CDLL 'libc.so.6', handle ... at ...> >>>
操作導入的動態鏈接庫中的函數
通過操作dll對象的屬性來操作這些函數。
>>> from ctypes import * >>> libc.printf <_FuncPtr object at 0x...> >>> print(windll.kernel32.GetModuleHandleA) <_FuncPtr object at 0x...> >>> print(windll.kernel32.MyOwnFunction) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "ctypes.py", line 239, in __getattr__ func = _StdcallFuncPtr(name, self) AttributeError: function 'MyOwnFunction' not found >>>
注意:Win32 系統的動態庫,比如 kernel32
和 user32
,通常會同時導出同一個函數的 ANSI 版本和 UNICODE 版本。UNICODE 版本通常會在名字最后以 W
結尾,而 ANSI 版本的則以 A
結尾。 win32的 GetModuleHandle
函數會根據一個模塊名返回一個 模塊句柄,該函數暨同時包含這樣的兩個版本的原型函數,並通過宏 UNICODE 是否定義,來決定宏 GetModuleHandle
導出的是哪個具體函數。
/* ANSI version */ HMODULE GetModuleHandleA(LPCSTR lpModuleName); /* UNICODE version */ HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll 不會通過這樣的魔法手段來幫你決定選擇哪一種函數,你必須顯式的調用 GetModuleHandleA
或 GetModuleHandleW
,並分別使用字節對象或字符串對象作參數。
有時候,dlls的導出的函數名不符合 Python 的標識符規范,比如 "??2@YAPAXI@Z"
。此時,你必須使用 getattr()
方法來獲得該函數。
>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z") <_FuncPtr object at 0x...> >>>
Windows 下,有些 dll 導出的函數沒有函數名,而是通過其順序號調用。對此類函數,你也可以通過 dll 對象的數值索引來操作這些函數。
>>> cdll.kernel32[1] <_FuncPtr object at 0x...> >>> cdll.kernel32[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "ctypes.py", line 310, in __getitem__ func = _StdcallFuncPtr(name, self) AttributeError: function ordinal 0 not found >>>
調用函數
你可以貌似是調用其它 Python 函數那樣直接調用這些函數。在這個例子中,我們調用了 time()
函數,該函數返回一個系統時間戳(從 Unix 時間起點到現在的秒數),而``GetModuleHandleA()`` 函數返回一個 win32 模塊句柄。
此函數中調用的兩個函數都使用了空指針(用 None
作為空指針):
>>> print(libc.time(None)) 1150640792 >>> print(hex(windll.kernel32.GetModuleHandleA(None))) 0x1d000000 >>>
如果你用 cdecl
調用方式調用 stdcall
約定的函數,則會甩出一個異常 ValueError
。反之亦然。
>>> cdll.kernel32.GetModuleHandleA(None) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Procedure probably called with not enough arguments (4 bytes missing) >>> >>> windll.msvcrt.printf(b"spam") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Procedure probably called with too many arguments (4 bytes in excess) >>>
你必須閱讀這些庫的頭文件或說明文檔來確定它們的正確的調用協議。
在 Windows 中,ctypes
使用 win32 結構化異常處理來防止由於在調用函數時使用非法參數導致的程序崩潰。
>>> windll.kernel32.GetModuleHandleA(32) Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: exception: access violation reading 0x00000020 >>>
然而,總有許多辦法,通過調用 ctypes
使得 Python 程序崩潰。因此,你必須小心使用。 faulthandler
模塊可以用於幫助診斷程序崩潰的原因。(比如由於錯誤的C庫函數調用導致的段錯誤)。
None
,整型,字節對象和(UNICODE)字符串是僅有的可以直接作為函數參數使用的四種Python本地數據類型。None` 作為C的空指針 (NULL
),字節和字符串類型作為一個指向其保存數據的內存塊指針 (char* 或 wchar_t*)。Python 的整型則作為平台默認的C的 int 類型,他們的數值被截斷以適應C類型的整型長度。
在我們開始調用函數前,我們必須先了解作為函數參數的 ctypes
數據類型。
基礎數據類型
ctypes
定義了一些和C兼容的基本數據類型:
ctypes 類型 |
C 類型 |
Python 類型 |
---|---|---|
_Bool |
bool (1) |
|
char |
單字符字節串對象 |
|
|
單字符字符串 |
|
char |
int |
|
unsigned char |
int |
|
short |
int |
|
unsigned short |
int |
|
int |
int |
|
unsigned int |
int |
|
long |
int |
|
unsigned long |
int |
|
|
int |
|
unsigned __int64 或 unsigned long long |
int |
|
|
int |
|
|
int |
|
float |
float |
|
double |
float |
|
long double |
float |
|
char* (以 NUL 結尾) |
字節串對象或 |
|
wchar_t* (以 NUL 結尾) |
字符串或 |
|
void* |
int 或 |
-
構造函數接受任何具有真值的對象。
所有這些類型都可以通過使用正確類型和值的可選初始值調用它們來創建:
>>> c_int() c_long(0) >>> c_wchar_p("Hello, World") c_wchar_p(140018365411392) >>> c_ushort(-3) c_ushort(65533) >>>
由於這些類型是可變的,它們的值也可以在以后更改:
>>> i = c_int(42) >>> print(i) c_long(42) >>> print(i.value) 42 >>> i.value = -99 >>> print(i.value) -99 >>>
當給指針類型的對象 c_char_p
, c_wchar_p
和 c_void_p
等賦值時,將改變它們所指向的 內存地址,而 不是 它們所指向的內存區域的 內容 (這是理所當然的,因為 Python 的 bytes 對象是不可變的):
>>> s = "Hello, World" >>> c_s = c_wchar_p(s) >>> print(c_s) c_wchar_p(139966785747344) >>> print(c_s.value) Hello World >>> c_s.value = "Hi, there" >>> print(c_s) # the memory location has changed c_wchar_p(139966783348904) >>> print(c_s.value) Hi, there >>> print(s) # first object is unchanged Hello, World >>>
但你要注意不能將它們傳遞給會改變指針所指內存的函數。如果你需要可改變的內存塊,ctypes 提供了 create_string_buffer()
函數,它提供多種方式創建這種內存塊。當前的內存塊內容可以通過 raw
屬性存取,如果你希望將它作為NUL結束的字符串,請使用 value
屬性:
>>> from ctypes import * >>> p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes >>> print(sizeof(p), repr(p.raw)) 3 b'\x00\x00\x00' >>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string >>> print(sizeof(p), repr(p.raw)) 6 b'Hello\x00' >>> print(repr(p.value)) b'Hello' >>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer >>> print(sizeof(p), repr(p.raw)) 10 b'Hello\x00\x00\x00\x00\x00' >>> p.value = b"Hi" >>> print(sizeof(p), repr(p.raw)) 10 b'Hi\x00lo\x00\x00\x00\x00\x00' >>>
create_string_buffer()
函數替代以前的ctypes版本中的 c_buffer()
函數 (仍然可當作別名使用)和 c_string()
函數。create_unicode_buffer()
函數創建包含 unicode 字符的可變內存塊,與之對應的C語言類型是 wchar_t
。
調用函數,繼續
注意 printf 將打印到真正標准輸出設備,而*不是* sys.stdout
,因此這些實例只能在控制台提示符下工作,而不能在 IDLE 或 PythonWin 中運行。
>>> printf = libc.printf >>> printf(b"Hello, %s\n", b"World!") Hello, World! 14 >>> printf(b"Hello, %S\n", "World!") Hello, World! 14 >>> printf(b"%d bottles of beer\n", 42) 42 bottles of beer 19 >>> printf(b"%f bottles of beer\n", 42.5) Traceback (most recent call last): File "<stdin>", line 1, in <module> ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2 >>>
正如前面所提到過的,除了整數、字符串以及字節串之外,所有的 Python 類型都必須使用它們對應的 ctypes
類型包裝,才能夠被正確地轉換為所需的C語言類型。
>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14)) An int 1234, a double 3.140000 31 >>>
使用自定義的數據類型調用函數
你也可以通過自定義 ctypes
參數轉換方式來允許自定義類型作為參數。 ctypes
會尋找 _as_parameter_
屬性並使用它作為函數參數。當然,它必須是數字、字符串或者二進制字符串:
>>> class Bottles: ... def __init__(self, number): ... self._as_parameter_ = number ... >>> bottles = Bottles(42) >>> printf(b"%d bottles of beer\n", bottles) 42 bottles of beer 19 >>>
如果你不想把實例的數據存儲到 _as_parameter_
屬性。可以通過定義 property
函數計算出這個屬性。
指定必選參數的類型(函數原型)
可以通過設置 argtypes
屬性的方法指定從 DLL 中導出函數的必選參數類型。
argtypes
必須是一個 C 數據類型的序列 (這里的 printf
可能不是個好例子,因為它是變長參數,而且每個參數的類型依賴於格式化字符串,不過嘗試這個功能也很方便):
>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double] >>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2) String 'Hi', Int 10, Double 2.200000 37 >>>
指定數據類型可以防止不合理的參數傳遞(就像 C 函數的原型),並且會自動嘗試將參數轉換為需要的類型:
>>> printf(b"%d %d %d", 1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> ArgumentError: argument 2: exceptions.TypeError: wrong type >>> printf(b"%s %d %f\n", b"X", 2, 3) X 2 3.000000 13 >>>
如果你想通過自定義類型傳遞參數給函數,必須實現 from_param()
類方法,才能夠將此自定義類型用於 argtypes
序列。from_param()
類方法接受一個 Python 對象作為函數輸入,它應該進行類型檢查或者其他必要的操作以保證接收到的對象是合法的,然后返回這個對象,或者它的 _as_parameter_
屬性,或者其他你想要傳遞給 C 函數的參數。這里也一樣,返回的結果必須是整型、字符串、二進制字符串、 ctypes
類型,或者一個具有 _as_parameter_
屬性的對象。
返回類型
默認情況下都會假定函數返回 C int 類型。 其他返回類型可以通過設置函數對象的 restype
屬性來指定。
這是個更高級的例子,它調用了 strchr
函數,這個函數接收一個字符串指針以及一個字符作為參數,返回另一個字符串指針。
>>> strchr = libc.strchr >>> strchr(b"abcdef", ord("d")) 8059983 >>> strchr.restype = c_char_p # c_char_p is a pointer to a string >>> strchr(b"abcdef", ord("d")) b'def' >>> print(strchr(b"abcdef", ord("x"))) None >>>
如果希望避免上述的 ord("x")
調用,可以設置 argtypes
屬性,第二個參數就會將單字符的 Python 二進制字符對象轉換為 C 字符:
>>> strchr.restype = c_char_p >>> strchr.argtypes = [c_char_p, c_char] >>> strchr(b"abcdef", b"d") 'def' >>> strchr(b"abcdef", b"def") Traceback (most recent call last): File "<stdin>", line 1, in <module> ArgumentError: argument 2: exceptions.TypeError: one character string expected >>> print(strchr(b"abcdef", b"x")) None >>> strchr(b"abcdef", b"d") 'def' >>>
如果外部函數返回了一個整數,你也可以使用要給可調用的 Python 對象(比如函數或者類)作為 restype
屬性的值。將會以 C 函數返回的 整數 對象作為參數調用這個可調用對象,執行后的結果作為最終函數返回值。這在錯誤返回值校驗和自動拋出異常等方面比較有用。
>>> GetModuleHandle = windll.kernel32.GetModuleHandleA >>> def ValidHandle(value): ... if value == 0: ... raise WinError() ... return value ... >>> >>> GetModuleHandle.restype = ValidHandle >>> GetModuleHandle(None) 486539264 >>> GetModuleHandle("something silly") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in ValidHandle OSError: [Errno 126] The specified module could not be found. >>>
WinError
函數可以調用 Windows 的 FormatMessage()
API 獲取錯誤碼的字符串說明,然后 返回 一個異常。 WinError
接收一個可選的錯誤碼作為參數,如果沒有的話,它將調用 GetLastError()
獲取錯誤碼。
請注意,使用 errcheck
屬性可以實現更強大的錯誤檢查手段;詳情請見參考手冊。
傳遞指針(或以引用方式傳遞形參)
有時候 C 函數接口可能由於要往某個地址寫入值,或者數據太大不適合作為值傳遞,從而希望接收一個 指針 作為數據參數類型。這和 傳遞參數引用 類似。
ctypes
暴露了 byref()
函數用於通過引用傳遞參數,使用 pointer()
函數也能達到同樣的效果,只不過 pointer()
需要更多步驟,因為它要先構造一個真實指針對象。所以在 Python 代碼本身不需要使用這個指針對象的情況下,使用 byref()
效率更高。
>>> i = c_int() >>> f = c_float() >>> s = create_string_buffer(b'\000' * 32) >>> print(i.value, f.value, repr(s.value)) 0 0.0 b'' >>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s", ... byref(i), byref(f), s) 3 >>> print(i.value, f.value, repr(s.value)) 1 3.1400001049 b'Hello' >>>
結構體和聯合
結構體和聯合必須繼承自 ctypes
模塊中的 Structure
和 Union
。子類必須定義 _fields_
屬性。 _fields_
是一個二元組列表,二元組中包含 field name 和 field type 。
type 字段必須是一個 ctypes
類型,比如 c_int
,或者其他 ctypes
類型: 結構體、聯合、數組、指針。
這是一個簡單的 POINT 結構體,它包含名稱為 x 和 y 的兩個變量,還展示了如何通過構造函數初始化結構體。
>>> from ctypes import * >>> class POINT(Structure): ... _fields_ = [("x", c_int), ... ("y", c_int)] ... >>> point = POINT(10, 20) >>> print(point.x, point.y) 10 20 >>> point = POINT(y=5) >>> print(point.x, point.y) 0 5 >>> POINT(1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: too many initializers >>>
當然,你可以構造更復雜的結構體。一個結構體可以通過設置 type 字段包含其他結構體或者自身。
這是以一個 RECT 結構體,他包含了兩個 POINT ,分別叫 upperleft 和 lowerright:
>>> class RECT(Structure): ... _fields_ = [("upperleft", POINT), ... ("lowerright", POINT)] ... >>> rc = RECT(point) >>> print(rc.upperleft.x, rc.upperleft.y) 0 5 >>> print(rc.lowerright.x, rc.lowerright.y) 0 0 >>>
嵌套結構體可以通過幾種方式構造初始化:
>>> r = RECT(POINT(1, 2), POINT(3, 4)) >>> r = RECT((1, 2), (3, 4))
可以通過 類 獲取字段 descriptor ,它能提供很多有用的調試信息。
>>> print(POINT.x) <Field type=c_long, ofs=0, size=4> >>> print(POINT.y) <Field type=c_long, ofs=4, size=4> >>>
警告
ctypes
不支持帶位域的結構體、聯合以值的方式傳給函數。這可能在 32 位 x86 平台上可以正常工作,但是對於一般情況,這種行為是未定義的。帶位域的結構體、聯合應該總是通過指針傳遞給函數。
結構體/聯合字段對齊及字節順序
默認情況下,結構體和聯合的字段與 C 的字節對齊是一樣的。也可以在定義子類的時候指定類的 _pack_
屬性來覆蓋這種行為。 它必須設置為一個正整數,表示字段的最大對齊字節。這和 MSVC 中的 #pragma pack(n)
功能一樣。
ctypes
中的結構體和聯合使用的是本地字節序。要使用非本地字節序,可以使用 BigEndianStructure
, LittleEndianStructure
, BigEndianUnion
, and LittleEndianUnion
作為基類。這些類不能包含指針字段。
結構體和聯合中的位域
結構體和聯合中是可以包含位域字段的。位域只能用於整型字段,位長度通過 _fields_
中的第三個參數指定:
>>> class Int(Structure): ... _fields_ = [("first_16", c_int, 16), ... ("second_16", c_int, 16)] ... >>> print(Int.first_16) <Field type=c_long, ofs=0:0, bits=16> >>> print(Int.second_16) <Field type=c_long, ofs=0:16, bits=16> >>>
數組
數組是一個序列,包含指定個數元素,且必須類型相同。
創建數組類型的推薦方式是使用一個類型乘以一個正數:
TenPointsArrayType = POINT * 10
下面是一個構造的數據案例,結構體中包含了4個 POINT 和一些其他東西。
>>> from ctypes import * >>> class POINT(Structure): ... _fields_ = ("x", c_int), ("y", c_int) ... >>> class MyStruct(Structure): ... _fields_ = [("a", c_int), ... ("b", c_float), ... ("point_array", POINT * 4)] >>> >>> print(len(MyStruct().point_array)) 4 >>>
和平常一樣,通過調用它創建實例:
arr = TenPointsArrayType() for pt in arr: print(pt.x, pt.y)
以上代碼會打印幾行 0 0
,因為數組內容被初始化為 0.
也能通過指定正確類型的數據來初始化:
>>> from ctypes import * >>> TenIntegers = c_int * 10 >>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) >>> print(ii) <c_long_Array_10 object at 0x...> >>> for i in ii: print(i, end=" ") ... 1 2 3 4 5 6 7 8 9 10 >>>
指針
可以將 ctypes
類型數據傳入 pointer()
函數創建指針:
>>> from ctypes import * >>> i = c_int(42) >>> pi = pointer(i) >>>
指針實例擁有 contents
屬性,它返回指針指向的真實對象,如上面的 i
對象:
>>> pi.contents c_long(42) >>>
注意 ctypes
並沒有 OOR (返回原始對象), 每次訪問這個屬性時都會構造返回一個新的相同對象:
>>> pi.contents is i False >>> pi.contents is pi.contents False >>>
將這個指針的 contents 屬性賦值為另一個 c_int
實例將會導致該指針指向該實例的內存地址:
>>> i = c_int(99) >>> pi.contents = i >>> pi.contents c_long(99) >>>
指針對象也可以通過整數下標進行訪問:
>>> pi[0] 99 >>>
通過整數下標賦值可以改變指針所指向的真實內容:
>>> print(i) c_long(99) >>> pi[0] = 22 >>> print(i) c_long(22) >>>
使用 0 以外的索引也是合法的,但是你必須確保知道自己為什么這么做,就像 C 語言中: 你可以訪問或者修改任意內存內容。 通常只會在函數接收指針是才會使用這種特性,而且你 知道 這個指針指向的是一個數組而不是單個值。
內部細節, pointer()
函數不只是創建了一個指針實例,它首先創建了一個指針 類型 。這是通過調用 POINTER()
函數實現的,它接收 ctypes
類型為參數,返回一個新的類型:
>>> PI = POINTER(c_int) >>> PI <class 'ctypes.LP_c_long'> >>> PI(42) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: expected c_long instead of int >>> PI(c_int(42)) <ctypes.LP_c_long object at 0x...> >>>
無參調用指針類型可以創建一個 NULL
指針。 NULL
指針的布爾值是 False
>>> null_ptr = POINTER(c_int)() >>> print(bool(null_ptr)) False >>>
解引用指針的時候, ctypes
會幫你檢測是否指針為 NULL
(但是解引用無效的 非 NULL
指針仍會導致 Python 崩潰):
>>> null_ptr[0] Traceback (most recent call last): .... ValueError: NULL pointer access >>> >>> null_ptr[0] = 1234 Traceback (most recent call last): .... ValueError: NULL pointer access >>>
類型轉換
通常情況下, ctypes 具有嚴格的類型檢查。這代表着, 如果在函數 argtypes
中或者結構體定義成員中有 POINTER(c_int)
類型,只有相同類型的實例才會被接受。 也有一些例外。比如,你可以傳遞兼容的數組實例給指針類型。所以,對於 POINTER(c_int)
,ctypes 也可以接受 c_int 類型的數組:
>>> class Bar(Structure): ... _fields_ = [("count", c_int), ("values", POINTER(c_int))] ... >>> bar = Bar() >>> bar.values = (c_int * 3)(1, 2, 3) >>> bar.count = 3 >>> for i in range(bar.count): ... print(bar.values[i]) ... 1 2 3 >>>
另外,如果一個函數 argtypes
列表中的參數顯式的定義為指針類型(如 POINTER(c_int)
),指針所指向的 類型 (這個例子中是 c_int
)也可以傳遞給函數。ctypes 會自動調用對應的 byref()
轉換。
可以給指針內容賦值為 None 將其設置為 Null
>>> bar.values = None >>>
有時候你擁有一個不兼容的類型。 在 C 中,你可以將一個類型強制轉換為另一個。 ctypes
中的 a cast()
函數提供了相同的功能。 上面的結構體 Bar
的 value
字段接收 POINTER(c_int)
指針或者 c_int
數組,但是不能接受其他類型的實例:
>>> bar.values = (c_byte * 4)() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance >>>
這種情況下, 需要手動使用 cast()
函數。
cast()
函數可以將一個指針實例強制轉換為另一種 ctypes 類型。 cast()
接收兩個參數,一個 ctypes 指針對象或者可以被轉換為指針的其他類型對象,和一個 ctypes 指針類型。 返回第二個類型的一個實例,該返回實例和第一個參數指向同一片內存空間:
>>> a = (c_byte * 4)() >>> cast(a, POINTER(c_int)) <ctypes.LP_c_long object at ...> >>>
所以 cast()
可以用來給結構體 Bar
的 values
字段賦值:
>>> bar = Bar() >>> bar.values = cast((c_byte * 4)(), POINTER(c_int)) >>> print(bar.values[0]) 0 >>>
不完整類型
不完整類型 即還沒有定義成員的結構體、聯合或者數組。在 C 中,它們通常用於前置聲明,然后在后面定義:
struct cell; /* forward declaration */ struct cell { char *name; struct cell *next; };
直接翻譯成 ctypes 的代碼如下,但是這行不通:
>>> class cell(Structure): ... _fields_ = [("name", c_char_p), ... ("next", POINTER(cell))] ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in cell NameError: name 'cell' is not defined >>>
因為新的 cell 類
在 class 語句結束之前還沒有完成定義。在 ctypes
中,我們可以先定義 cell
類,在 class 語句結束之后再設置 _fields_
屬性:
>>> from ctypes import * >>> class cell(Structure): ... pass ... >>> cell._fields_ = [("name", c_char_p), ... ("next", POINTER(cell))] >>>
讓我們試試。我們定義兩個 cell
實例,讓它們互相指向對方,然后通過指針鏈式訪問幾次:
>>> c1 = cell() >>> c1.name = b"foo" >>> c2 = cell() >>> c2.name = b"bar" >>> c1.next = pointer(c2) >>> c2.next = pointer(c1) >>> p = c1 >>> for i in range(8): ... print(p.name, end=" ") ... p = p.next[0] ... foo bar foo bar foo bar foo bar >>>
回調函數
ctypes
允許創建一個指向 Python 可調用對象的 C 函數。它們有時候被稱為 回調函數 。
首先,你必須為回調函數創建一個類,這個類知道調用約定,包括返回值類型以及函數接收的參數類型及個數。
CFUNCTYPE()
工廠函數使用 cdecl
調用約定創建回調函數類型。在 Windows 上, WINFUNCTYPE()
工廠函數使用 stdcall
調用約定為回調函數創建類型。
這些工廠函數的第一個參數是返回值類型,回調函數的參數類型作為剩余參數。
這里展示一個使用 C 標准庫函數 qsort()
的例子,它使用一個回調函數對數據進行排序。 qsort()
將用來給整數數組排序:
>>> IntArray5 = c_int * 5 >>> ia = IntArray5(5, 1, 7, 33, 99) >>> qsort = libc.qsort >>> qsort.restype = None >>>
qsort()
必須接收的參數,一個指向待排序數據的指針,元素個數,每個元素的大小,以及一個指向排序函數的指針,即回調函數。然后回調函數接收兩個元素的指針,如果第一個元素小於第二個,則返回一個負整數,如果相等則返回0,否則返回一個正整數。
所以,我們的回調函數要接收兩個整數指針,返回一個整數。首先我們創建回調函數的 類型
>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) >>>
首先,這是一個簡單的回調,它會顯示傳入的值:
>>> def py_cmp_func(a, b): ... print("py_cmp_func", a[0], b[0]) ... return 0 ... >>> cmp_func = CMPFUNC(py_cmp_func) >>>
結果:
>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) py_cmp_func 5 1 py_cmp_func 33 99 py_cmp_func 7 33 py_cmp_func 5 7 py_cmp_func 1 7 >>>
現在我們可以比較兩個元素並返回有用的結果了:
>>> def py_cmp_func(a, b): ... print("py_cmp_func", a[0], b[0]) ... return a[0] - b[0] ... >>> >>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) py_cmp_func 5 1 py_cmp_func 33 99 py_cmp_func 7 33 py_cmp_func 1 7 py_cmp_func 5 7 >>>
我們可以輕易地驗證,現在數組是有序的了:
>>> for i in ia: print(i, end=" ") ... 1 5 7 33 99 >>>
這些工廠函數可以當作裝飾器工廠,所以可以這樣寫:
>>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) ... def py_cmp_func(a, b): ... print("py_cmp_func", a[0], b[0]) ... return a[0] - b[0] ... >>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func) py_cmp_func 5 1 py_cmp_func 33 99 py_cmp_func 7 33 py_cmp_func 1 7 py_cmp_func 5 7 >>>
注解
請確保你維持的 CFUNCTYPE()
對象的引用周期與它們在 C 代碼中的使用期一樣長。 ctypes
不會確保這一點,如果不這樣做,它們可能會被垃圾回收,導致程序在執行回調函數時發生崩潰。
注意,如果回調函數在Python之外的另外一個線程使用(比如,外部代碼調用這個回調函數), ctypes 會在每一次調用上創建一個虛擬 Python 線程。這個行為在大多數情況下是合理的,但也意味着如果有數據使用 threading.local
方式存儲,將無法訪問,就算它們是在同一個 C 線程中調用的 。
訪問 dll 的導出變量
一些動態鏈接庫不僅僅導出函數,也會導出變量。一個例子就是 Python 庫本身的 Py_OptimizeFlag
,根據啟動選項 -O
、 -OO
的不同,它是值可能為 0、1、2 的整型。
ctypes
可以通過 in_dll()
類方法訪問這類變量 。 pythonapi 是用於訪問 Python C 接口的預定義符號:
>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag") >>> print(opt_flag) c_long(0) >>>
如果解釋器使用 -O
選項啟動,這個例子會打印 c_long(1)
, 如果使用 -OO
啟動,則會打印 c_long(2)
。
一個擴展例子, 同時也展示了使用指針訪問 Python 導出的 PyImport_FrozenModules
指針對象。
對文檔中這個值的解釋說明
該指針被初始化為指向 struct _frozen 數組,以
NULL
或者 0 作為結束標記。當一個凍結模塊被導入,首先要在這個表中搜索。第三方庫可以以此來提供動態創建的凍結模塊集合。
這足以證明修改這個指針是很有用的。為了讓實例大小不至於太長,這里只展示如何使用 ctypes
讀取這個表:
>>> from ctypes import * >>> >>> class struct_frozen(Structure): ... _fields_ = [("name", c_char_p), ... ("code", POINTER(c_ubyte)), ... ("size", c_int)] ... >>>
我們定義了 struct _frozen 數據類型,接着就可以獲取這張表的指針了:
>>> FrozenTable = POINTER(struct_frozen) >>> table = FrozenTable.in_dll(pythonapi, "PyImport_FrozenModules") >>>
由於 table
是指向 struct_frozen
數組的 指針
,我們可以遍歷它,只不過需要自己判斷循環是否結束,因為指針本身並不包含長度。它早晚會因為訪問到野指針或者什么的把自己搞崩潰,所以我們最好在遇到 NULL
后就讓它退出循環:
>>> for item in table: ... if item.name is None: ... break ... print(item.name.decode("ascii"), item.size) ... _frozen_importlib 31764 _frozen_importlib_external 41499 __hello__ 161 __phello__ -161 __phello__.spam 161 >>>
Python 的凍結模塊和凍結包(由負 size
成員表示)並不是廣為人知的事情,它們僅僅用於實驗。例如,可以使用 import __hello__
嘗試一下這個功能。
意外
ctypes
也有自己的邊界,有時候會發生一些意想不到的事情。
比如下面的例子:
>>> from ctypes import * >>> class POINT(Structure): ... _fields_ = ("x", c_int), ("y", c_int) ... >>> class RECT(Structure): ... _fields_ = ("a", POINT), ("b", POINT) ... >>> p1 = POINT(1, 2) >>> p2 = POINT(3, 4) >>> rc = RECT(p1, p2) >>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y) 1 2 3 4 >>> # now swap the two points >>> rc.a, rc.b = rc.b, rc.a >>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y) 3 4 3 4 >>>
嗯。我們預想應該打印 3 4 1 2
。但是為什么呢? 這是 rc.a, rc.b = rc.b, rc.a
這行代碼展開后的步驟:
>>> temp0, temp1 = rc.b, rc.a >>> rc.a = temp0 >>> rc.b = temp1 >>>
注意 temp0
和 temp1
對象始終引用了對象 rc
的內容。然后執行 rc.a = temp0
會把 temp0
的內容拷貝到 rc
的空間。這也改變了 temp1
的內容。最終導致賦值語句 rc.b = temp1
沒有產生預想的效果。
記住,訪問被包含在結構體、聯合、數組中的對象並不會將其 復制 出來,而是得到了一個代理對象,它是對根對象的內部內容的一層包裝。
下面是另一個可能和預期有偏差的例子:
>>> s = c_char_p() >>> s.value = b"abc def ghi" >>> s.value b'abc def ghi' >>> s.value is s.value False >>>
注解
使用 c_char_p
實例化的對象只能將其值設置為 bytes 或者整數。
為什么這里打印了 False
? ctypes 實例是一些內存塊加上一些用於訪問這些內存塊的 descriptor 組成。將 Python 對象存儲在內存塊並不會存儲對象本身,而是存儲了對象的 內容
。每次訪問對象的內容都會構造一個新的 Python 對象。
變長數據類型
ctypes
對變長數組和結構體提供了一些支持 。
The resize()
function can be used to resize the memory buffer of an existing ctypes object. The function takes the object as first argument, and the requested size in bytes as the second argument. The memory block cannot be made smaller than the natural memory block specified by the objects type, a ValueError
is raised if this is tried:
>>> short_array = (c_short * 4)() >>> print(sizeof(short_array)) 8 >>> resize(short_array, 4) Traceback (most recent call last): ... ValueError: minimum size is 8 >>> resize(short_array, 32) >>> sizeof(short_array) 32 >>> sizeof(type(short_array)) 8 >>>
這非常好,但是要怎么訪問數組中額外的元素呢?因為數組類型已經定義包含4個元素,導致我們訪問新增元素時會產生以下錯誤:
>>> short_array[:] [0, 0, 0, 0] >>> short_array[7] Traceback (most recent call last): ... IndexError: invalid index >>>
使用 ctypes
訪問變長數據類型的一個可行方法是利用 Python 的動態特性,根據具體情況,在知道這個數據的大小后,(重新)指定這個數據的類型。
ctypes 參考手冊
外部函數
正如之前小節的說明,外部函數可作為被加載共享庫的屬性來訪問。 用此方式創建的函數對象默認接受任意數量的參數,接受任意 ctypes 數據實例作為參數,並且返回庫加載器所指定的默認結果類型。 它們是一個私有類的實例:
-
class
ctypes.
_FuncPtr
-
C 可調用外部函數的基類。
外部函數的實例也是兼容 C 的數據類型;它們代表 C 函數指針。
此行為可通過對外部函數對象的特殊屬性賦值來自定義。
-
restype
-
賦值為一個 ctypes 類型來指定外部函數的結果類型。 使用
None
表示 void,即不返回任何結果的函數。賦值為一個不為 ctypes 類型的可調用 Python 對象也是可以的,在此情況下函數應返回 C int,該可調用對象將附帶此整數被調用,以允許進一步的處理或錯誤檢測。 這種用法已被棄用,為了更靈活的后續處理或錯誤檢測請使用一個 ctypes 數據類型作為
restype
並將errcheck
屬性賦值為一個可調用對象。
-
argtypes
-
賦值為一個 ctypes 類型的元組來指定函數所接受的參數類型。 使用
stdcall
調用規范的函數只能附帶與此元組長度相同數量的參數進行調用;使用 C 調用規范的函數還可接受額外的未指明參數。當外部函數被調用時,每個實際參數都會被傳給
argtypes
元組中條目的from_param()
類方法,此方法允許將實際參數適配為此外部函數所接受的對象。 例如,argtypes
元組中的c_char_p
條目將使用 ctypes 約定規則把作為參數傳入的字符串轉換為字節串對象。新增:現在可以將不是 ctypes 類型的條目放入 argtypes,但每個條目都必須具有
from_param()
方法用於返回可作為參數的值(整數、字符串、ctypes 實例)。 這樣就允許定義可將自定義對象適配為函數形參的適配器。
-
errcheck
-
將一個 Python 函數或其他可調用對象賦值給此屬性。 該可調用對象將附帶三個及以上的參數被調用。
-
callable
(result, func, arguments) -
result 是外部函數返回的結果,由
restype
屬性指明。func 是外部函數對象本身,這樣就允許重新使用相同的可調用對象來對多個函數進行檢查或后續處理。
arguments 是一個包含最初傳遞給函數調用的形參的元組,這樣就允許對所用參數的行為進行特別處理。
此函數所返回的對象將會由外部函數調用返回,但它還可以在外部函數調用失敗時檢查結果並引發異常。
-
-
-
exception
ctypes.
ArgumentError
-
此異常會在外部函數無法對某個傳入參數執行轉換時被引發。
引發一個審計事件 ctypes.seh_exception
並附帶參數 code
。
引發一個審計事件 ctypes.call_function
,附帶參數 func_pointer
, arguments
。
函數原型
外部函數也可通過實例化函數原型來創建。 函數原型類似於 C 中的函數原型;它們在不定義具體實現的情況下描述了一個函數(返回類型、參數類型、調用約定)。 工廠函數必須使用函數所需要的結果類型和參數類型來調用,並可被用作裝飾器工廠函數,在此情況下可以通過 @wrapper
語法應用於函數。 請參閱 回調函數 了解有關示例。
-
ctypes.
CFUNCTYPE
(restype, *argtypes, use_errno=False, use_last_error=False) -
返回的函數原型會創建使用標准 C 調用約定的函數。 該函數在調用過程中將釋放 GIL。 如果 use_errno 設為真值,則在調用之前和之后系統
errno
變量的 ctypes 私有副本會與真正的errno
值進行交換;use_last_error 會為 Windows 錯誤碼執行同樣的操作。
-
ctypes.
WINFUNCTYPE
(restype, *argtypes, use_errno=False, use_last_error=False) -
僅限 Windows only:返回的函數原型會創建使用
stdcall
調用約定的函數,但在 Windows CE 上WINFUNCTYPE()
則會與CFUNCTYPE()
相同。 該函數在調用過程中將釋放 GIL。 use_errno 和 use_last_error 具有與前面相同的含義。
-
ctypes.
PYFUNCTYPE
(restype, *argtypes) -
返回的函數原型會創建使用 Python 調用約定的函數。 該函數在調用過程中將 不會 釋放 GIL。
這些工廠函數所創建的函數原型可通過不同的方式來實例化,具體取決於調用中的類型與數量:
prototype
(address)在指定地址上返回一個外部函數,地址值必須為整數。
prototype
(callable)基於 Python callable 創建一個 C 可調用函數(回調函數)。
prototype
(func_spec[, paramflags])返回由一個共享庫導出的外部函數。 func_spec 必須為一個 2 元組
(name_or_ordinal, library)
。 第一項是字符串形式的所導出函數名稱,或小整數形式的所導出函數序號。 第二項是該共享庫實例。
prototype
(vtbl_index, name[, paramflags[, iid]])返回將調用一個 COM 方法的外部函數。 vtbl_index 虛擬函數表中的索引。 name 是 COM 方法的名稱。 iid 是可選的指向接口標識符的指針,它被用於擴展的錯誤報告。
COM 方法使用特殊的調用約定:除了在
argtypes
元組中指定的形參,它們還要求一個指向 COM 接口的指針作為第一個參數。可選的 paramflags 形參會創建相比上述特性具有更多功能的外部函數包裝器。
paramflags 必須為一個與
argtypes
長度相同的元組。此元組中的每一項都包含有關形參的更多信息,它必須為包含一個、兩個或更多條目的元組。
第一項是包含形參指令旗標組合的整數。
- 1
指定函數的一個輸入形參。
- 2
輸出形參。 外部函數會填入一個值。
- 4
默認為整數零值的輸入形參。
可選的第二項是字符串形式的形參名稱。 如果指定此項,則可以使用該形參名稱來調用外部函數。
可選的第三項是該形參的默認值。
這個例子演示了如何包裝 Windows 的 MessageBoxW
函數以使其支持默認形參和已命名參數。 相應 windows 頭文件的 C 聲明是這樣的:
WINUSERAPI int WINAPI MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
這是使用 ctypes
的包裝:
>>> from ctypes import c_int, WINFUNCTYPE, windll >>> from ctypes.wintypes import HWND, LPCWSTR, UINT >>> prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT) >>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0) >>> MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)
現在 MessageBox
外部函數可以通過以下方式來調用:
>>> MessageBox() >>> MessageBox(text="Spam, spam, spam") >>> MessageBox(flags=2, text="foo bar")
第二個例子演示了輸出形參。 這個 win32 GetWindowRect
函數通過將指定窗口的維度拷貝至調用者必須提供的 RECT
結構體來提取這些值。 這是相應的 C 聲明:
WINUSERAPI BOOL WINAPI GetWindowRect( HWND hWnd, LPRECT lpRect);
這是使用 ctypes
的包裝:
>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError >>> from ctypes.wintypes import BOOL, HWND, RECT >>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT)) >>> paramflags = (1, "hwnd"), (2, "lprect") >>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags) >>>
帶有輸出形參的函數如果輸出形參存在單一值則會自動返回該值,或是當輸出形參存在多個值時返回包含這些值的元組,因此當 GetWindowRect 被調用時現在將返回一個 RECT 實例。
輸出形參可以與 errcheck
協議相結合以執行進一步的輸出處理與錯誤檢查。 Win32 GetWindowRect
API 函數返回一個 BOOL
來表示成功或失敗,因此此函數可執行錯誤檢查,並在 API 調用失敗時引發異常:
>>> def errcheck(result, func, args): ... if not result: ... raise WinError() ... return args ... >>> GetWindowRect.errcheck = errcheck >>>
如果 errcheck
不加更改地返回它所接收的參數元組,則 ctypes
會繼續對輸出形參執行常規處理。 如果你希望返回一個窗口坐標的元組而非 RECT
實例,你可以從函數中提取這些字段並返回它們,常規處理將不會再執行:
>>> def errcheck(result, func, args): ... if not result: ... raise WinError() ... rc = args[1] ... return rc.left, rc.top, rc.bottom, rc.right ... >>> GetWindowRect.errcheck = errcheck >>>
工具函數
-
ctypes.
addressof
(obj) -
以整數形式返回內存緩沖區地址。 obj 必須為一個 ctypes 類型的實例。
引發一個 審計事件
ctypes.addressof
,附帶參數obj
。
-
ctypes.
alignment
(obj_or_type) -
返回一個 ctypes 類型的對齊要求。 obj_or_type 必須為一個 ctypes 類型或實例。
-
ctypes.
byref
(obj[, offset]) -
返回指向 obj 的輕量指針,該對象必須為一個 ctypes 類型的實例。 offset 默認值為零,且必須為一個將被添加到內部指針值的整數。
byref(obj, offset)
對應於這段 C 代碼:(((char *)&obj) + offset)
返回的對象只能被用作外部函數調用形參。 它的行為類似於
pointer(obj)
,但構造起來要快很多。
-
ctypes.
cast
(obj, type) -
此函數類似於 C 的強制轉換運算符。 它返回一個 type 的新實例,該實例指向與 obj 相同的內存塊。 type 必須為指針類型,而 obj 必須為可以被作為指針來解讀的對象。
-
ctypes.
create_string_buffer
(init_or_size, size=None) -
此函數會創建一個可變的字符緩沖區。 返回的對象是一個
c_char
的 ctypes 數組。init_or_size 必須是一個指明數組大小的整數,或者是一個將被用來初始化數組條目的字節串對象。
如果將一個字節串對象指定為第一個參數,則將使緩沖區大小比其長度多一項以便數組的最后一項為一個 NUL 終結符。 可以傳入一個整數作為第二個參數以允許在不使用字節串長度的情況下指定數組大小。
引發一個 審計事件
ctypes.create_string_buffer
,附帶參數init
,size
。
-
ctypes.
create_unicode_buffer
(init_or_size, size=None) -
此函數會創建一個可變的 unicode 字符緩沖區。 返回的對象是一個
c_wchar
的 ctypes 數組。init_or_size 必須是一個指明數組大小的整數,或者是一個將被用來初始化數組條目的字符串。
如果將一個字符串指定為第一個參數,則將使緩沖區大小比其長度多一項以便數組的最后一項為一個 NUL 終結符。 可以傳入一個整數作為第二個參數以允許在不使用字符串長度的情況下指定數組大小。
引發一個 審計事件
ctypes.create_unicode_buffer
,附帶參數init
,size
。
-
ctypes.
DllCanUnloadNow
() -
僅限 Windows:此函數是一個允許使用 ctypes 實現進程內 COM 服務的鈎子。 它將由 _ctypes 擴展 dll 所導出的 DllCanUnloadNow 函數來調用。
-
ctypes.
DllGetClassObject
() -
僅限 Windows:此函數是一個允許使用 ctypes 實現進程內 COM 服務的鈎子。 它將由
_ctypes
擴展 dll 所導出的 DllGetClassObject 函數來調用。
-
ctypes.util.
find_library
(name) -
嘗試尋找一個庫並返回路徑名稱。 name 是庫名稱並且不帶任何前綴如
lib
以及后綴如.so
,.dylib
或版本號(形式與 posix 鏈接器選項-l
所用的一致)。 如果找不到庫,則返回None
。確切的功能取決於系統。
-
ctypes.util.
find_msvcrt
() -
僅限 Windows:返回 Python 以及擴展模塊所使用的 VC 運行時庫的文件名。 如果無法確定庫名稱,則返回
None
。如果你需要通過調用
free(void *)
來釋放內存,例如某個擴展模塊所分配的內存,重要的一點是你應當使用分配內存的庫中的函數。
-
ctypes.
FormatError
([code]) -
僅限 Windows:返回錯誤碼 code 的文本描述。 如果未指定錯誤碼,則會通過調用 Windows api 函數 GetLastError 來獲得最新的錯誤碼。
-
ctypes.
GetLastError
() -
僅限 Windows:返回 Windows 在調用線程中設置的最新錯誤碼。 此函數會直接調用 Windows GetLastError() 函數,它並不返回錯誤碼的 ctypes 私有副本。
-
ctypes.
get_last_error
() -
僅限 Windows:返回調用線程中系統
LastError
變量的 ctypes 私有副本的當前值。引發一個 審計事件
ctypes.get_last_error
,不附帶任何參數。
-
ctypes.
memmove
(dst, src, count) -
與標准 C memmove 庫函數相同:將 count 個字節從 src 拷貝到 dst。 dst 和 src 必須為整數或可被轉換為指針的 ctypes 實例。
-
ctypes.
memset
(dst, c, count) -
與標准 C memset 庫函數相同:將位於地址 dst 的內存塊用 count 個字節的 c 值填充。 dst 必須為指定地址的整數或 ctypes 實例。
-
ctypes.
POINTER
(type) -
這個工廠函數創建並返回一個新的 ctypes 指針類型。 指針類型會被緩存並在內部重用,因此重復調用此函數耗費不大。 type 必須為 ctypes 類型。
-
ctypes.
pointer
(obj) -
此函數會創建一個新的指向 obj 的指針實例。 返回的對象類型為
POINTER(type(obj))
。注意:如果你只是想向外部函數調用傳遞一個對象指針,你應當使用更為快速的
byref(obj)
。
-
ctypes.
resize
(obj, size) -
此函數可改變 obj 的內部內存緩沖區大小,其參數必須為 ctypes 類型的實例。 沒有可能將緩沖區設為小於對象類型的本機大小值,該值由
sizeof(type(obj))
給出,但將緩沖區加大則是可能的。
-
ctypes.
set_errno
(value) -
設置調用線程中系統
errno
變量的 ctypes 私有副本的當前值為 value 並返回原來的值。引發一個 審計事件
ctypes.set_errno
附帶參數errno
。
-
ctypes.
set_last_error
(value) -
僅限 Windows:設置調用線程中系統
LastError
變量的 ctypes 私有副本的當前值為 value 並返回原來的值。引發一個 審計事件
ctypes.set_last_error
,附帶參數error
。
-
ctypes.
sizeof
(obj_or_type) -
返回 ctypes 類型或實例的內存緩沖區以字節表示的大小。 其功能與 C
sizeof
運算符相同。
-
ctypes.
string_at
(address, size=- 1) -
此函數返回從內存地址 address 開始的以字節串表示的 C 字符串。 如果指定了 size,則將其用作長度,否則將假定字符串以零值結尾。
引發一個 審計事件
ctypes.string_at
,附帶參數address
,size
。
-
ctypes.
WinError
(code=None, descr=None) -
僅限 Windows:此函數可能是 ctypes 中名字起得最差的函數。 它會創建一個 OSError 的實例。 如果未指定 code,則會調用
GetLastError
來確定錯誤碼。 如果未指定 descr,則會調用FormatError()
來獲取錯誤的文本描述。在 3.3 版更改: 以前是會創建一個
WindowsError
的實例。
-
ctypes.
wstring_at
(address, size=- 1) -
此函數返回從內存地址 address 開始的以字符串表示的寬字節字符串。 如果指定了 size,則將其用作字符串中的字符數量,否則將假定字符串以零值結尾。
引發一個 審計事件
ctypes.wstring_at
,附帶參數address
,size
。
數據類型
-
class
ctypes.
_CData
-
這個非公有類是所有 ctypes 數據類型的共同基類。 另外,所有 ctypes 類型的實例都包含一個存放 C 兼容數據的內存塊;該內存塊的地址可由
addressof()
輔助函數返回。 還有一個實例變量被公開為_objects
;此變量包含其他在內存塊包含指針的情況下需要保持存活的 Python 對象。ctypes 數據類型的通用方法,它們都是類方法(嚴謹地說,它們是 metaclass 的方法):
-
from_buffer
(source[, offset]) -
此方法返回一個共享 source 對象緩沖區的 ctypes 實例。 source 對象必須支持可寫緩沖區接口。 可選的 offset 形參指定以字節表示的源緩沖區內偏移量;默認值為零。 如果源緩沖區不夠大則會引發
ValueError
。引發一個 審計事件
ctypes.cdata/buffer
附帶參數pointer
,size
,offset
。
-
from_buffer_copy
(source[, offset]) -
此方法創建一個 ctypes 實例,從 source 對象緩沖區拷貝緩沖區,該對象必須是可讀的。 可選的 offset 形參指定以字節表示的源緩沖區內偏移量;默認值為零。 如果源緩沖區不夠大則會引發
ValueError
。引發一個 審計事件
ctypes.cdata/buffer
附帶參數pointer
,size
,offset
。
-
from_address
(address) -
此方法會使用 address 所指定的內存返回一個 ctypes 類型的實例,該參數必須為一個整數。
引發一個 審計事件
ctypes.cdata
,附帶參數address
。
-
from_param
(obj) -
此方法會將 obj 適配為一個 ctypes 類型。 它調用時會在當該類型存在於外部函數的
argtypes
元組時傳入外部函數調用所使用的實際對象;它必須返回一個可被用作函數調用參數的對象。所有 ctypes 數據類型都帶有這個類方法的默認實現,它通常會返回 obj,如果該對象是此類型的實例的話。 某些類型也能接受其他對象。
-
in_dll
(library, name) -
此方法返回一個由共享庫導出的 ctypes 類型。 name 為導出數據的符號名稱,library 為所加載的共享庫。
ctypes 數據類型的通用實例變量:
-
_b_base_
-
有時 ctypes 數據實例並不擁有它們所包含的內存塊,它們只是共享了某個基對象的部分內存塊。
_b_base_
只讀成員是擁有內存塊的根 ctypes 對象。
-
_b_needsfree_
-
這個只讀變量在 ctypes 數據實例自身已分配了內存塊時為真值,否則為假值。
-
_objects
-
這個成員或者為
None
,或者為一個包含需要保持存活以使內存塊的內存保持有效的 Python 對象的字典。 這個對象只是出於調試目的而對外公開;絕對不要修改此字典的內容。
-
基礎數據類型
-
class
ctypes.
_SimpleCData
-
這個非公有類是所有基本 ctypes 數據類型的基類。 它在這里被提及是因為它包含基本 ctypes 數據類型共有的屬性。
_SimpleCData
是_CData
的子類,因此繼承了其方法和屬性。 非指針及不包含指針的 ctypes 數據類型現在將可以被封存。實例擁有一個屬性:
-
value
-
這個屬性包含實例的實際值。 對於整數和指針類型,它是一個整數,對於字符類型,它是一個單字符字符串對象或字符串,對於字符指針類型,它是一個 Python 字節串對象或字符串。
當從 ctypes 實例提取
value
屬性時,通常每次會返回一個新的對象。ctypes
並 沒有 實現原始對象返回,它總是會構造一個新的對象。 所有其他 ctypes 對象實例也同樣如此。
-
基本數據類型當作為外部函數調用結果被返回或者作為結構字段成員或數組項被提取時,會透明地轉換為原生 Python 類型。 換句話說,如果某個外部函數具有 c_char_p
的 restype
,你將總是得到一個 Python 字節串對象,而 不是 一個 c_char_p
實例。
基本數據類型的子類並 沒有 繼續此行為。 因此,如果一個外部函數的 restype
是 c_void_p
的一個子類,你將從函數調用得到一個該子類的實例。 當然,你可以通過訪問 value
屬性來獲取指針的值。
這些是基本 ctypes 數據類型:
-
class
ctypes.
c_byte
-
代表 C signed char 數據類型,並將值解讀為一個小整數。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_char
-
代表 C char 數據類型,並將值解讀為單個字符。 該構造器接受一個可選的字符串初始化器,字符串的長度必須恰好為一個字符。
-
class
ctypes.
c_char_p
-
當指向一個以零為結束符的字符串時代表 C char* 數據類型。 對於通用字符指針來說也可能指向二進制數據,必須要使用
POINTER(c_char)
。 該構造器接受一個整數地址,或者一個字節串對象。
-
class
ctypes.
c_double
-
代表 C double 數據類型。 該構造器接受一個可選的浮點數初始化器。
-
class
ctypes.
c_longdouble
-
代表 C long double 數據類型。 該構造器接受一個可選的浮點數初始化器。 在
sizeof(long double) == sizeof(double)
的平台上它是c_double
的一個別名。
-
class
ctypes.
c_float
-
代表 C float 數據類型。 該構造器接受一個可選的浮點數初始化器。
-
class
ctypes.
c_int
-
代表 C signed int 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。 在
sizeof(int) == sizeof(long)
的平台上它是c_long
的一個別名。
-
class
ctypes.
c_int8
-
代表 C 8 位 signed int 數據類型。 通常是
c_byte
的一個別名。
-
class
ctypes.
c_int16
-
代表 C 16 位 signed int 數據類型。 通常是
c_short
的一個別名。
-
class
ctypes.
c_int32
-
代表 C 32 位 signed int 數據類型。 通常是
c_int
的一個別名。
-
class
ctypes.
c_int64
-
代表 C 64 位 signed int 數據類型。 通常是
c_longlong
的一個別名。
-
class
ctypes.
c_long
-
代表 C signed long 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_longlong
-
代表 C signed long long 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_short
-
代表 C signed short 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_size_t
-
代表 C
size_t
數據類型。
-
class
ctypes.
c_ssize_t
-
代表 C
ssize_t
數據類型。3.2 新版功能.
-
class
ctypes.
c_ubyte
-
代表 C unsigned char 數據類型,它將值解讀為一個小整數。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_uint
-
代表 C unsigned int 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。 在
sizeof(int) == sizeof(long)
的平台上它是c_ulong
的一個別名。
-
class
ctypes.
c_uint8
-
代表 C 8 位 unsigned int 數據類型。 通常是
c_ubyte
的一個別名。
-
class
ctypes.
c_uint16
-
代表 C 16 位 unsigned int 數據類型。 通常是
c_ushort
的一個別名。
-
class
ctypes.
c_uint32
-
代表 C 32 位 unsigned int 數據類型。 通常是
c_uint
的一個別名。
-
class
ctypes.
c_uint64
-
代表 C 64 位 unsigned int 數據類型。 通常是
c_ulonglong
的一個別名。
-
class
ctypes.
c_ulong
-
代表 C unsigned long 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_ulonglong
-
代表 C unsigned long long 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_ushort
-
代表 C unsigned short 數據類型。 該構造器接受一個可選的整數初始化器;不會執行溢出檢查。
-
class
ctypes.
c_void_p
-
代表 C void* 類型。 該值被表示為整數形式。 該構造器接受一個可選的整數初始化器。
-
class
ctypes.
c_wchar
-
代表 C
wchar_t
數據類型,並將值解讀為一單個字符的 unicode 字符串。 該構造器接受一個可選的字符串初始化器,字符串的長度必須恰好為一個字符。
-
class
ctypes.
c_wchar_p
-
代表 C wchar_t* 數據類型,它必須為指向以零為結束符的寬字符串的指針。 該構造器接受一個整數地址或者一個字符串。
-
class
ctypes.
c_bool
-
代表 C bool 數據類型 (更准確地說是 C99 _Bool)。 它的值可以為
True
或False
,並且該構造器接受任何具有邏輯值的對象。
-
class
ctypes.
HRESULT
-
Windows 專屬:代表一個
HRESULT
值,它包含某個函數或方法調用的成功或錯誤信息。
ctypes.wintypes
模塊提供了其他許多 Windows 專屬的數據類型,例如 HWND
, WPARAM
或 DWORD
。 還定義了一些有用的結構體例如 MSG
或 RECT
。
結構化數據類型
-
class
ctypes.
Union
(*args, **kw) -
本機字節序的聯合所對應的抽象基類。
-
class
ctypes.
BigEndianStructure
(*args, **kw) -
大端 字節序的結構體所對應的抽象基類。
-
class
ctypes.
LittleEndianStructure
(*args, **kw) -
小端 字節序的結構體所對應的抽象基類。
非本機字節序的結構體不能包含指針類型字段,或任何其他包含指針類型字段的數據類型。
-
class
ctypes.
Structure
(*args, **kw) -
本機 字節序的結構體所對應的抽象基類。
實際的結構體和聯合類型必須通過子類化這些類型之一來創建,並且至少要定義一個
_fields_
類變量。ctypes
將創建 descriptor,它允許通過直接屬性訪問來讀取和寫入字段。 這些是-
_fields_
-
一個定義結構體字段的序列。 其中的條目必須為 2 元組或 3 元組。 元組的第一項是字段名稱,第二項指明字段類型;它可以是任何 ctypes 數據類型。
對於整數類型字段例如
c_int
,可以給定第三個可選項。 它必須是一個定義字段比特位寬度的小正整數。字段名稱在一個結構體或聯合中必須唯一。 不會檢查這個唯一性,但當名稱出現重復時將只有一個字段可被訪問。
可以在定義 Structure 子類的類語句 之后 再定義
_fields_
類變量,這將允許創建直接或間接引用其自身的數據類型:class List(Structure): pass List._fields_ = [("pnext", POINTER(List)), ... ]
但是,
_fields_
類變量必須在類型第一次被使用(創建實例,調用sizeof()
等等)之前進行定義。 在此之后對_fields_
類變量賦值將會引發 AttributeError。可以定義結構體類型的子類,它們會繼承基類的字段再加上在子類中定義的任何
_fields_
。
-
_anonymous_
-
一個可選的序列,它會列出未命名(匿名)字段的名稱。 當
_fields_
被賦值時必須已經定義了_anonymous_
,否則它將沒有效果。在此變量中列出的字段必須為結構體或聯合類型字段。
ctypes
將在結構體類型中創建描述器以允許直接訪問嵌套字段,而無需創建對應的結構體或聯合字段。以下是一個示例類型(Windows):
class _U(Union): _fields_ = [("lptdesc", POINTER(TYPEDESC)), ("lpadesc", POINTER(ARRAYDESC)), ("hreftype", HREFTYPE)] class TYPEDESC(Structure): _anonymous_ = ("u",) _fields_ = [("u", _U), ("vt", VARTYPE)]
TYPEDESC
結構體描述了一個 COM 數據類型,vt
字段指明哪個聯合字段是有效的。 由於u
字段被定義為匿名字段,現在可以直接從 TYPEDESC 實例訪問成員。td.lptdesc
和td.u.lptdesc
是等價的,但前者速度更快,因為它不需要創建臨時的聯合實例:td = TYPEDESC() td.vt = VT_PTR td.lptdesc = POINTER(some_type) td.u.lptdesc = POINTER(some_type)
可以定義結構體的子類,它們會繼承基類的字段。 如果子類定義具有單獨的
_fields_
變量,在其中指定的字段會被添加到基類的字段中。結構體和聯合的構造器均可接受位置和關鍵字參數。 位置參數用於按照
_fields_
中的出現順序來初始化成員字段。 構造器中的關鍵字參數會被解讀為屬性賦值,因此它們將以相應的名稱來初始化_fields_
,或為不存在於_fields_
中的名稱創建新的屬性。 -