ctypes
--- Python 的外部函數庫?
ctypes
是 Python 的外部函數庫。它提供了與 C 兼容的數據類(lèi)型,并允許調用 DLL 或共享庫中的函數??墒褂迷撃K以純 Python 形式對這些庫進(jìn)行封裝。
ctypes 教程?
Note: The code samples in this tutorial use doctest
to make sure that
they actually work. Since some code samples behave differently under Linux,
Windows, or macOS, they contain doctest directives in comments.
注意:部分示例代碼引用了 ctypes c_int
類(lèi)型。在 sizeof(long) == sizeof(int)
的平臺上此類(lèi)型是 c_long
的一個(gè)別名。所以,在程序輸出 c_long
而不是你期望的 c_int
時(shí)不必感到迷惑 --- 它們實(shí)際上是同一種類(lèi)型。
載入動(dòng)態(tài)連接庫?
ctypes
導出了 cdll 對象,在 Windows 系統中還導出了 windll 和 oledll 對象用于載入動(dòng)態(tài)連接庫。
通過(guò)操作這些對象的屬性,你可以載入外部的動(dòng)態(tài)鏈接庫。cdll 載入按標準的 cdecl
調用協(xié)議導出的函數,而 windll 導入的庫按 stdcall
調用協(xié)議調用其中的函數。 oledll 也按 stdcall
調用協(xié)議調用其中的函數,并假定該函數返回的是 Windows HRESULT
錯誤代碼,并當函數調用失敗時(shí),自動(dòng)根據該代碼甩出一個(gè) OSError
異常。
在 3.3 版更改: 原來(lái)在 Windows 下拋出的異常類(lèi)型 WindowsError
現在是 OSError
的一個(gè)別名。
這是一些 Windows 下的例子。注意:msvcrt
是微軟 C 標準庫,包含了大部分 C 標準函數,這些函數都是以 cdecl 調用協(xié)議進(jìn)行調用的。
>>> from ctypes import *
>>> print(windll.kernel32)
<WinDLL 'kernel32', handle ... at ...>
>>> print(cdll.msvcrt)
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt
>>>
Windows 會(huì )自動(dòng)添加通常的 .dll
文件擴展名。
備注
通過(guò) cdll.msvcrt
調用的標準 C 函數,可能會(huì )導致調用一個(gè)過(guò)時(shí)的,與當前 Python 所不兼容的函數。因此,請盡量使用標準的 Python 函數,而不要使用 msvcrt
模塊。
在 Linux 下,必須使用 包含 文件擴展名的文件名來(lái)導入共享庫。因此不能簡(jiǎn)單使用對象屬性的方式來(lái)導入庫。因此,你可以使用方法 LoadLibrary()
,或構造 CDLL 對象來(lái)導入庫。
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")
>>> libc
<CDLL 'libc.so.6', handle ... at ...>
>>>
操作導入的動(dòng)態(tài)鏈接庫中的函數?
通過(guò)操作dll對象的屬性來(lái)操作這些函數。
>>> 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 系統的動(dòng)態(tài)庫,比如 kernel32
和 user32
,通常會(huì )同時(shí)導出同一個(gè)函數的 ANSI 版本和 UNICODE 版本。UNICODE 版本通常會(huì )在名字最后以 W
結尾,而 ANSI 版本的則以 A
結尾。 win32的 GetModuleHandle
函數會(huì )根據一個(gè)模塊名返回一個(gè) 模塊句柄,該函數暨同時(shí)包含這樣的兩個(gè)版本的原型函數,并通過(guò)宏 UNICODE 是否定義,來(lái)決定宏 GetModuleHandle
導出的是哪個(gè)具體函數。
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll 不會(huì )通過(guò)這樣的魔法手段來(lái)幫你決定選擇哪一種函數,你必須顯式的調用 GetModuleHandleA
或 GetModuleHandleW
,并分別使用字節對象或字符串對象作參數。
有時(shí)候,dlls的導出的函數名不符合 Python 的標識符規范,比如 "??2@YAPAXI@Z"
。此時(shí),你必須使用 getattr()
方法來(lái)獲得該函數。
>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object at 0x...>
>>>
Windows 下,有些 dll 導出的函數沒(méi)有函數名,而是通過(guò)其順序號調用。對此類(lèi)函數,你也可以通過(guò) dll 對象的數值索引來(lái)操作這些函數。
>>> 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 函數那樣直接調用這些函數。在這個(gè)例子中,我們調用了 time()
函數,該函數返回一個(gè)系統時(shí)間戳(從 Unix 時(shí)間起點(diǎn)到現在的秒數),而``GetModuleHandleA()`` 函數返回一個(gè) win32 模塊句柄。
此函數中調用的兩個(gè)函數都使用了空指針(用 None
作為空指針):
>>> print(libc.time(None))
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
>>>
如果你用 cdecl
調用方式調用 stdcall
約定的函數,則會(huì )甩出一個(gè)異常 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)
>>>
你必須閱讀這些庫的頭文件或說(shuō)明文檔來(lái)確定它們的正確的調用協(xié)議。
在 Windows 中,ctypes
使用 win32 結構化異常處理來(lái)防止由于在調用函數時(shí)使用非法參數導致的程序崩潰。
>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>
然而,總有許多辦法,通過(guò)調用 ctypes
使得 Python 程序崩潰。因此,你必須小心使用。 faulthandler
模塊可以用于幫助診斷程序崩潰的原因。(比如由于錯誤的C庫函數調用導致的段錯誤)。
None
,整型,字節對象和(UNICODE)字符串是僅有的可以直接作為函數參數使用的四種Python本地數據類(lèi)型。None` 作為C的空指針 (NULL
),字節和字符串類(lèi)型作為一個(gè)指向其保存數據的內存塊指針 (char* 或 wchar_t*)。Python 的整型則作為平臺默認的C的 int 類(lèi)型,他們的數值被截斷以適應C類(lèi)型的整型長(cháng)度。
在我們開(kāi)始調用函數前,我們必須先了解作為函數參數的 ctypes
數據類(lèi)型。
基礎數據類(lèi)型?
ctypes
定義了一些和C兼容的基本數據類(lèi)型:
ctypes 類(lèi)型 |
C 類(lèi)型 |
Python 類(lèi)型 |
---|---|---|
_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 或 |
構造函數接受任何具有真值的對象。
所有這些類(lèi)型都可以通過(guò)使用正確類(lèi)型和值的可選初始值調用它們來(lái)創(chuàng )建:
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
>>>
由于這些類(lèi)型是可變的,它們的值也可以在以后更改:
>>> i = c_int(42)
>>> print(i)
c_long(42)
>>> print(i.value)
42
>>> i.value = -99
>>> print(i.value)
-99
>>>
當給指針類(lèi)型的對象 c_char_p
, c_wchar_p
和 c_void_p
等賦值時(shí),將改變它們所指向的 內存地址,而 不是 它們所指向的內存區域的 內容 (這是理所當然的,因為 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
>>>
但你要注意不能將它們傳遞給會(huì )改變指針所指內存的函數。如果你需要可改變的內存塊,ctypes 提供了 create_string_buffer()
函數,它提供多種方式創(chuàng )建這種內存塊。當前的內存塊內容可以通過(guò) 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'
>>>
The create_string_buffer()
function replaces the old c_buffer()
function (which is still available as an alias). To create a mutable memory
block containing unicode characters of the C type wchar_t
, use the
create_unicode_buffer()
function.
調用函數,繼續?
注意 printf 將打印到真正標準輸出設備,而*不是* sys.stdout
,因此這些實(shí)例只能在控制臺提示符下工作,而不能在 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
>>>
正如前面所提到過(guò)的,除了整數、字符串以及字節串之外,所有的 Python 類(lèi)型都必須使用它們對應的 ctypes
類(lèi)型包裝,才能夠被正確地轉換為所需的C語(yǔ)言類(lèi)型。
>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>
使用自定義的數據類(lèi)型調用函數?
你也可以通過(guò)自定義 ctypes
參數轉換方式來(lái)允許自定義類(lèi)型作為參數。 ctypes
會(huì )尋找 _as_parameter_
屬性并使用它作為函數參數。當然,它必須是數字、字符串或者二進(jìn)制字符串:
>>> 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
>>>
如果你不想把實(shí)例的數據存儲到 _as_parameter_
屬性??梢酝ㄟ^(guò)定義 property
函數計算出這個(gè)屬性。
指定必選參數的類(lèi)型(函數原型)?
可以通過(guò)設置 argtypes
屬性的方法指定從 DLL 中導出函數的必選參數類(lèi)型。
argtypes
必須是一個(gè) C 數據類(lèi)型的序列 (這里的 printf
可能不是個(gè)好例子,因為它是變長(cháng)參數,而且每個(gè)參數的類(lèi)型依賴(lài)于格式化字符串,不過(guò)嘗試這個(gè)功能也很方便):
>>> 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
>>>
指定數據類(lèi)型可以防止不合理的參數傳遞(就像 C 函數的原型),并且會(huì )自動(dòng)嘗試將參數轉換為需要的類(lèi)型:
>>> 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
>>>
如果你想通過(guò)自定義類(lèi)型傳遞參數給函數,必須實(shí)現 from_param()
類(lèi)方法,才能夠將此自定義類(lèi)型用于 argtypes
序列。from_param()
類(lèi)方法接受一個(gè) Python 對象作為函數輸入,它應該進(jìn)行類(lèi)型檢查或者其他必要的操作以保證接收到的對象是合法的,然后返回這個(gè)對象,或者它的 _as_parameter_
屬性,或者其他你想要傳遞給 C 函數的參數。這里也一樣,返回的結果必須是整型、字符串、二進(jìn)制字符串、 ctypes
類(lèi)型,或者一個(gè)具有 _as_parameter_
屬性的對象。
返回類(lèi)型?
默認情況下都會(huì )假定函數返回 C int 類(lèi)型。 其他返回類(lèi)型可以通過(guò)設置函數對象的 restype
屬性來(lái)指定。
這是個(gè)更高級的例子,它調用了 strchr
函數,這個(gè)函數接收一個(gè)字符串指針以及一個(gè)字符作為參數,返回另一個(gè)字符串指針。
>>> 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
屬性,第二個(gè)參數就會(huì )將單字符的 Python 二進(jìn)制字符對象轉換為 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'
>>>
如果外部函數返回了一個(gè)整數,你也可以使用要給可調用的 Python 對象(比如函數或者類(lèi))作為 restype
屬性的值。將會(huì )以 C 函數返回的 整數 對象作為參數調用這個(gè)可調用對象,執行后的結果作為最終函數返回值。這在錯誤返回值校驗和自動(dòng)拋出異常等方面比較有用。
>>> 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 獲取錯誤碼的字符串說(shuō)明,然后 返回 一個(gè)異常。 WinError
接收一個(gè)可選的錯誤碼作為參數,如果沒(méi)有的話(huà),它將調用 GetLastError()
獲取錯誤碼。
請注意,使用 errcheck
屬性可以實(shí)現更強大的錯誤檢查手段;詳情請見(jiàn)參考手冊。
傳遞指針(或以引用方式傳遞形參)?
有時(shí)候 C 函數接口可能由于要往某個(gè)地址寫(xiě)入值,或者數據太大不適合作為值傳遞,從而希望接收一個(gè) 指針 作為數據參數類(lèi)型。這和 傳遞參數引用 類(lèi)似。
ctypes
暴露了 byref()
函數用于通過(guò)引用傳遞參數,使用 pointer()
函數也能達到同樣的效果,只不過(guò) pointer()
需要更多步驟,因為它要先構造一個(gè)真實(shí)指針對象。所以在 Python 代碼本身不需要使用這個(gè)指針對象的情況下,使用 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'
>>>
結構體和聯(lián)合?
結構體和聯(lián)合必須繼承自 ctypes
模塊中的 Structure
和 Union
。子類(lèi)必須定義 _fields_
屬性。 _fields_
是一個(gè)二元組列表,二元組中包含 field name 和 field type 。
type 字段必須是一個(gè) ctypes
類(lèi)型,比如 c_int
,或者其他 ctypes
類(lèi)型: 結構體、聯(lián)合、數組、指針。
這是一個(gè)簡(jiǎn)單的 POINT 結構體,它包含名稱(chēng)為 x 和 y 的兩個(gè)變量,還展示了如何通過(guò)構造函數初始化結構體。
>>> 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
>>>
當然,你可以構造更復雜的結構體。一個(gè)結構體可以通過(guò)設置 type 字段包含其他結構體或者自身。
這是以一個(gè) RECT 結構體,他包含了兩個(gè) 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
>>>
嵌套結構體可以通過(guò)幾種方式構造初始化:
>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))
可以通過(guò) 類(lèi) 獲取字段 descriptor ,它能提供很多有用的調試信息。
>>> print(POINT.x)
<Field type=c_long, ofs=0, size=4>
>>> print(POINT.y)
<Field type=c_long, ofs=4, size=4>
>>>
警告
ctypes
不支持帶位域的結構體、聯(lián)合以值的方式傳給函數。這可能在 32 位 x86 平臺上可以正常工作,但是對于一般情況,這種行為是未定義的。帶位域的結構體、聯(lián)合應該總是通過(guò)指針傳遞給函數。
結構體/聯(lián)合字段對齊及字節順序?
默認情況下,結構體和聯(lián)合的字段與 C 的字節對齊是一樣的。也可以在定義子類(lèi)的時(shí)候指定類(lèi)的 _pack_
屬性來(lái)覆蓋這種行為。 它必須設置為一個(gè)正整數,表示字段的最大對齊字節。這和 MSVC 中的 #pragma pack(n)
功能一樣。
ctypes
中的結構體和聯(lián)合使用的是本地字節序。要使用非本地字節序,可以使用 BigEndianStructure
, LittleEndianStructure
, BigEndianUnion
, and LittleEndianUnion
作為基類(lèi)。這些類(lèi)不能包含指針字段。
結構體和聯(lián)合中的位域?
結構體和聯(lián)合中是可以包含位域字段的。位域只能用于整型字段,位長(cháng)度通過(guò) _fields_
中的第三個(gè)參數指定:
>>> 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>
>>>
數組?
數組是一個(gè)序列,包含指定個(gè)數元素,且必須類(lèi)型相同。
創(chuàng )建數組類(lèi)型的推薦方式是使用一個(gè)類(lèi)型乘以一個(gè)正數:
TenPointsArrayType = POINT * 10
下面是一個(gè)構造的數據案例,結構體中包含了4個(gè) 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
>>>
和平常一樣,通過(guò)調用它創(chuàng )建實(shí)例:
arr = TenPointsArrayType()
for pt in arr:
print(pt.x, pt.y)
以上代碼會(huì )打印幾行 0 0
,因為數組內容被初始化為 0.
也能通過(guò)指定正確類(lèi)型的數據來(lái)初始化:
>>> 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
類(lèi)型數據傳入 pointer()
函數創(chuàng )建指針:
>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>>
指針實(shí)例擁有 contents
屬性,它返回指針指向的真實(shí)對象,如上面的 i
對象:
>>> pi.contents
c_long(42)
>>>
注意 ctypes
并沒(méi)有 OOR (返回原始對象), 每次訪(fǎng)問(wèn)這個(gè)屬性時(shí)都會(huì )構造返回一個(gè)新的相同對象:
>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>
將這個(gè)指針的 contents 屬性賦值為另一個(gè) c_int
實(shí)例將會(huì )導致該指針指向該實(shí)例的內存地址:
>>> i = c_int(99)
>>> pi.contents = i
>>> pi.contents
c_long(99)
>>>
指針對象也可以通過(guò)整數下標進(jìn)行訪(fǎng)問(wèn):
>>> pi[0]
99
>>>
通過(guò)整數下標賦值可以改變指針所指向的真實(shí)內容:
>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)
>>>
使用 0 以外的索引也是合法的,但是你必須確保知道自己為什么這么做,就像 C 語(yǔ)言中: 你可以訪(fǎng)問(wèn)或者修改任意內存內容。 通常只會(huì )在函數接收指針是才會(huì )使用這種特性,而且你 知道 這個(gè)指針指向的是一個(gè)數組而不是單個(gè)值。
內部細節, pointer()
函數不只是創(chuàng )建了一個(gè)指針實(shí)例,它首先創(chuàng )建了一個(gè)指針 類(lèi)型 。這是通過(guò)調用 POINTER()
函數實(shí)現的,它接收 ctypes
類(lèi)型為參數,返回一個(gè)新的類(lèi)型:
>>> 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...>
>>>
無(wú)參調用指針類(lèi)型可以創(chuàng )建一個(gè) NULL
指針。 NULL
指針的布爾值是 False
>>> null_ptr = POINTER(c_int)()
>>> print(bool(null_ptr))
False
>>>
解引用指針的時(shí)候, ctypes
會(huì )幫你檢測是否指針為 NULL
(但是解引用無(wú)效的 非 NULL
指針仍會(huì )導致 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
>>>
類(lèi)型轉換?
通常情況下, ctypes 具有嚴格的類(lèi)型檢查。這代表著(zhù), 如果在函數 argtypes
中或者結構體定義成員中有 POINTER(c_int)
類(lèi)型,只有相同類(lèi)型的實(shí)例才會(huì )被接受。 也有一些例外。比如,你可以傳遞兼容的數組實(shí)例給指針類(lèi)型。所以,對于 POINTER(c_int)
,ctypes 也可以接受 c_int 類(lèi)型的數組:
>>> 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
>>>
另外,如果一個(gè)函數 argtypes
列表中的參數顯式的定義為指針類(lèi)型(如 POINTER(c_int)
),指針所指向的 類(lèi)型 (這個(gè)例子中是 c_int
)也可以傳遞給函數。ctypes 會(huì )自動(dòng)調用對應的 byref()
轉換。
可以給指針內容賦值為 None 將其設置為 Null
>>> bar.values = None
>>>
有時(shí)候你擁有一個(gè)不兼容的類(lèi)型。 在 C 中,你可以將一個(gè)類(lèi)型強制轉換為另一個(gè)。 ctypes
中的 a cast()
函數提供了相同的功能。 上面的結構體 Bar
的 value
字段接收 POINTER(c_int)
指針或者 c_int
數組,但是不能接受其他類(lèi)型的實(shí)例:
>>> 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
>>>
這種情況下, 需要手動(dòng)使用 cast()
函數。
cast()
函數可以將一個(gè)指針實(shí)例強制轉換為另一種 ctypes 類(lèi)型。 cast()
接收兩個(gè)參數,一個(gè) ctypes 指針對象或者可以被轉換為指針的其他類(lèi)型對象,和一個(gè) ctypes 指針類(lèi)型。 返回第二個(gè)類(lèi)型的一個(gè)實(shí)例,該返回實(shí)例和第一個(gè)參數指向同一片內存空間:
>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>
所以 cast()
可以用來(lái)給結構體 Bar
的 values
字段賦值:
>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0
>>>
不完整類(lèi)型?
不完整類(lèi)型 即還沒(méi)有定義成員的結構體、聯(lián)合或者數組。在 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 類(lèi)
在 class 語(yǔ)句結束之前還沒(méi)有完成定義。在 ctypes
中,我們可以先定義 cell
類(lèi),在 class 語(yǔ)句結束之后再設置 _fields_
屬性:
>>> from ctypes import *
>>> class cell(Structure):
... pass
...
>>> cell._fields_ = [("name", c_char_p),
... ("next", POINTER(cell))]
>>>
讓我們試試。我們定義兩個(gè) cell
實(shí)例,讓它們互相指向對方,然后通過(guò)指針鏈式訪(fǎng)問(wèn)幾次:
>>> 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
允許創(chuàng )建一個(gè)指向 Python 可調用對象的 C 函數。它們有時(shí)候被稱(chēng)為 回調函數 。
首先,你必須為回調函數創(chuàng )建一個(gè)類(lèi),這個(gè)類(lèi)知道調用約定,包括返回值類(lèi)型以及函數接收的參數類(lèi)型及個(gè)數。
CFUNCTYPE()
工廠(chǎng)函數使用 cdecl
調用約定創(chuàng )建回調函數類(lèi)型。在 Windows 上, WINFUNCTYPE()
工廠(chǎng)函數使用 stdcall
調用約定為回調函數創(chuàng )建類(lèi)型。
這些工廠(chǎng)函數的第一個(gè)參數是返回值類(lèi)型,回調函數的參數類(lèi)型作為剩余參數。
這里展示一個(gè)使用 C 標準庫函數 qsort()
的例子,它使用一個(gè)回調函數對數據進(jìn)行排序。 qsort()
將用來(lái)給整數數組排序:
>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>
qsort()
必須接收的參數,一個(gè)指向待排序數據的指針,元素個(gè)數,每個(gè)元素的大小,以及一個(gè)指向排序函數的指針,即回調函數。然后回調函數接收兩個(gè)元素的指針,如果第一個(gè)元素小于第二個(gè),則返回一個(gè)負整數,如果相等則返回0,否則返回一個(gè)正整數。
所以,我們的回調函數要接收兩個(gè)整數指針,返回一個(gè)整數。首先我們創(chuàng )建回調函數的 類(lèi)型
>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>
首先,這是一個(gè)簡(jiǎn)單的回調,它會(huì )顯示傳入的值:
>>> 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
>>>
現在我們可以比較兩個(gè)元素并返回有用的結果了:
>>> 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
>>>
這些工廠(chǎng)函數可以當作裝飾器工廠(chǎng),所以可以這樣寫(xiě):
>>> @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 代碼中的使用期一樣長(cháng)。 ctypes
不會(huì )確保這一點(diǎn),如果不這樣做,它們可能會(huì )被垃圾回收,導致程序在執行回調函數時(shí)發(fā)生崩潰。
注意,如果回調函數在Python之外的另外一個(gè)線(xiàn)程使用(比如,外部代碼調用這個(gè)回調函數), ctypes 會(huì )在每一次調用上創(chuàng )建一個(gè)虛擬 Python 線(xiàn)程。這個(gè)行為在大多數情況下是合理的,但也意味著(zhù)如果有數據使用 threading.local
方式存儲,將無(wú)法訪(fǎng)問(wèn),就算它們是在同一個(gè) C 線(xiàn)程中調用的 。
訪(fǎng)問(wèn) dll 的導出變量?
一些動(dòng)態(tài)鏈接庫不僅僅導出函數,也會(huì )導出變量。一個(gè)例子就是 Python 庫本身的 Py_OptimizeFlag
,根據啟動(dòng)選項 -O
、 -OO
的不同,它是值可能為 0、1、2 的整型。
ctypes
可以通過(guò) in_dll()
類(lèi)方法訪(fǎng)問(wèn)這類(lèi)變量 。 pythonapi 是用于訪(fǎng)問(wèn) Python C 接口的預定義符號:
>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
>>> print(opt_flag)
c_long(0)
>>>
如果解釋器使用 -O
選項啟動(dòng),這個(gè)例子會(huì )打印 c_long(1)
, 如果使用 -OO
啟動(dòng),則會(huì )打印 c_long(2)
。
一個(gè)擴展例子, 同時(shí)也展示了使用指針訪(fǎng)問(wèn) Python 導出的 PyImport_FrozenModules
指針對象。
對文檔中這個(gè)值的解釋說(shuō)明
該指針被初始化為指向 struct _frozen 數組,以
NULL
或者 0 作為結束標記。當一個(gè)凍結模塊被導入,首先要在這個(gè)表中搜索。第三方庫可以以此來(lái)提供動(dòng)態(tài)創(chuàng )建的凍結模塊集合。
這足以證明修改這個(gè)指針是很有用的。為了讓實(shí)例大小不至于太長(cháng),這里只展示如何使用 ctypes
讀取這個(gè)表:
>>> 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
... ]
...
>>>
我們定義了 struct _frozen 數據類(lèi)型,接著(zhù)就可以獲取這張表的指針了:
>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "_PyImport_FrozenBootstrap")
>>>
由于 table
是指向 struct_frozen
數組的 指針
,我們可以遍歷它,只不過(guò)需要自己判斷循環(huán)是否結束,因為指針本身并不包含長(cháng)度。它早晚會(huì )因為訪(fǎng)問(wèn)到野指針或者什么的把自己搞崩潰,所以我們最好在遇到 NULL
后就讓它退出循環(huán):
>>> 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
成員表示)并不是廣為人知的事情,它們僅僅用于實(shí)驗。例如,可以使用 import __hello__
嘗試一下這個(gè)功能。
意外?
ctypes
也有自己的邊界,有時(shí)候會(huì )發(fā)生一些意想不到的事情。
比如下面的例子:
>>> 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
這行代碼展開(kāi)后的步驟:
>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>
注意 temp0
和 temp1
對象始終引用了對象 rc
的內容。然后執行 rc.a = temp0
會(huì )把 temp0
的內容拷貝到 rc
的空間。這也改變了 temp1
的內容。最終導致賦值語(yǔ)句 rc.b = temp1
沒(méi)有產(chǎn)生預想的效果。
記住,訪(fǎng)問(wèn)被包含在結構體、聯(lián)合、數組中的對象并不會(huì )將其 復制 出來(lái),而是得到了一個(gè)代理對象,它是對根對象的內部?jì)热莸囊粚影b。
下面是另一個(gè)可能和預期有偏差的例子:
>>> 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
實(shí)例化的對象只能將其值設置為 bytes 或者整數。
為什么這里打印了 False
? ctypes 實(shí)例是一些內存塊加上一些用于訪(fǎng)問(wèn)這些內存塊的 descriptor 組成。將 Python 對象存儲在內存塊并不會(huì )存儲對象本身,而是存儲了對象的 內容
。每次訪(fǎng)問(wèn)對象的內容都會(huì )構造一個(gè)新的 Python 對象。
變長(cháng)數據類(lèi)型?
ctypes
對變長(cháng)數組和結構體提供了一些支持 。
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
>>>
這非常好,但是要怎么訪(fǎng)問(wèn)數組中額外的元素呢?因為數組類(lèi)型已經(jīng)定義包含4個(gè)元素,導致我們訪(fǎng)問(wèn)新增元素時(shí)會(huì )產(chǎn)生以下錯誤:
>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
...
IndexError: invalid index
>>>
使用 ctypes
訪(fǎng)問(wèn)變長(cháng)數據類(lèi)型的一個(gè)可行方法是利用 Python 的動(dòng)態(tài)特性,根據具體情況,在知道這個(gè)數據的大小后,(重新)指定這個(gè)數據的類(lèi)型。
ctypes 參考手冊?
外部函數?
正如之前小節的說(shuō)明,外部函數可作為被加載共享庫的屬性來(lái)訪(fǎng)問(wèn)。 用此方式創(chuàng )建的函數對象默認接受任意數量的參數,接受任意 ctypes 數據實(shí)例作為參數,并且返回庫加載器所指定的默認結果類(lèi)型。 它們是一個(gè)私有類(lèi)的實(shí)例:
- class ctypes._FuncPtr?
C 可調用外部函數的基類(lèi)。
外部函數的實(shí)例也是兼容 C 的數據類(lèi)型;它們代表 C 函數指針。
此行為可通過(guò)對外部函數對象的特殊屬性賦值來(lái)自定義。
- restype?
賦值為一個(gè) ctypes 類(lèi)型來(lái)指定外部函數的結果類(lèi)型。 使用
None
表示 void,即不返回任何結果的函數。賦值為一個(gè)不為 ctypes 類(lèi)型的可調用 Python 對象也是可以的,在此情況下函數應返回 C int,該可調用對象將附帶此整數被調用,以允許進(jìn)一步的處理或錯誤檢測。 這種用法已被棄用,為了更靈活的后續處理或錯誤檢測請使用一個(gè) ctypes 數據類(lèi)型作為
restype
并將errcheck
屬性賦值為一個(gè)可調用對象。
- argtypes?
賦值為一個(gè) ctypes 類(lèi)型的元組來(lái)指定函數所接受的參數類(lèi)型。 使用
stdcall
調用規范的函數只能附帶與此元組長(cháng)度相同數量的參數進(jìn)行調用;使用 C 調用規范的函數還可接受額外的未指明參數。當外部函數被調用時(shí),每個(gè)實(shí)際參數都會(huì )被傳給
argtypes
元組中條目的from_param()
類(lèi)方法,此方法允許將實(shí)際參數適配為此外部函數所接受的對象。 例如,argtypes
元組中的c_char_p
條目將使用 ctypes 約定規則把作為參數傳入的字符串轉換為字節串對象。新增:現在可以將不是 ctypes 類(lèi)型的條目放入 argtypes,但每個(gè)條目都必須具有
from_param()
方法用于返回可作為參數的值(整數、字符串、ctypes 實(shí)例)。 這樣就允許定義可將自定義對象適配為函數形參的適配器。
- errcheck?
將一個(gè) Python 函數或其他可調用對象賦值給此屬性。 該可調用對象將附帶三個(gè)及以上的參數被調用。
- callable(result, func, arguments)
result 是外部函數返回的結果,由
restype
屬性指明。func 是外部函數對象本身,這樣就允許重新使用相同的可調用對象來(lái)對多個(gè)函數進(jìn)行檢查或后續處理。
arguments 是一個(gè)包含最初傳遞給函數調用的形參的元組,這樣就允許對所用參數的行為進(jìn)行特別處理。
此函數所返回的對象將會(huì )由外部函數調用返回,但它還可以在外部函數調用失敗時(shí)檢查結果并引發(fā)異常。
- exception ctypes.ArgumentError?
此異常會(huì )在外部函數無(wú)法對某個(gè)傳入參數執行轉換時(shí)被引發(fā)。
引發(fā)一個(gè)審計事件 ctypes.seh_exception
并附帶參數 code
。
引發(fā)一個(gè)審計事件 ctypes.call_function
,附帶參數 func_pointer
, arguments
。
函數原型?
外部函數也可通過(guò)實(shí)例化函數原型來(lái)創(chuàng )建。 函數原型類(lèi)似于 C 中的函數原型;它們在不定義具體實(shí)現的情況下描述了一個(gè)函數(返回類(lèi)型、參數類(lèi)型、調用約定)。 工廠(chǎng)函數必須使用函數所需要的結果類(lèi)型和參數類(lèi)型來(lái)調用,并可被用作裝飾器工廠(chǎng)函數,在此情況下可以通過(guò) @wrapper
語(yǔ)法應用于函數。 請參閱 回調函數 了解有關(guān)示例。
- ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)?
返回的函數原型會(huì )創(chuàng )建使用標準 C 調用約定的函數。 該函數在調用過(guò)程中將釋放 GIL。 如果 use_errno 設為真值,則在調用之前和之后系統
errno
變量的 ctypes 私有副本會(huì )與真正的errno
值進(jìn)行交換;use_last_error 會(huì )為 Windows 錯誤碼執行同樣的操作。
- ctypes.WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)?
Windows only: The returned function prototype creates functions that use the
stdcall
calling convention. The function will release the GIL during the call. use_errno and use_last_error have the same meaning as above.
- ctypes.PYFUNCTYPE(restype, *argtypes)?
返回的函數原型會(huì )創(chuàng )建使用 Python 調用約定的函數。 該函數在調用過(guò)程中將 不會(huì ) 釋放 GIL。
這些工廠(chǎng)函數所創(chuàng )建的函數原型可通過(guò)不同的方式來(lái)實(shí)例化,具體取決于調用中的類(lèi)型與數量:
- prototype(address)
在指定地址上返回一個(gè)外部函數,地址值必須為整數。
- prototype(callable)
基于 Python callable 創(chuàng )建一個(gè) C 可調用函數(回調函數)。
- prototype(func_spec[, paramflags])
返回由一個(gè)共享庫導出的外部函數。 func_spec 必須為一個(gè) 2 元組
(name_or_ordinal, library)
。 第一項是字符串形式的所導出函數名稱(chēng),或小整數形式的所導出函數序號。 第二項是該共享庫實(shí)例。
- prototype(vtbl_index, name[, paramflags[, iid]])
返回將調用一個(gè) COM 方法的外部函數。 vtbl_index 虛擬函數表中的索引。 name 是 COM 方法的名稱(chēng)。 iid 是可選的指向接口標識符的指針,它被用于擴展的錯誤報告。
COM 方法使用特殊的調用約定:除了在
argtypes
元組中指定的形參,它們還要求一個(gè)指向 COM 接口的指針作為第一個(gè)參數。可選的 paramflags 形參會(huì )創(chuàng )建相比上述特性具有更多功能的外部函數包裝器。
paramflags 必須為一個(gè)與
argtypes
長(cháng)度相同的元組。此元組中的每一項都包含有關(guān)形參的更多信息,它必須為包含一個(gè)、兩個(gè)或更多條目的元組。
第一項是包含形參指令旗標組合的整數。
- 1
指定函數的一個(gè)輸入形參。
- 2
輸出形參。 外部函數會(huì )填入一個(gè)值。
- 4
默認為整數零值的輸入形參。
可選的第二項是字符串形式的形參名稱(chēng)。 如果指定此項,則可以使用該形參名稱(chēng)來(lái)調用外部函數。
可選的第三項是該形參的默認值。
這個(gè)例子演示了如何包裝 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
外部函數可以通過(guò)以下方式來(lái)調用:
>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")
第二個(gè)例子演示了輸出形參。 這個(gè) win32 GetWindowRect
函數通過(guò)將指定窗口的維度拷貝至調用者必須提供的 RECT
結構體來(lái)提取這些值。 這是相應的 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)
>>>
帶有輸出形參的函數如果輸出形參存在單一值則會(huì )自動(dòng)返回該值,或是當輸出形參存在多個(gè)值時(shí)返回包含這些值的元組,因此當 GetWindowRect 被調用時(shí)現在將返回一個(gè) RECT 實(shí)例。
輸出形參可以與 errcheck
協(xié)議相結合以執行進(jìn)一步的輸出處理與錯誤檢查。 Win32 GetWindowRect
API 函數返回一個(gè) BOOL
來(lái)表示成功或失敗,因此此函數可執行錯誤檢查,并在 API 調用失敗時(shí)引發(fā)異常:
>>> def errcheck(result, func, args):
... if not result:
... raise WinError()
... return args
...
>>> GetWindowRect.errcheck = errcheck
>>>
如果 errcheck
不加更改地返回它所接收的參數元組,則 ctypes
會(huì )繼續對輸出形參執行常規處理。 如果你希望返回一個(gè)窗口坐標的元組而非 RECT
實(shí)例,你可以從函數中提取這些字段并返回它們,常規處理將不會(huì )再執行:
>>> 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 必須為一個(gè) ctypes 類(lèi)型的實(shí)例。
引發(fā)一個(gè) 審計事件
ctypes.addressof
,附帶參數obj
。
- ctypes.alignment(obj_or_type)?
返回一個(gè) ctypes 類(lèi)型的對齊要求。 obj_or_type 必須為一個(gè) ctypes 類(lèi)型或實(shí)例。
- ctypes.byref(obj[, offset])?
返回指向 obj 的輕量指針,該對象必須為一個(gè) ctypes 類(lèi)型的實(shí)例。 offset 默認值為零,且必須為一個(gè)將被添加到內部指針值的整數。
byref(obj, offset)
對應于這段 C 代碼:(((char *)&obj) + offset)
返回的對象只能被用作外部函數調用形參。 它的行為類(lèi)似于
pointer(obj)
,但構造起來(lái)要快很多。
- ctypes.cast(obj, type)?
此函數類(lèi)似于 C 的強制轉換運算符。 它返回一個(gè) type 的新實(shí)例,該實(shí)例指向與 obj 相同的內存塊。 type 必須為指針類(lèi)型,而 obj 必須為可以被作為指針來(lái)解讀的對象。
- ctypes.create_string_buffer(init_or_size, size=None)?
此函數會(huì )創(chuàng )建一個(gè)可變的字符緩沖區。 返回的對象是一個(gè)
c_char
的 ctypes 數組。init_or_size 必須是一個(gè)指明數組大小的整數,或者是一個(gè)將被用來(lái)初始化數組條目的字節串對象。
如果將一個(gè)字節串對象指定為第一個(gè)參數,則將使緩沖區大小比其長(cháng)度多一項以便數組的最后一項為一個(gè) NUL 終結符。 可以傳入一個(gè)整數作為第二個(gè)參數以允許在不使用字節串長(cháng)度的情況下指定數組大小。
引發(fā)一個(gè) 審計事件
ctypes.create_string_buffer
,附帶參數init
,size
。
- ctypes.create_unicode_buffer(init_or_size, size=None)?
此函數會(huì )創(chuàng )建一個(gè)可變的 unicode 字符緩沖區。 返回的對象是一個(gè)
c_wchar
的 ctypes 數組。init_or_size 必須是一個(gè)指明數組大小的整數,或者是一個(gè)將被用來(lái)初始化數組條目的字符串。
如果將一個(gè)字符串指定為第一個(gè)參數,則將使緩沖區大小比其長(cháng)度多一項以便數組的最后一項為一個(gè) NUL 終結符。 可以傳入一個(gè)整數作為第二個(gè)參數以允許在不使用字符串長(cháng)度的情況下指定數組大小。
引發(fā)一個(gè) 審計事件
ctypes.create_unicode_buffer
,附帶參數init
,size
。
- ctypes.DllCanUnloadNow()?
僅限 Windows:此函數是一個(gè)允許使用 ctypes 實(shí)現進(jìn)程內 COM 服務(wù)的鉤子。 它將由 _ctypes 擴展 dll 所導出的 DllCanUnloadNow 函數來(lái)調用。
- ctypes.DllGetClassObject()?
僅限 Windows:此函數是一個(gè)允許使用 ctypes 實(shí)現進(jìn)程內 COM 服務(wù)的鉤子。 它將由
_ctypes
擴展 dll 所導出的 DllGetClassObject 函數來(lái)調用。
- ctypes.util.find_library(name)?
嘗試尋找一個(gè)庫并返回路徑名稱(chēng)。 name 是庫名稱(chēng)并且不帶任何前綴如
lib
以及后綴如.so
,.dylib
或版本號(形式與 posix 鏈接器選項-l
所用的一致)。 如果找不到庫,則返回None
。確切的功能取決于系統。
- ctypes.util.find_msvcrt()?
僅限 Windows:返回 Python 以及擴展模塊所使用的 VC 運行時(shí)庫的文件名。 如果無(wú)法確定庫名稱(chēng),則返回
None
。如果你需要通過(guò)調用
free(void *)
來(lái)釋放內存,例如某個(gè)擴展模塊所分配的內存,重要的一點(diǎn)是你應當使用分配內存的庫中的函數。
- ctypes.FormatError([code])?
僅限 Windows:返回錯誤碼 code 的文本描述。 如果未指定錯誤碼,則會(huì )通過(guò)調用 Windows api 函數 GetLastError 來(lái)獲得最新的錯誤碼。
- ctypes.GetLastError()?
僅限 Windows:返回 Windows 在調用線(xiàn)程中設置的最新錯誤碼。 此函數會(huì )直接調用 Windows GetLastError() 函數,它并不返回錯誤碼的 ctypes 私有副本。
- ctypes.get_errno()?
返回調用線(xiàn)程中系統
errno
變量的 ctypes 私有副本的當前值。引發(fā)一個(gè) 審計事件
ctypes.get_errno
,不附帶任何參數。
- ctypes.get_last_error()?
僅限 Windows:返回調用線(xiàn)程中系統
LastError
變量的 ctypes 私有副本的當前值。引發(fā)一個(gè) 審計事件
ctypes.get_last_error
,不附帶任何參數。
- ctypes.memmove(dst, src, count)?
與標準 C memmove 庫函數相同:將 count 個(gè)字節從 src 拷貝到 dst。 dst 和 src 必須為整數或可被轉換為指針的 ctypes 實(shí)例。
- ctypes.memset(dst, c, count)?
與標準 C memset 庫函數相同:將位于地址 dst 的內存塊用 count 個(gè)字節的 c 值填充。 dst 必須為指定地址的整數或 ctypes 實(shí)例。
- ctypes.POINTER(type)?
這個(gè)工廠(chǎng)函數創(chuàng )建并返回一個(gè)新的 ctypes 指針類(lèi)型。 指針類(lèi)型會(huì )被緩存并在內部重用,因此重復調用此函數耗費不大。 type 必須為 ctypes 類(lèi)型。
- ctypes.pointer(obj)?
此函數會(huì )創(chuàng )建一個(gè)新的指向 obj 的指針實(shí)例。 返回的對象類(lèi)型為
POINTER(type(obj))
。注意:如果你只是想向外部函數調用傳遞一個(gè)對象指針,你應當使用更為快速的
byref(obj)
。
- ctypes.resize(obj, size)?
此函數可改變 obj 的內部?jì)却婢彌_區大小,其參數必須為 ctypes 類(lèi)型的實(shí)例。 沒(méi)有可能將緩沖區設為小于對象類(lèi)型的本機大小值,該值由
sizeof(type(obj))
給出,但將緩沖區加大則是可能的。
- ctypes.set_errno(value)?
設置調用線(xiàn)程中系統
errno
變量的 ctypes 私有副本的當前值為 value 并返回原來(lái)的值。引發(fā)一個(gè) 審計事件
ctypes.set_errno
附帶參數errno
。
- ctypes.set_last_error(value)?
僅限 Windows:設置調用線(xiàn)程中系統
LastError
變量的 ctypes 私有副本的當前值為 value 并返回原來(lái)的值。引發(fā)一個(gè) 審計事件
ctypes.set_last_error
,附帶參數error
。
- ctypes.sizeof(obj_or_type)?
返回 ctypes 類(lèi)型或實(shí)例的內存緩沖區以字節表示的大小。 其功能與 C
sizeof
運算符相同。
- ctypes.string_at(address, size=- 1)?
此函數返回從內存地址 address 開(kāi)始的以字節串表示的 C 字符串。 如果指定了 size,則將其用作長(cháng)度,否則將假定字符串以零值結尾。
引發(fā)一個(gè) 審計事件
ctypes.string_at
,附帶參數address
,size
。
- ctypes.WinError(code=None, descr=None)?
僅限 Windows:此函數可能是 ctypes 中名字起得最差的函數。 它會(huì )創(chuàng )建一個(gè) OSError 的實(shí)例。 如果未指定 code,則會(huì )調用
GetLastError
來(lái)確定錯誤碼。 如果未指定 descr,則會(huì )調用FormatError()
來(lái)獲取錯誤的文本描述。在 3.3 版更改: 以前是會(huì )創(chuàng )建一個(gè)
WindowsError
的實(shí)例。
數據類(lèi)型?
- class ctypes._CData?
這個(gè)非公有類(lèi)是所有 ctypes 數據類(lèi)型的共同基類(lèi)。 另外,所有 ctypes 類(lèi)型的實(shí)例都包含一個(gè)存放 C 兼容數據的內存塊;該內存塊的地址可由
addressof()
輔助函數返回。 還有一個(gè)實(shí)例變量被公開(kāi)為_objects
;此變量包含其他在內存塊包含指針的情況下需要保持存活的 Python 對象。ctypes 數據類(lèi)型的通用方法,它們都是類(lèi)方法(嚴謹地說(shuō),它們是 metaclass 的方法):
- from_buffer(source[, offset])?
此方法返回一個(gè)共享 source 對象緩沖區的 ctypes 實(shí)例。 source 對象必須支持可寫(xiě)緩沖區接口。 可選的 offset 形參指定以字節表示的源緩沖區內偏移量;默認值為零。 如果源緩沖區不夠大則會(huì )引發(fā)
ValueError
。引發(fā)一個(gè) 審計事件
ctypes.cdata/buffer
附帶參數pointer
,size
,offset
。
- from_buffer_copy(source[, offset])?
此方法創(chuàng )建一個(gè) ctypes 實(shí)例,從 source 對象緩沖區拷貝緩沖區,該對象必須是可讀的。 可選的 offset 形參指定以字節表示的源緩沖區內偏移量;默認值為零。 如果源緩沖區不夠大則會(huì )引發(fā)
ValueError
。引發(fā)一個(gè) 審計事件
ctypes.cdata/buffer
附帶參數pointer
,size
,offset
。
- from_address(address)?
此方法會(huì )使用 address 所指定的內存返回一個(gè) ctypes 類(lèi)型的實(shí)例,該參數必須為一個(gè)整數。
引發(fā)一個(gè) 審計事件
ctypes.cdata
,附帶參數address
。
- from_param(obj)?
此方法會(huì )將 obj 適配為一個(gè) ctypes 類(lèi)型。 它調用時(shí)會(huì )在當該類(lèi)型存在于外部函數的
argtypes
元組時(shí)傳入外部函數調用所使用的實(shí)際對象;它必須返回一個(gè)可被用作函數調用參數的對象。所有 ctypes 數據類(lèi)型都帶有這個(gè)類(lèi)方法的默認實(shí)現,它通常會(huì )返回 obj,如果該對象是此類(lèi)型的實(shí)例的話(huà)。 某些類(lèi)型也能接受其他對象。
- in_dll(library, name)?
此方法返回一個(gè)由共享庫導出的 ctypes 類(lèi)型。 name 為導出數據的符號名稱(chēng),library 為所加載的共享庫。
ctypes 數據類(lèi)型的通用實(shí)例變量:
- _b_base_?
有時(shí) ctypes 數據實(shí)例并不擁有它們所包含的內存塊,它們只是共享了某個(gè)基對象的部分內存塊。
_b_base_
只讀成員是擁有內存塊的根 ctypes 對象。
- _b_needsfree_?
這個(gè)只讀變量在 ctypes 數據實(shí)例自身已分配了內存塊時(shí)為真值,否則為假值。
- _objects?
這個(gè)成員或者為
None
,或者為一個(gè)包含需要保持存活以使內存塊的內存保持有效的 Python 對象的字典。 這個(gè)對象只是出于調試目的而對外公開(kāi);絕對不要修改此字典的內容。
基礎數據類(lèi)型?
- class ctypes._SimpleCData?
這個(gè)非公有類(lèi)是所有基本 ctypes 數據類(lèi)型的基類(lèi)。 它在這里被提及是因為它包含基本 ctypes 數據類(lèi)型共有的屬性。
_SimpleCData
是_CData
的子類(lèi),因此繼承了其方法和屬性。 非指針及不包含指針的 ctypes 數據類(lèi)型現在將可以被封存。實(shí)例擁有一個(gè)屬性:
基本數據類(lèi)型當作為外部函數調用結果被返回或者作為結構字段成員或數組項被提取時(shí),會(huì )透明地轉換為原生 Python 類(lèi)型。 換句話(huà)說(shuō),如果某個(gè)外部函數具有 c_char_p
的 restype
,你將總是得到一個(gè) Python 字節串對象,而 不是 一個(gè) c_char_p
實(shí)例。
基本數據類(lèi)型的子類(lèi)并 沒(méi)有 繼續此行為。 因此,如果一個(gè)外部函數的 restype
是 c_void_p
的一個(gè)子類(lèi),你將從函數調用得到一個(gè)該子類(lèi)的實(shí)例。 當然,你可以通過(guò)訪(fǎng)問(wèn) value
屬性來(lái)獲取指針的值。
這些是基本 ctypes 數據類(lèi)型:
- class ctypes.c_byte?
代表 C signed char 數據類(lèi)型,并將值解讀為一個(gè)小整數。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_char?
代表 C char 數據類(lèi)型,并將值解讀為單個(gè)字符。 該構造器接受一個(gè)可選的字符串初始化器,字符串的長(cháng)度必須恰好為一個(gè)字符。
- class ctypes.c_char_p?
當指向一個(gè)以零為結束符的字符串時(shí)代表 C char* 數據類(lèi)型。 對于通用字符指針來(lái)說(shuō)也可能指向二進(jìn)制數據,必須要使用
POINTER(c_char)
。 該構造器接受一個(gè)整數地址,或者一個(gè)字節串對象。
- class ctypes.c_double?
代表 C double 數據類(lèi)型。 該構造器接受一個(gè)可選的浮點(diǎn)數初始化器。
- class ctypes.c_longdouble?
代表 C long double 數據類(lèi)型。 該構造器接受一個(gè)可選的浮點(diǎn)數初始化器。 在
sizeof(long double) == sizeof(double)
的平臺上它是c_double
的一個(gè)別名。
- class ctypes.c_float?
代表 C float 數據類(lèi)型。 該構造器接受一個(gè)可選的浮點(diǎn)數初始化器。
- class ctypes.c_int?
代表 C signed int 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。 在
sizeof(int) == sizeof(long)
的平臺上它是c_long
的一個(gè)別名。
- class ctypes.c_int64?
代表 C 64 位 signed int 數據類(lèi)型。 通常是
c_longlong
的一個(gè)別名。
- class ctypes.c_long?
代表 C signed long 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_longlong?
代表 C signed long long 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_short?
代表 C signed short 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_size_t?
代表 C
size_t
數據類(lèi)型。
- class ctypes.c_ssize_t?
代表 C
ssize_t
數據類(lèi)型。3.2 新版功能.
- class ctypes.c_ubyte?
代表 C unsigned char 數據類(lèi)型,它將值解讀為一個(gè)小整數。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_uint?
代表 C unsigned int 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。 在
sizeof(int) == sizeof(long)
的平臺上它是c_ulong
的一個(gè)別名。
- class ctypes.c_uint64?
代表 C 64 位 unsigned int 數據類(lèi)型。 通常是
c_ulonglong
的一個(gè)別名。
- class ctypes.c_ulong?
代表 C unsigned long 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_ulonglong?
代表 C unsigned long long 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_ushort?
代表 C unsigned short 數據類(lèi)型。 該構造器接受一個(gè)可選的整數初始化器;不會(huì )執行溢出檢查。
- class ctypes.c_void_p?
代表 C void* 類(lèi)型。 該值被表示為整數形式。 該構造器接受一個(gè)可選的整數初始化器。
- class ctypes.c_wchar?
代表 C
wchar_t
數據類(lèi)型,并將值解讀為一單個(gè)字符的 unicode 字符串。 該構造器接受一個(gè)可選的字符串初始化器,字符串的長(cháng)度必須恰好為一個(gè)字符。
- class ctypes.c_wchar_p?
代表 C wchar_t* 數據類(lèi)型,它必須為指向以零為結束符的寬字符串的指針。 該構造器接受一個(gè)整數地址或者一個(gè)字符串。
- class ctypes.c_bool?
代表 C bool 數據類(lèi)型 (更準確地說(shuō)是 C99 _Bool)。 它的值可以為
True
或False
,并且該構造器接受任何具有邏輯值的對象。
- class ctypes.HRESULT?
Windows 專(zhuān)屬:代表一個(gè)
HRESULT
值,它包含某個(gè)函數或方法調用的成功或錯誤信息。
ctypes.wintypes
模塊提供了其他許多 Windows 專(zhuān)屬的數據類(lèi)型,例如 HWND
, WPARAM
或 DWORD
。 還定義了一些有用的結構體例如 MSG
或 RECT
。
結構化數據類(lèi)型?
- class ctypes.Union(*args, **kw)?
本機字節序的聯(lián)合所對應的抽象基類(lèi)。
- class ctypes.BigEndianUnion(*args, **kw)?
Abstract base class for unions in big endian byte order.
3.11 新版功能.
- class ctypes.LittleEndianUnion(*args, **kw)?
Abstract base class for unions in little endian byte order.
3.11 新版功能.
- class ctypes.BigEndianStructure(*args, **kw)?
大端 字節序的結構體所對應的抽象基類(lèi)。
- class ctypes.LittleEndianStructure(*args, **kw)?
小端 字節序的結構體所對應的抽象基類(lèi)。
Structures and unions with non-native byte order cannot contain pointer type fields, or any other data types containing pointer type fields.
- class ctypes.Structure(*args, **kw)?
本機 字節序的結構體所對應的抽象基類(lèi)。
實(shí)際的結構體和聯(lián)合類(lèi)型必須通過(guò)子類(lèi)化這些類(lèi)型之一來(lái)創(chuàng )建,并且至少要定義一個(gè)
_fields_
類(lèi)變量。ctypes
將創(chuàng )建 descriptor,它允許通過(guò)直接屬性訪(fǎng)問(wèn)來(lái)讀取和寫(xiě)入字段。 這些是- _fields_?
一個(gè)定義結構體字段的序列。 其中的條目必須為 2 元組或 3 元組。 元組的第一項是字段名稱(chēng),第二項指明字段類(lèi)型;它可以是任何 ctypes 數據類(lèi)型。
對于整數類(lèi)型字段例如
c_int
,可以給定第三個(gè)可選項。 它必須是一個(gè)定義字段比特位寬度的小正整數。字段名稱(chēng)在一個(gè)結構體或聯(lián)合中必須唯一。 不會(huì )檢查這個(gè)唯一性,但當名稱(chēng)出現重復時(shí)將只有一個(gè)字段可被訪(fǎng)問(wèn)。
可以在定義 Structure 子類(lèi)的類(lèi)語(yǔ)句 之后 再定義
_fields_
類(lèi)變量,這將允許創(chuàng )建直接或間接引用其自身的數據類(lèi)型:class List(Structure): pass List._fields_ = [("pnext", POINTER(List)), ... ]
但是,
_fields_
類(lèi)變量必須在類(lèi)型第一次被使用(創(chuàng )建實(shí)例,調用sizeof()
等等)之前進(jìn)行定義。 在此之后對_fields_
類(lèi)變量賦值將會(huì )引發(fā) AttributeError。可以定義結構體類(lèi)型的子類(lèi),它們會(huì )繼承基類(lèi)的字段再加上在子類(lèi)中定義的任何
_fields_
。
- _pack_?
一個(gè)可選的小整數,它允許覆蓋實(shí)體中結構體字段的對齊方式。 當
_fields_
被賦值時(shí)必須已經(jīng)定義了_pack_
,否則它將沒(méi)有效果。
- _anonymous_?
一個(gè)可選的序列,它會(huì )列出未命名(匿名)字段的名稱(chēng)。 當
_fields_
被賦值時(shí)必須已經(jīng)定義了_anonymous_
,否則它將沒(méi)有效果。在此變量中列出的字段必須為結構體或聯(lián)合類(lèi)型字段。
ctypes
將在結構體類(lèi)型中創(chuàng )建描述器以允許直接訪(fǎng)問(wèn)嵌套字段,而無(wú)需創(chuàng )建對應的結構體或聯(lián)合字段。以下是一個(gè)示例類(lèi)型(Windows):
class _U(Union): _fields_ = [("lptdesc", POINTER(TYPEDESC)), ("lpadesc", POINTER(ARRAYDESC)), ("hreftype", HREFTYPE)] class TYPEDESC(Structure): _anonymous_ = ("u",) _fields_ = [("u", _U), ("vt", VARTYPE)]
TYPEDESC
結構體描述了一個(gè) COM 數據類(lèi)型,vt
字段指明哪個(gè)聯(lián)合字段是有效的。 由于u
字段被定義為匿名字段,現在可以直接從 TYPEDESC 實(shí)例訪(fǎng)問(wèn)成員。td.lptdesc
和td.u.lptdesc
是等價(jià)的,但前者速度更快,因為它不需要創(chuàng )建臨時(shí)的聯(lián)合實(shí)例:td = TYPEDESC() td.vt = VT_PTR td.lptdesc = POINTER(some_type) td.u.lptdesc = POINTER(some_type)
可以定義結構體的子類(lèi),它們會(huì )繼承基類(lèi)的字段。 如果子類(lèi)定義具有單獨的
_fields_
變量,在其中指定的字段會(huì )被添加到基類(lèi)的字段中。結構體和聯(lián)合的構造器均可接受位置和關(guān)鍵字參數。 位置參數用于按照
_fields_
中的出現順序來(lái)初始化成員字段。 構造器中的關(guān)鍵字參數會(huì )被解讀為屬性賦值,因此它們將以相應的名稱(chēng)來(lái)初始化_fields_
,或為不存在于_fields_
中的名稱(chēng)創(chuàng )建新的屬性。
數組與指針?
- class ctypes.Array(*args)?
數組的抽象基類(lèi)。
The recommended way to create concrete array types is by multiplying any
ctypes
data type with a non-negative integer. Alternatively, you can subclass this type and define_length_
and_type_
class variables. Array elements can be read and written using standard subscript and slice accesses; for slice reads, the resulting object is not itself anArray
.- _length_?
一個(gè)指明數組中元素數量的正整數。 超出范圍的抽取會(huì )導致
IndexError
。 該值將由len()
返回。
- _type_?
指明數組中每個(gè)元素的類(lèi)型。
Array 子類(lèi)構造器可接受位置參數,用來(lái)按順序初始化元素。
- class ctypes._Pointer?
私有對象,指針的抽象基類(lèi)。
實(shí)際的指針類(lèi)型是通過(guò)調用
POINTER()
并附帶其將指向的類(lèi)型來(lái)創(chuàng )建的;這會(huì )由pointer()
自動(dòng)完成。如果一個(gè)指針指向的是數組,則其元素可使用標準的抽取和切片方式來(lái)讀寫(xiě)。 指針對象沒(méi)有長(cháng)度,因此
len()
將引發(fā)TypeError
。 抽取負值將會(huì )從指針 之前 的內存中讀?。ㄅc C 一樣),并且超出范圍的抽取將可能因非法訪(fǎng)問(wèn)而導致崩潰(視你的運氣而定)。- _type_?
指明所指向的類(lèi)型。
- contents?
返回指針所指向的對象。 對此屬性賦值會(huì )使指針改為指向所賦值的對象。