ctypes
— Python 的外部函式庫¶
原始碼: Lib/ctypes
ctypes
是 Python 的一個外部函式庫。它提供了與 C 語言相容的資料型別,並允許呼叫 DLL 或共享庫中的函式。可使用該模組以純 Python 方式對這些庫進行封裝。
ctypes 教程¶
注意:本教程中的程式碼示例使用 doctest
來確保它們實際可用。由於某些程式碼示例在 Linux、Windows 或 macOS 下的行為不同,它們在註釋中包含了 doctest 指令。
注意:一些程式碼示例引用了 ctypes 的 c_int
型別。在 sizeof(long) == sizeof(int)
的平臺上,它是 c_long
的別名。因此,如果你期望看到 c_int
卻打印出了 c_long
,不必感到困惑——它們實際上是同一個型別。
載入動態連結庫¶
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 上,載入庫時必須指定包括副檔名在內的檔名,因此不能使用屬性訪問來載入庫。應使用 DLL 載入器的 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 中訪問函式¶
函式作為 DLL 物件的屬性來訪問。
>>> 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
>>>
請注意,像 kernel32
和 user32
這樣的 win32 系統 DLL 通常會匯出 ANSI 和 UNICODE 兩個版本的函式。UNICODE 版本的函式名後會附加一個 W
,而 ANSI 版本的函式名後會附加一個 A
。win32 的 GetModuleHandle
函式,它為一個給定的模組名返回一個*模組控制代碼*,具有以下 C 原型,並使用一個宏來根據是否定義了 UNICODE 來將其中的一個暴露為 GetModuleHandle
。
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll 不會嘗試透過魔法來選擇其中之一,你必須透過明確指定 GetModuleHandleA
或 GetModuleHandleW
來訪問你需要的版本,然後分別用位元組串或字串物件來呼叫它。
有時,DLL 匯出的函式名稱不是有效的 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 可呼叫物件一樣呼叫這些函式。這個例子使用了 rand()
函式,它不接受任何引數並返回一個偽隨機整數。
>>> print(libc.rand())
1804289383
在 Windows 上,你可以呼叫 GetModuleHandleA()
函式,它返回一個 win32 模組控制代碼(傳遞 None
作為單個引數以 NULL
指標呼叫它)。
>>> 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)
>>>
要找出正確的呼叫約定,你需要檢視 C 標頭檔案或你想要呼叫的函式的文件。
在 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 |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
unsigned int |
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
long |
int |
|
unsigned long |
int |
|
__int64 或 long long |
int |
|
unsigned __int64 或 unsigned long long |
int |
|
|
int |
|
|
int |
|
|
int |
|
浮點數 |
浮點數 |
|
double |
浮點數 |
|
long double |
浮點數 |
|
char* (NUL 結尾) |
位元組物件或 |
|
wchar_t* (NUL 結尾) |
字串或 |
|
void* |
整數或 |
建構函式接受任何具有真值的物件。
此外,如果 C 和 libffi
都支援符合 IEC 60559 標準的複數運算(附件 G),則可以使用以下複數型別:
ctypes 型別 |
C 型別 |
Python 型別 |
---|---|---|
float complex |
complex |
|
double complex |
complex |
|
long double complex |
complex |
所有這些型別都可以透過呼叫它們並帶有一個可選的、型別和值都正確的初始化器來建立。
>>> 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 字串物件是不可變的)。
>>> 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()
函式取代了舊的 c_buffer()
函式(它仍然作為一個別名存在)。要建立一個包含 C 型別為 wchar_t
的 unicode 字元的可變記憶體塊,請使用 create_unicode_buffer()
函式。
呼叫函式,續¶
請注意,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>
ctypes.ArgumentError: argument 2: 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 呼叫可變引數函式與呼叫具有固定數量引數的函式完全相同。在某些平臺上,特別是蘋果平臺的 ARM64,可變引數函式的呼叫約定與常規函式不同。
在這些平臺上,需要為常規的、非可變引數的函式引數指定 argtypes
屬性。
libc.printf.argtypes = [ctypes.c_char_p]
因為指定該屬性不會影響可移植性,所以建議總是為所有可變引數函式指定 argtypes
。
使用自定義資料型別呼叫函式¶
你還可以自定義 ctypes
的引數轉換,以允許使用你自己類的例項作為函式引數。ctypes
會查詢一個 _as_parameter_
屬性,並將其用作函式引數。該屬性必須是整數、字串、位元組串、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>
ctypes.ArgumentError: argument 2: TypeError: 'int' object cannot be interpreted as ctypes.c_char_p
>>> 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
屬性來指定其他返回型別。
time()
的 C 原型是 time_t time(time_t *)
。因為 time_t
可能與預設返回型別 int 的型別不同,所以你應該指定 restype
屬性。
>>> libc.time.restype = c_time_t
引數型別可以使用 argtypes
來指定。
>>> libc.time.argtypes = (POINTER(c_time_t),)
要以 NULL
指標作為第一個引數來呼叫該函式,請使用 None
。
>>> print(libc.time(None))
1150640792
這裡有一個更高階的例子,它使用了 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")
b'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
ctypes.ArgumentError: argument 2: TypeError: one character bytes, bytearray or integer expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
b'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 API 函式期望一個指向資料型別的*指標*作為引數,可能是為了寫入相應的位置,或者如果資料太大而無法按值傳遞。這也被稱為*透過引用傳遞引數*。
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_
必須是一個由*二元組*組成的列表,每個元組包含一個*欄位名*和一個*欄位型別*。
欄位型別必須是像 c_int
這樣的 ctypes
型別,或者任何其他派生的 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
>>>
然而,你可以構建更復雜的結構體。一個結構體本身可以透過使用一個結構體作為欄位型別來包含其他結構體。
這是一個 RECT 結構體,它包含兩個名為 *upperleft* 和 *lowerright* 的 POINT。
>>> 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))
欄位描述符可以從*類*中檢索,它們對於除錯很有用,因為它們可以提供有用的資訊。請參見 CField
。
>>> POINT.x
<ctypes.CField 'x' type=c_int, ofs=0, size=4>
>>> POINT.y
<ctypes.CField 'y' type=c_int, ofs=4, size=4>
>>>
警告
ctypes
不支援按值向函式傳遞帶有位域的聯合體或結構體。雖然這在 32 位 x86 上可能有效,但庫不保證在一般情況下也有效。帶有位域的聯合體和結構體應始終透過指標傳遞給函式。
結構體/聯合體佈局、對齊和位元組序¶
預設情況下,結構體和聯合體欄位的佈局方式與 C 編譯器相同。透過在子類定義中指定一個 _layout_
類屬性,可以完全覆蓋此行為;詳情請參閱該屬性的文件。
可以透過分別設定類屬性 _pack_
和/或 _align_
來指定欄位和/或結構體本身的最大對齊方式。詳情請參閱屬性文件。
ctypes
對結構體和聯合體使用本地位元組序。要構建非本地位元組序的結構體,你可以使用 BigEndianStructure
, LittleEndianStructure
, BigEndianUnion
, 和 LittleEndianUnion
基類中的一個。這些類不能包含指標欄位。
結構體和聯合體中的位域¶
可以建立包含位域的結構體和聯合體。位域只適用於整數字段,位寬在 _fields_
元組中作為第三項指定。
>>> class Int(Structure):
... _fields_ = [("first_16", c_int, 16),
... ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
<ctypes.CField 'first_16' type=c_int, ofs=0, bit_size=16, bit_offset=0>
>>> print(Int.second_16)
<ctypes.CField 'second_16' type=c_int, ofs=0, bit_size=16, bit_offset=16>
需要注意的是,位域在記憶體中的分配和佈局並未被 C 標準定義;它們的實現是編譯器特定的。預設情況下,Python 將嘗試匹配當前平臺“原生”編譯器的行為。有關預設行為以及如何更改它的詳細資訊,請參見 _layout_
屬性。
陣列¶
陣列是序列,包含固定數量的相同型別的例項。
建立陣列型別的推薦方法是將資料型別與一個正整數相乘。
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
行,因為陣列內容被初始化為零。
也可以指定正確型別的初始化器。
>>> 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
>>>
將另一個 c_int
例項賦給指標的 contents 屬性會導致指標指向儲存該例項的記憶體位置。
>>> 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 語言中一樣:你可以訪問或更改任意記憶體位置。通常你只在從 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
>>>
無 GIL 時的執行緒安全¶
從 Python 3.13 開始,GIL 可以在自由執行緒構建中被停用。在 ctypes 中,對單個物件的併發讀寫是安全的,但跨多個物件則不安全。
>>> number = c_int(42) >>> pointer_a = pointer(number) >>> pointer_b = pointer(number)
在上述程式碼中,如果 GIL 被停用,只有一個物件能夠同時對該地址進行讀寫才是安全的。所以,pointer_a
可以在多個執行緒間共享和寫入,但前提是 pointer_b
沒有同時嘗試做同樣的事情。如果這是一個問題,可以考慮使用 threading.Lock
來同步對記憶體的訪問。
>>> import threading >>> lock = threading.Lock() >>> # Thread 1 >>> with lock: ... pointer_a.contents = 24 >>> # Thread 2 >>> with lock: ... pointer_b.contents = 42
型別轉換¶
通常,ctypes 會進行嚴格的型別檢查。這意味著,如果函式引數型別列表 argtypes
中有 POINTER(c_int)
,或者結構體定義中的成員欄位型別是它,那麼只接受完全相同型別的例項。這個規則有一些例外,ctypes 會接受其他物件。例如,你可以傳遞相容的陣列例項來代替指標型別。所以,對於 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()
轉換。
要將一個 POINTER 型別的欄位設定為 NULL
,你可以賦 None
。
>>> bar.values = None
>>>
有時你會有不相容型別的例項。在 C 語言中,你可以將一種型別強制轉換為另一種型別。ctypes
提供了一個 cast()
函式,可以以同樣的方式使用。上面定義的 Bar
結構體接受 POINTER(c_int)
指標或 c_int
陣列作為其 values
欄位,但不接受其他型別的例項。
>>> 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 例項轉換為指向不同 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
>>>
因為新的 class cell
在類語句本身中是不可用的。在 ctypes
中,我們可以在類語句之後定義 cell
類,然後再設定 _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()
必須用指向待排序資料的指標、資料陣列中的專案數、單個專案的大小以及指向比較函式(即回撥函式)的指標來呼叫。然後,回撥函式將被兩個指向專案的指標呼叫,如果第一個專案小於第二個,它必須返回一個負整數;如果它們相等,則返回零;否則返回一個正整數。
所以我們的回撥函式接收指向整數的指標,並且必須返回一個整數。首先我們為回撥函式建立 type
。
>>> 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
>>>
備註
只要 C 程式碼還在使用 CFUNCTYPE()
物件,請確保你保留對它們的引用。ctypes
不會這樣做,如果你不保留引用,它們可能會被垃圾回收,導致在回撥時程式崩潰。
另外,請注意,如果回撥函式是在 Python 控制之外建立的執行緒中被呼叫的(例如,由呼叫回撥的外部程式碼建立),ctypes 會在每次呼叫時建立一個新的虛擬 Python 執行緒。這種行為對於大多數目的來說是正確的,但它意味著儲存在 threading.local
中的值在不同的回撥之間將*不*會保留,即使這些呼叫是從同一個 C 執行緒發出的。
訪問從 DLL 匯出的值¶
一些共享庫不僅匯出函式,還匯出變數。Python 庫本身的一個例子是 Py_Version
,即編碼在單個常量整數中的 Python 執行時版本號。
ctypes
可以透過型別的 in_dll()
類方法來訪問這樣的值。pythonapi 是一個預定義的符號,用於訪問 Python C API。
>>> version = ctypes.c_int.in_dll(ctypes.pythonapi, "Py_Version")
>>> print(hex(version.value))
0x30c00a0
一個擴充套件的例子,它也演示了指標的使用,訪問了由 Python 匯出的 PyImport_FrozenModules
指標。
引用該值的文件:
該指標被初始化為指向一個
_frozen
記錄的陣列,陣列以一個所有成員均為NULL
或零的記錄結尾。當一個凍結模組被匯入時,會在此表中搜索。第三方程式碼可以利用這一點來提供一個動態建立的凍結模組集合。
所以操縱這個指標甚至可能是有用的。為了限制示例的大小,我們只展示如何用 ctypes
來讀取這個表。
>>> from ctypes import *
>>>
>>> class struct_frozen(Structure):
... _fields_ = [("name", c_char_p),
... ("code", POINTER(c_ubyte)),
... ("size", c_int),
... ("get_code", POINTER(c_ubyte)), # Function pointer
... ]
...
>>>
我們已經定義了 _frozen
資料型別,所以我們可以獲取指向表的指標。
>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "_PyImport_FrozenBootstrap")
>>>
因為 table
是一個指向 struct_frozen
記錄陣列的 pointer
,我們可以遍歷它,但我們必須確保我們的迴圈會終止,因為指標沒有大小。遲早它可能會因訪問衝突或其他原因而崩潰,所以當我們遇到 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
zipimport 12345
>>>
標準 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
例項化的物件的值只能設定為位元組或整數。
為什麼它列印 False
?ctypes 例項是包含一個記憶體塊和一些訪問記憶體內容的描述符的物件。在記憶體塊中儲存一個 Python 物件並不會儲存物件本身,而是儲存了物件的 contents
。每次再次訪問內容時都會構造一個新的 Python 物件!
可變大小的資料型別¶
ctypes
為可變大小的陣列和結構體提供了一些支援。
resize()
函式可用於調整現有 ctypes 物件的記憶體緩衝區大小。該函式以物件作為第一個引數,以請求的大小(以位元組為單位)作為第二個引數。記憶體塊不能小於物件型別指定的自然記憶體塊,如果嘗試這樣做會引發 ValueError
。
>>> 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 資料例項作為引數,並返回庫載入器指定的預設結果型別。
它們是一個私有本地類 _FuncPtr
(未在 ctypes
中公開) 的例項,該類繼承自私有類 _CFuncPtr
。
>>> import ctypes
>>> lib = ctypes.CDLL(None)
>>> issubclass(lib._FuncPtr, ctypes._CFuncPtr)
True
>>> lib._FuncPtr is ctypes._CFuncPtr
False
- class ctypes._CFuncPtr¶
C 可呼叫外部函式的基類。
外部函式的例項也是 C 相容的資料型別;它們表示 C 函式指標。
這種行為可以透過給外部函式物件的特殊屬性賦值來定製。
- restype¶
指定一個 ctypes 型別來規定外部函式的返回型別。對於不返回任何內容的 void 函式,請使用
None
。可以賦值一個非 ctypes 型別的可呼叫 Python 物件,在這種情況下,函式被假定返回一個 C int,並且該可呼叫物件將被這個整數呼叫,從而允許進一步的處理或錯誤檢查。不推薦使用此方法,為了更靈活的後處理或錯誤檢查,請使用 ctypes 資料型別作為
restype
並將一個可呼叫物件賦給errcheck
屬性。
- argtypes¶
指定一個 ctypes 型別的元組,用於規定函式接受的引數型別。使用
stdcall
呼叫約定的函式只能用與此元組長度相同的引數數量來呼叫;使用 C 呼叫約定的函式還接受額外的、未指定的引數。當呼叫一個外部函式時,每個實際引數都會傳遞給
argtypes
元組中各項的from_param()
類方法,此方法允許將實際引數適配為外部函式可接受的物件。例如,argtypes
元組中的一個c_char_p
項會使用 ctypes 轉換規則將作為引數傳遞的字串轉換為位元組物件。新增:現在可以在 argtypes 中放入非 ctypes 型別的項,但每一項都必須有一個
from_param()
方法,該方法返回一個可用作引數的值(整數、字串、ctypes 例項)。這允許定義可以將自定義物件適配為函式引數的介面卡。
- errcheck¶
將一個 Python 函式或其他可呼叫物件賦給此屬性。該可呼叫物件將以三個或更多引數被呼叫:
- callable(result, func, arguments)
result 是外部函式返回的結果,由
restype
屬性指定。func 是外部函式物件本身,這允許重用同一個可呼叫物件來檢查或後處理多個函式的結果。
arguments 是一個包含最初傳遞給函式呼叫的引數的元組,這允許根據所用引數來定製行為。
此函式返回的物件將從外部函式呼叫中返回,但它也可以檢查結果值,並在外部函式呼叫失敗時引發異常。
在 Windows 上,當一個外部函式呼叫引發系統異常(例如,由於訪問衝突)時,它將被捕獲並替換為適當的 Python 異常。此外,將引發一個審計事件 ctypes.set_exception
,引數為 code
,允許審計鉤子用自己的異常替換它。
某些呼叫外部函式的方式以及此模組中的某些函式可能會引發一個審計事件 ctypes.call_function
,引數為 function pointer
和 arguments
。
函式原型¶
外部函式也可以透過例項化函式原型來建立。函式原型類似於 C 中的函式原型;它們描述一個函式(返回型別、引數型別、呼叫約定)而不定義實現。必須用期望的結果型別和函式的引數型別來呼叫這些工廠函式,它們可以用作裝飾器工廠,因此可以透過 @wrapper
語法應用於函式。請參閱 回撥函式 檢視示例。
- ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)¶
返回的函式原型建立使用標準 C 呼叫約定的函式。函式在呼叫期間將釋放 GIL。如果 use_errno 設定為 true,則在呼叫前後,ctypes 私有的系統
errno
變數副本會與真實的errno
值交換;use_last_error 對 Windows 錯誤程式碼執行相同的操作。
- ctypes.WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)¶
返回的函式原型建立使用
stdcall
呼叫約定的函式。函式在呼叫期間將釋放 GIL。use_errno 和 use_last_error 的含義與上面相同。可用性: Windows
- ctypes.PYFUNCTYPE(restype, *argtypes)¶
返回的函式原型建立使用 Python 呼叫約定的函式。函式在呼叫期間不會釋放 GIL。
這些工廠函式建立的函式原型可以透過不同的方式例項化,具體取決於呼叫中引數的型別和數量:
- prototype(address)
返回指定地址處的外部函式,該地址必須是一個整數。
- prototype(callable)
從 Python callable 建立一個 C 可呼叫函式(回撥函式)。
- prototype(func_spec[, paramflags])
返回由共享庫匯出的外部函式。func_spec 必須是一個二元組
(name_or_ordinal, library)
。第一項是匯出函式的名稱(字串),或匯出函式的序號(小整數)。第二項是共享庫例項。
- prototype(vtbl_index, name[, paramflags[, iid]])
返回一個將呼叫 COM 方法的外部函式。vtbl_index 是虛擬函式表中的索引,是一個小的非負整數。name 是 COM 方法的名稱。iid 是一個可選的介面識別符號指標,用於擴充套件錯誤報告。
如果未指定 iid,則在 COM 方法呼叫失敗時會引發
OSError
。如果指定了 iid,則會引發COMError
。COM 方法使用一種特殊的呼叫約定:除了在
argtypes
元組中指定的引數外,它們還需要一個指向 COM 介面的指標作為第一個引數。可用性: Windows
可選的 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.alignment(obj_or_type)¶
返回 ctypes 型別的對齊要求。obj_or_type 必須是 ctypes 型別或例項。
- ctypes.byref(obj[, offset])¶
返回一個指向 obj 的輕量級指標,obj 必須是 ctypes 型別的例項。offset 預設為零,並且必須是一個將加到內部指標值上的整數。
byref(obj, offset)
對應於以下 C 程式碼:(((char *)&obj) + offset)
返回的物件只能用作外部函式呼叫的引數。它的行為類似於
pointer(obj)
,但構造速度快得多。
- ctypes.CopyComPointer(src, dst)¶
將一個 COM 指標從 src 複製到 dst 並返回 Windows 特定的
HRESULT
值。如果 src 不為
NULL
,則會呼叫其AddRef
方法,增加引用計數。相反,在賦新值之前,dst 的引用計數不會減少。除非 dst 是
NULL
,否則呼叫者有責任在必要時透過呼叫其Release
方法來減少引用計數。可用性: Windows
在 3.14 版本加入。
- ctypes.cast(obj, type)¶
此函式類似於 C 中的強制型別轉換運算子。它返回一個 type 的新例項,該例項指向與 obj 相同的記憶體塊。type 必須是指標型別,而 obj 必須是可以解釋為指標的物件。
- ctypes.create_string_buffer(init, size=None)¶
- ctypes.create_string_buffer(size)
此函式建立一個可變的字元緩衝區。返回的物件是
c_char
的 ctypes 陣列。如果給定了 size(且不為
None
),它必須是一個int
。它指定了返回陣列的大小。如果給定了 init 引數,它必須是
bytes
。它用於初始化陣列項。未以此方式初始化的位元組被設定為零 (NUL)。如果未給定 size(或者為
None
),則緩衝區會比 init 大一個元素,實際上是添加了一個 NUL 終止符。如果同時給定了兩個引數,size 必須不小於
len(init)
。警告
如果 size 等於
len(init)
,則不會新增 NUL 終止符。不要將這樣的緩衝區視為 C 字串。例如:
>>> bytes(create_string_buffer(2)) b'\x00\x00' >>> bytes(create_string_buffer(b'ab')) b'ab\x00' >>> bytes(create_string_buffer(b'ab', 2)) b'ab' >>> bytes(create_string_buffer(b'ab', 4)) b'ab\x00\x00' >>> bytes(create_string_buffer(b'abcdef', 2)) Traceback (most recent call last): ... ValueError: byte string too long
引發一個 審計事件
ctypes.create_string_buffer
,並附帶引數init
,size
。
- ctypes.create_unicode_buffer(init, size=None)¶
- ctypes.create_unicode_buffer(size)
此函式建立一個可變的 unicode 字元緩衝區。返回的物件是
c_wchar
的 ctypes 陣列。該函式接受與
create_string_buffer()
相同的引數,但 init 必須是字串,且 size 計數的是c_wchar
。引發一個 審計事件
ctypes.create_unicode_buffer
,並附帶引數init
,size
。
- ctypes.DllCanUnloadNow()¶
此函式是一個鉤子,允許使用 ctypes 實現程序內 COM 伺服器。它從 _ctypes 擴充套件 dll 匯出的 DllCanUnloadNow 函式中呼叫。
可用性: Windows
- ctypes.DllGetClassObject()¶
此函式是一個鉤子,允許使用 ctypes 實現程序內 COM 伺服器。它從
_ctypes
擴充套件 dll 匯出的 DllGetClassObject 函式中呼叫。可用性: Windows
- ctypes.util.find_library(name)¶
嘗試查詢一個庫並返回其路徑名。name 是庫名,不帶任何字首(如
lib
)、字尾(如.so
,.dylib
)或版本號(這是 posix 連結器選項-l
使用的形式)。如果找不到庫,則返回None
。確切的功能取決於系統。
- ctypes.util.find_msvcrt()¶
返回 Python 和擴充套件模組使用的 VC 執行時庫的檔名。如果無法確定庫的名稱,則返回
None
。如果你需要釋放記憶體,例如,由擴充套件模組透過呼叫
free(void *)
分配的記憶體,那麼使用分配記憶體的同一個庫中的函式來釋放它非常重要。可用性: Windows
- ctypes.util.dllist()¶
嘗試提供載入到當前程序中的共享庫的路徑列表。這些路徑未經任何規範化或處理。如果底層平臺 API 失敗,該函式可能會引發
OSError
。具體功能取決於系統。在大多數平臺上,列表的第一個元素代表當前的可執行檔案。它可能是一個空字串。
可用性:Windows、macOS、iOS、glibc、BSD libc、musl
在 3.14 版本加入。
- ctypes.FormatError([code])¶
返回錯誤程式碼 code 的文字描述。如果未指定錯誤程式碼,則透過呼叫 Windows API 函式
GetLastError()
來使用最後一個錯誤程式碼。可用性: Windows
- ctypes.GetLastError()¶
返回 Windows 在呼叫執行緒中設定的最後一個錯誤程式碼。此函式直接呼叫 Windows 的
GetLastError()
函式,它不返回 ctypes 私有的錯誤程式碼副本。可用性: Windows
- ctypes.get_last_error()¶
返回呼叫執行緒中系統
LastError
變數的 ctypes 私有副本的當前值。可用性: Windows
引發一個 審計事件
ctypes.get_last_error
,不帶任何引數。
- ctypes.memmove(dst, src, count)¶
與標準 C 庫函式 memmove 相同:將 count 位元組從 src 複製到 dst。dst 和 src 必須是可以轉換為指標的整數或 ctypes 例項。
- ctypes.memset(dst, c, count)¶
與標準 C 庫函式 memset 相同:用 count 個值為 c 的位元組填充地址 dst 處的記憶體塊。dst 必須是指定地址的整數或 ctypes 例項。
- ctypes.POINTER(type, /)¶
建立或返回一個 ctypes 指標型別。指標型別在內部被快取和重用,因此重複呼叫此函式開銷很小。type 必須是 ctypes 型別。
CPython 實現細節: 生成的指標型別被快取在 type 的
__pointer_type__
屬性中。可以在首次呼叫POINTER
之前設定此屬性,以設定自定義指標型別。但是,不鼓勵這樣做:如果不依賴可能在未來 Python 版本中更改的實現細節,手動建立合適的指標型別很困難。
- ctypes.pointer(obj, /)¶
建立一個新的指標例項,指向 obj。返回的物件型別為
POINTER(type(obj))
。注意:如果你只是想將一個物件的指標傳遞給外部函式呼叫,你應該使用
byref(obj)
,它快得多。
- ctypes.resize(obj, size)¶
此函式調整 obj 的內部記憶體緩衝區大小,obj 必須是 ctypes 型別的例項。不能使緩衝區小於物件型別的本機大小(由
sizeof(type(obj))
給出),但可以擴大緩衝區。
- ctypes.set_errno(value)¶
將呼叫執行緒中系統
errno
變數的 ctypes 私有副本的當前值設定為 value 並返回之前的值。引發一個 審計事件
ctypes.set_errno
,並附帶引數errno
。
- ctypes.set_last_error(value)¶
將呼叫執行緒中系統
LastError
變數的 ctypes 私有副本的當前值設定為 value 並返回之前的值。可用性: Windows
引發一個 審計事件
ctypes.set_last_error
,並附帶引數error
。
- ctypes.sizeof(obj_or_type)¶
返回 ctypes 型別或例項記憶體緩衝區的位元組大小。與 C 的
sizeof
運算子作用相同。
- ctypes.string_at(ptr, size=-1)¶
返回 void *ptr 處的位元組字串。如果指定了 size,則用作大小,否則假定字串以零結尾。
引發一個 審計事件
ctypes.string_at
,並附帶引數ptr
,size
。
- ctypes.WinError(code=None, descr=None)¶
建立
OSError
的一個例項。如果未指定 code,則呼叫GetLastError()
來確定錯誤程式碼。如果未指定 descr,則呼叫FormatError()
來獲取錯誤的文字描述。可用性: Windows
在 3.3 版本發生變更: 過去會建立
WindowsError
的一個例項,現在它是OSError
的別名。
- ctypes.wstring_at(ptr, size=-1)¶
返回 void *ptr 處的寬字元字串。如果指定了 size,則用作字串的字元數,否則假定字串以零結尾。
引發一個 審計事件
ctypes.wstring_at
,並附帶引數ptr
,size
。
- ctypes.memoryview_at(ptr, size, readonly=False)¶
返回一個長度為 size 的
memoryview
物件,它引用從 void *ptr 開始的記憶體。如果 readonly 為 true,則返回的
memoryview
物件不能用於修改底層記憶體。(透過其他方式進行的更改仍會反映在返回的物件中。)此函式與
string_at()
類似,主要區別在於它不會建立指定記憶體的副本。它是memoryview((c_byte * size).from_address(ptr))
的語義等效(但更高效)的替代方案。(雖然from_address()
只接受整數,但 ptr 也可以作為ctypes.POINTER
或byref()
物件給出。)引發一個 審計事件
ctypes.memoryview_at
,並附帶引數address
,size
,readonly
。在 3.14 版本加入。
資料型別¶
- class ctypes._CData¶
這個非公開類是所有 ctypes 資料型別的通用基類。除其他事項外,所有 ctypes 型別例項都包含一個儲存 C 相容資料的記憶體塊;該記憶體塊的地址由
addressof()
輔助函式返回。另一個例項變數以_objects
的形式公開;它包含其他需要保持存活的 Python 物件,以防記憶體塊包含指標。ctypes 資料型別的通用方法,這些都是類方法(準確地說,它們是 元類 的方法):
- 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 型別例項,address 必須是一個整數。
此方法以及其他間接呼叫此方法的方法,會引發一個 審計事件
ctypes.cdata
,並附帶引數address
。
- from_param(obj)¶
此方法將 obj 適配為 ctypes 型別。當該型別出現在外部函式的
argtypes
元組中時,它會與外部函式呼叫中使用的實際物件一起被呼叫;它必須返回一個可用作函式呼叫引數的物件。所有 ctypes 資料型別都有此classmethod的預設實現,通常在 obj 是該型別的例項時返回 obj。某些型別也接受其他物件。
- in_dll(library, name)¶
此方法返回由共享庫匯出的 ctypes 型別例項。name 是匯出資料的符號名稱,library 是已載入的共享庫。
ctypes 資料型別的通用類變數:
ctypes 資料型別的通用例項變數:
- _b_needsfree_¶
這個只讀變數在 ctypes 資料例項自己分配了記憶體塊時為 true,否則為 false。
- _objects¶
此成員要麼是
None
,要麼是包含需要保持存活以使記憶體塊內容保持有效的 Python 物件的字典。此物件僅用於除錯;切勿修改此字典的內容。
基本資料型別¶
- class ctypes._SimpleCData¶
這個非公開類是所有基本 ctypes 資料型別的基類。在此提及它是因為它包含了基本 ctypes 資料型別的通用屬性。
_SimpleCData
是_CData
的子類,因此它繼承了它們的方法和屬性。現在,不包含指標且本身不是指標的 ctypes 資料型別可以被 pickle。例項只有一個屬性:
基本資料型別,當作為外部函式呼叫結果返回時,或者例如透過檢索結構欄位成員或陣列成員時,會被透明地轉換為原生的 Python 型別。換句話說,如果一個外部函式的 restype
是 c_char_p
,你將總是收到一個 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_double_complex¶
表示 C double complex 資料型別(如果可用)。建構函式接受一個可選的
complex
初始化值。在 3.14 版本加入。
- class ctypes.c_longdouble_complex¶
表示 C long double complex 資料型別(如果可用)。建構函式接受一個可選的
complex
初始化值。在 3.14 版本加入。
- class ctypes.c_int¶
表示 C signed int 資料型別。建構函式接受一個可選的整數初始化值;不進行溢位檢查。在
sizeof(int) == sizeof(long)
的平臺上,它是c_long
的別名。
- 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_time_t¶
表示 C
time_t
資料型別。3.12 新版功能.
- class ctypes.c_ubyte¶
表示 C unsigned char 資料型別,它將值解釋為小整數。建構函式接受一個可選的整數初始化值;不進行溢位檢查。
- class ctypes.c_uint¶
表示 C unsigned int 資料型別。建構函式接受一個可選的整數初始化值;不進行溢位檢查。在
sizeof(int) == sizeof(long)
的平臺上,它是c_ulong
的別名。
- 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.py_object¶
表示 C PyObject* 資料型別。不帶引數呼叫它會建立一個
NULL
PyObject* 指標。在 3.14 版本發生變更:
py_object
現在是一個 泛型。
ctypes.wintypes
模組提供了相當多其他 Windows 特定的資料型別,例如 HWND
、WPARAM
或 DWORD
。一些有用的結構體如 MSG
或 RECT
也被定義了。
結構化資料型別¶
- class ctypes.BigEndianUnion(*args, **kw)¶
大端位元組序聯合體的抽象基類。
在 3.11 版本中新增。
- class ctypes.LittleEndianUnion(*args, **kw)¶
小端位元組序聯合體的抽象基類。
在 3.11 版本中新增。
- class ctypes.BigEndianStructure(*args, **kw)¶
大端位元組序結構體的抽象基類。
- class ctypes.LittleEndianStructure(*args, **kw)¶
小端位元組序結構體的抽象基類。
非原生位元組序的結構體和聯合體不能包含指標型別欄位,或任何其他包含指標型別欄位的資料型別。
- class ctypes.Structure(*args, **kw)¶
原生位元組序結構體的抽象基類。
具體的結構體和聯合體型別必須透過子類化這些型別之一來建立,並且至少要定義一個
_fields_
類變數。ctypes
將建立描述符,允許透過直接的屬性訪問來讀寫欄位。這些是- _fields_¶
一個定義結構體欄位的序列。其中的項必須是二元組或三元組。第一項是欄位的名稱,第二項指定欄位的型別;它可以是任何 ctypes 資料型別。
對於像
c_int
這樣的整數型別欄位,可以給出第三個可選的項。它必須是一個小的正整數,定義欄位的位寬。在一個結構體或聯合體中,欄位名必須是唯一的。這一點不會被檢查,當名稱重複時,只有一個欄位可以被訪問。
可以在定義 Structure 子類的 class 語句之後定義
_fields_
類變數,這允許建立直接或間接引用自身的資料型別。class List(Structure): pass List._fields_ = [("pnext", POINTER(List)), ... ]
_fields_
類變數只能設定一次。之後的賦值將引發AttributeError
。此外,
_fields_
類變數必須在結構體或聯合體型別首次使用前定義:建立例項或子類,對其呼叫sizeof()
等。之後對_fields_
的賦值將引發AttributeError
。如果在這樣的使用之前沒有設定_fields_
,那麼結構體或聯合體將沒有自己的欄位,就好像_fields_
是空的一樣。結構體型別的子類的子類會繼承基類的欄位,再加上在子類的子類中定義的
_fields_
(如果有的話)。
- _pack_¶
一個可選的小整數,允許在例項中覆蓋結構體欄位的對齊方式。
這僅對與 MSVC 相容的記憶體佈局實現(見
_layout_
)。將
_pack_
設定為 0 與完全不設定它是一樣的。否則,該值必須是 2 的正整數次冪。其效果等同於 C 中的#pragma pack(N)
,只是ctypes
可能允許比編譯器接受的更大的 n。在
_fields_
被賦值時,_pack_
必須已經被定義,否則它將不起作用。自 3.14 版本起不推薦使用,將在 3.19 版本中移除: 由於歷史原因,如果
_pack_
不為零,預設將使用與 MSVC 相容的佈局。在非 Windows 平臺上,這個預設行為已被棄用,並計劃在 Python 3.19 中成為一個錯誤。如果這是你想要的行為,請明確地將_layout_
設定為'ms'
。
- _align_¶
一個可選的小整數,允許在將結構體打包或解包到/從記憶體時增加其對齊方式。
該值不能為負數。其效果等同於 GCC 上的
__attribute__((aligned(N)))
或 MSVC 上的#pragma align(N)
,只是ctypes
可能允許編譯器會拒絕的值。_align_
只能增加結構體的對齊要求。將其設定為 0 或 1 沒有效果。不鼓勵使用非 2 的冪次的值,這可能導致意外的行為。
在
_fields_
被賦值時,_align_
必須已經被定義,否則它將不起作用。在 3.13 版本加入。
- _layout_¶
一個可選的字串,用於命名結構體/聯合體的佈局。目前可以設定為
"ms"
:微軟編譯器(MSVC)使用的佈局。在 GCC 和 Clang 上,可以透過__attribute__((ms_struct))
選擇此佈局。"gcc-sysv"
:GCC 使用的 System V 或“類 SysV”資料模型的佈局,如在 Linux 和 macOS 上使用的。使用此佈局時,_pack_
必須未設定或為零。
如果未明確設定,
ctypes
將使用與平臺約定相匹配的預設值。此預設值可能會在未來的 Python 版本中更改(例如,當新平臺獲得官方支援時,或者當發現相似平臺之間的差異時)。目前預設值為在
_fields_
被賦值時,_layout_
必須已經被定義,否則它將不起作用。在 3.14 版本加入。
- _anonymous_¶
一個可選的序列,列出未命名(匿名)欄位的名稱。
_anonymous_
必須在_fields_
被賦值時已經定義,否則它將不起作用。此變數中列出的欄位必須是結構體或聯合體型別的欄位。
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_
中的名稱建立新屬性。
- class ctypes.CField(*args, **kw)¶
-
>>> class Color(Structure): ... _fields_ = ( ... ('red', c_uint8), ... ('green', c_uint8), ... ('blue', c_uint8), ... ('intense', c_bool, 1), ... ('blinking', c_bool, 1), ... ) ... >>> Color.red <ctypes.CField 'red' type=c_ubyte, ofs=0, size=1> >>> Color.green.type <class 'ctypes.c_ubyte'> >>> Color.blue.byte_offset 2 >>> Color.intense <ctypes.CField 'intense' type=c_bool, ofs=3, bit_size=1, bit_offset=0> >>> Color.blinking.bit_offset 1
所有屬性都是隻讀的。
CField
物件是透過_fields_
建立的;不要直接例項化該類。在 3.14 版本加入: 以前,描述符只有
offset
和size
屬性以及一個可讀的字串表示;CField
類不能直接使用。- name¶
欄位的名稱,為字串。
- offset¶
- byte_offset¶
欄位的偏移量,以位元組為單位。
對於位欄位,這是底層位元組對齊的*儲存單元*的偏移量;參見
bit_offset
。
- byte_size¶
欄位的大小,以位元組為單位。
對於位欄位,這是底層*儲存單元*的大小。通常,它與位欄位的型別大小相同。
- size¶
對於非位欄位,等同於
byte_size
。對於位欄位,這包含一個向後相容的位壓縮值,它結合了
bit_size
和bit_offset
。請優先使用明確的屬性。
- is_bitfield¶
如果這是一個位欄位,則為 True。
- bit_offset¶
- bit_size¶
位欄位在其*儲存單元*內的位置,即在從
byte_offset
開始的byte_size
位元組記憶體中。要獲取欄位的值,請將儲存單元讀取為整數,左移
bit_offset
位,並取bit_size
個最低有效位。對於非位欄位,
bit_offset
為零,bit_size
等於byte_size * 8
。
- is_anonymous¶
如果此欄位是匿名的,則為 True,即它包含應合併到包含它的結構體或聯合體中的巢狀子欄位。
陣列和指標¶
- class ctypes.Array(*args)¶
陣列的抽象基類。
建立具體陣列型別的推薦方法是將任何
ctypes
資料型別與一個非負整數相乘。或者,您可以子類化此型別並定義_length_
和_type_
類變數。可以使用標準的下標和切片訪問來讀寫陣列元素;對於切片讀取,結果物件本身*不是*一個Array
。- _length_¶
一個正整數,指定陣列中的元素數量。超出範圍的下標會導致
IndexError
。它將由len()
返回。
- _type_¶
指定陣列中每個元素的型別。
陣列子類的建構函式接受位置引數,用於按順序初始化元素。
異常¶
- exception ctypes.ArgumentError¶
當外部函式呼叫無法轉換傳遞的某個引數時,會引發此異常。
- exception ctypes.COMError(hresult, text, details)¶
當 COM 方法呼叫失敗時,會引發此異常。
- hresult¶
代表錯誤程式碼的整數值。
- text¶
錯誤訊息。
- details¶
五元組
(descr, source, helpfile, helpcontext, progid)
。descr 是文字描述。source 是引發錯誤的類或應用程式的與語言相關的
ProgID
。helpfile 是幫助檔案的路徑。helpcontext 是幫助上下文識別符號。progid 是定義該錯誤的介面的ProgID
。
可用性: Windows
在 3.14 版本加入。