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 函式的 MS 標準 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 |
1 字元位元組物件 |
|
|
1 字元字串 |
|
char |
int |
|
unsigned char |
int |
|
short |
int |
|
unsigned short |
int |
|
int |
int |
|
unsigned int |
int |
|
long |
int |
|
unsigned long |
int |
|
__int64 或 long long |
int |
|
unsigned __int64 或 unsigned long long |
int |
|
|
int |
|
|
int |
|
|
int |
|
float |
float |
|
double |
float |
|
long double |
float |
|
char* (NUL 終止) |
位元組物件或 |
|
wchar_t* (NUL 終止) |
字串或 |
|
void* |
整數或 |
建構函式接受任何具有真值的物件。
所有這些型別都可以透過使用正確型別和值的可選初始化器來呼叫它們來建立。
>>> 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 呼叫可變引數函式與呼叫具有固定數量引數的函式完全相同。在某些平臺,特別是 Apple 平臺的 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()
函式,該函式需要一個字串指標和一個 char,並返回一個指向字串的指標
>>> 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 char
>>> 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'
>>>
結構體和聯合體¶
結構體和聯合體必須從 Structure
和 Union
基類派生,這些基類在 ctypes
模組中定義。每個子類都必須定義一個 _fields_
屬性。_fields_
必須是一個 2 元組的列表,其中包含一個欄位名稱和一個欄位型別。
欄位型別必須是一個 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
>>>
但是,您可以構建更復雜的結構。結構體本身可以透過使用結構體作為欄位型別來包含其他結構體。
這是一個 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))
欄位 描述符 可以從類中檢索,它們對於除錯很有用,因為它們可以提供有用的資訊
>>> 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)
的作用相同。還可以為子類本身如何打包設定最小對齊方式,這與 MSVC 中 #pragma align(n)
的工作方式相同。可以透過在子類定義中指定一個 :_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)
<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
行,因為陣列內容初始化為零。
也可以指定正確型別的初始化程式
>>> 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
>>>
型別轉換¶
通常,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
>>>
注意
請確保保留對 CFUNCTYPE()
物件的引用,只要它們在 C 程式碼中使用。 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 呼叫約定的函式也接受其他未指定的引數。呼叫外部函式時,每個實際引數都會傳遞給
from_param()
,這是argtypes
元組中各項的類方法,此方法允許將實際引數調整為外部函式接受的物件。例如,c_char_p
在argtypes
元組中的項將使用 ctypes 轉換規則將作為引數傳遞的字串轉換為位元組物件。新增功能:現在可以在 argtypes 中放入不是 ctypes 型別的項,但每個項都必須具有
from_param()
方法,該方法返回可用作引數的值(整數、字串、ctypes 例項)。這允許定義可以調整自定義物件作為函式引數的介面卡。
- errcheck¶
將 Python 函式或其他可呼叫物件分配給此屬性。該可呼叫物件將使用三個或更多引數呼叫:
- callable(result, func, arguments)
result 是外部函式返回的內容,由
restype
屬性指定。func 是外部函式物件本身,這允許重用相同的可呼叫物件來檢查或後處理多個函式的結果。
arguments 是一個元組,其中包含最初傳遞給函式呼叫的引數,這允許根據使用的引數專門化行為。
此函式返回的物件將從外部函式呼叫返回,但它也可以檢查結果值,並在外部函式呼叫失敗時引發異常。
- exception ctypes.ArgumentError¶
當外部函式呼叫無法轉換傳遞的某個引數時,將引發此異常。
在 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,則在呼叫之前和之後,系統
errno
變數的 ctypes 私有副本將與真實的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 必須是一個 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.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.cast(obj, type)¶
此函式類似於 C 中的強制轉換運算子。它返回 type 的新例項,該例項指向與 obj 相同的記憶體塊。type 必須是指標型別,而 obj 必須是可以解釋為指標的物件。
- ctypes.create_string_buffer(init_or_size, size=None)¶
此函式建立一個可變的字元緩衝區。返回的物件是
c_char
的 ctypes 陣列。init_or_size 必須是一個整數,指定陣列的大小,或者一個位元組物件,將用於初始化陣列項。
如果將一個位元組物件指定為第一個引數,則緩衝區的大小會比其長度大一個,以便陣列中的最後一個元素是 NUL 終止字元。可以將一個整數作為第二個引數傳遞,以指定陣列的大小,此時不會使用位元組物件的長度。
引發一個帶有引數
init
,size
的 審計事件ctypes.create_string_buffer
。
- ctypes.create_unicode_buffer(init_or_size, size=None)¶
此函式建立一個可變的 Unicode 字元緩衝區。返回的物件是
c_wchar
的 ctypes 陣列。init_or_size 必須是一個整數,用於指定陣列的大小,或者是一個字串,用於初始化陣列項。
如果將一個字串指定為第一個引數,則緩衝區的大小會比字串的長度大一個,以便陣列中的最後一個元素是 NUL 終止字元。可以將一個整數作為第二個引數傳遞,以指定陣列的大小,此時不會使用字串的長度。
引發一個帶有引數
init
,size
的 審計事件ctypes.create_unicode_buffer
。
- 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.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 庫函式相同:從 src 複製 count 個位元組到 dst。dst 和 src 必須是可以轉換為指標的整數或 ctypes 例項。
- ctypes.memset(dst, c, count)¶
與標準 C memset 庫函式相同:使用值 c 的 count 個位元組填充地址 dst 處的記憶體塊。dst 必須是指定地址的整數,或者是一個 ctypes 例項。
- ctypes.POINTER(type, /)¶
建立並返回一個新的 ctypes 指標型別。指標型別在內部被快取和重用,因此重複呼叫此函式的開銷很低。type 必須是一個 ctypes 型別。
- 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 並返回先前的值。引發一個帶有引數
errno
的 審計事件ctypes.set_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)¶
此函式可能是 ctypes 中命名最差的函式。它建立
OSError
的一個例項。如果未指定 code,則會呼叫GetLastError
來確定錯誤程式碼。如果未指定 descr,則會呼叫FormatError()
來獲取錯誤的文字描述。可用性:Windows
在 3.3 版本中更改: 之前建立的是
WindowsError
的例項,現在它是OSError
的別名。
資料型別¶
- class ctypes._CData¶
這個非公共類是所有 ctypes 資料型別的公共基類。 除此之外,所有 ctypes 型別例項都包含一個儲存 C 相容資料的記憶體塊;該記憶體塊的地址由
addressof()
輔助函式返回。另一個例項變數作為_objects
公開;它包含其他需要保持活動狀態的 Python 物件,以防記憶體塊包含指標。ctypes 資料型別的常用方法,這些都是類方法(確切地說,它們是 元類 的方法)
- from_buffer(source[, offset])¶
此方法返回一個 ctypes 例項,該例項共享 source 物件的緩衝區。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 資料型別都具有此類方法的預設實現,如果 obj 是該型別的例項,通常會返回 obj。某些型別也接受其他物件。
- in_dll(library, name)¶
此方法返回共享庫匯出的 ctypes 型別例項。name 是匯出資料的符號的名稱,library 是載入的共享庫。
ctypes 資料型別的常用例項變數
- _b_needsfree_¶
當 ctypes 資料例項本身已分配記憶體塊時,此只讀變數為 true,否則為 false。
- _objects¶
此成員為
None
或包含需要保持活動狀態的 Python 物件的字典,以便記憶體塊內容保持有效。 此物件僅用於除錯;切勿修改此字典的內容。
基本資料型別¶
- class ctypes._SimpleCData¶
這個非公共類是所有基本 ctypes 資料型別的基類。這裡提到它是因為它包含基本 ctypes 資料型別的公共屬性。
_SimpleCData
是_CData
的子類,因此它繼承了它們的方法和屬性。現在可以 pickle 不是和不包含指標的 ctypes 資料型別。例項具有單個屬性
當基本資料型別作為外部函式呼叫結果返回,或者例如透過檢索結構體欄位成員或陣列項時,它們會透明地轉換為原生 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_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
,並且建構函式接受任何具有真值的物件。
ctypes.wintypes
模組提供了許多其他 Windows 特定的資料型別,例如 HWND
、WPARAM
或 DWORD
。還定義了一些有用的結構,如 MSG
或 RECT
。
結構化資料型別¶
- class ctypes.Union(*args, **kw)¶
本機位元組順序聯合的抽象基類。
- 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_¶
定義結構欄位的序列。項必須是 2 元組或 3 元組。第一個項是欄位的名稱,第二個項指定欄位的型別;它可以是任何 ctypes 資料型別。
對於像
c_int
這樣的整數型別欄位,可以給出第三個可選項。它必須是一個小的正整數,定義欄位的位寬度。欄位名稱在一個結構或聯合中必須是唯一的。這不會被檢查,當名稱重複時只能訪問一個欄位。
可以在定義 Structure 子類的 class 語句 *之後* 定義
_fields_
類變數,這允許建立直接或間接引用自身的資料型別class List(Structure): pass List._fields_ = [("pnext", POINTER(List)), ... ]
但是,必須在首次使用該型別之前定義
_fields_
類變數(建立例項,在其上呼叫sizeof()
,等等)。稍後對_fields_
類變數的賦值將引發 AttributeError。可以定義結構型別的子子類,它們繼承基類的欄位,以及在子子類中定義的
_fields_
(如果有)。
- _align_¶
一個可選的小整數,允許在將結構打包或從記憶體解包時覆蓋結構的對齊方式。將此屬性設定為 0 與根本不設定它相同。
3.13 版本新增。
- _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_
中不存在的名稱建立新的屬性。
陣列和指標¶
- class ctypes.Array(*args)¶
陣列的抽象基類。
建立具體陣列型別的推薦方法是將任何
ctypes
資料型別與一個非負整數相乘。或者,您可以子類化此型別並定義_length_
和_type_
類變數。可以使用標準下標和切片訪問來讀取和寫入陣列元素;對於切片讀取,結果物件本身不是Array
。- _length_¶
一個正整數,指定陣列中的元素數量。超出範圍的下標會導致
IndexError
。將由len()
返回。
- _type_¶
指定陣列中每個元素的型別。
陣列子類的建構函式接受位置引數,用於按順序初始化元素。