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)鏈接庫中的函數?

通過(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)庫,比如 kernel32user32,通常會(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)幫你決定選擇哪一種函數,你必須顯式的調用 GetModuleHandleAGetModuleHandleW,并分別使用字節對象或字符串對象作參數。

有時(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)型

c_bool

_Bool

bool (1)

c_char

char

單字符字節串對象

c_wchar

wchar_t

單字符字符串

c_byte

char

int

c_ubyte

unsigned char

int

c_short

short

int

c_ushort

unsigned short

int

c_int

int

int

c_uint

unsigned int

int

c_long

long

int

c_ulong

unsigned long

int

c_longlong

__int64long long

int

c_ulonglong

unsigned __int64unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_tPy_ssize_t

int

c_float

float

float

c_double

double

float

c_longdouble

long double

float

c_char_p

char* (以 NUL 結尾)

字節串對象或 None

c_wchar_p

wchar_t* (以 NUL 結尾)

字符串或 None

c_void_p

void*

int 或 None

  1. 構造函數接受任何具有真值的對象。

所有這些類(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_pc_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í)例只能在控制臺提示符下工作,而不能在 IDLEPythonWin 中運行。

>>>
>>> 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 模塊中的 StructureUnion 。子類(lèi)必須定義 _fields_ 屬性。 _fields_ 是一個(gè)二元組列表,二元組中包含 field namefield type 。

type 字段必須是一個(gè) ctypes 類(lèi)型,比如 c_int,或者其他 ctypes 類(lèi)型: 結構體、聯(lián)合、數組、指針。

這是一個(gè)簡(jiǎn)單的 POINT 結構體,它包含名稱(chēng)為 xy 的兩個(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 ,分別叫 upperleftlowerright:

>>>
>>> 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() 函數提供了相同的功能。 上面的結構體 Barvalue 字段接收 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)給結構體 Barvalues 字段賦值:

>>>
>>> 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
>>>

注意 temp0temp1 對象始終引用了對象 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 參考手冊?

尋找動(dòng)態(tài)鏈接庫?

在編譯型語(yǔ)言中,動(dòng)態(tài)鏈接庫會(huì )在編譯、鏈接或者程序運行時(shí)訪(fǎng)問(wèn)。

The purpose of the find_library() function is to locate a library in a way similar to what the compiler or runtime loader does (on platforms with several versions of a shared library the most recent should be loaded), while the ctypes library loaders act like when a program is run, and call the runtime loader directly.

ctypes.util 模塊提供了一個(gè)函數,可以幫助確定需要加載的庫。

ctypes.util.find_library(name)

嘗試尋找一個(gè)庫然后返回其路徑名, name 是庫名稱(chēng), 且去除了 lib 等前綴和 .so 、 .dylib 、版本號等后綴(這是 posix 連接器 -l 選項使用的格式)。如果沒(méi)有找到對應的庫,則返回 None 。

確切的功能取決于系統。

在 Linux 上, find_library() 會(huì )嘗試運行外部程序(/sbin/ldconfig, gcc, objdump 以及 ld) 來(lái)尋找庫文件。返回庫文件的文件名。

在 3.6 版更改: 在Linux 上,如果其他方式找不到的話(huà),會(huì )使用環(huán)境變量 LD_LIBRARY_PATH 搜索動(dòng)態(tài)鏈接庫。

這是一些例子:

>>>
>>> from ctypes.util import find_library
>>> find_library("m")
'libm.so.6'
>>> find_library("c")
'libc.so.6'
>>> find_library("bz2")
'libbz2.so.1.0'
>>>

On macOS, find_library() tries several predefined naming schemes and paths to locate the library, and returns a full pathname if successful:

>>>
>>> from ctypes.util import find_library
>>> find_library("c")
'/usr/lib/libc.dylib'
>>> find_library("m")
'/usr/lib/libm.dylib'
>>> find_library("bz2")
'/usr/lib/libbz2.dylib'
>>> find_library("AGL")
'/System/Library/Frameworks/AGL.framework/AGL'
>>>

在 Windows 上, find_library() 在系統路徑中搜索,然后返回全路徑,但是如果沒(méi)有預定義的命名方案, find_library("c") 調用會(huì )返回 None

使用 ctypes 包裝動(dòng)態(tài)鏈接庫,更好的方式 可能 是在開(kāi)發(fā)的時(shí)候就確定名稱(chēng),然后硬編碼到包裝模塊中去,而不是在運行時(shí)使用 find_library() 尋找庫。

加載動(dòng)態(tài)鏈接庫?

有很多方式可以將動(dòng)態(tài)鏈接庫加載到 Python 進(jìn)程。其中之一是實(shí)例化以下類(lèi)的其中一個(gè):

class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None)?

此類(lèi)的實(shí)例即已加載的動(dòng)態(tài)鏈接庫。庫中的函數使用標準 C 調用約定,并假定返回 int 。

在 Windows 上創(chuàng )建 CDLL 實(shí)例可能會(huì )失敗,即使 DLL 名稱(chēng)確實(shí)存在。 當某個(gè)被加載 DLL 所依賴(lài)的 DLL 未找到時(shí),將引發(fā) OSError 錯誤并附帶消息 "[WinError 126] The specified module could not be found". 此錯誤消息不包含缺失 DLL 的名稱(chēng),因為 Windows API 并不會(huì )返回此類(lèi)信息,這使得此錯誤難以診斷。 要解決此錯誤并確定是哪一個(gè) DLL 未找到,你需要找出所依賴(lài)的 DLL 列表并使用 Windows 調試與跟蹤工具確定是哪一個(gè)未找到。

參見(jiàn)

Microsoft DUMPBIN 工具 -- 一個(gè)用于查找 DLL 依賴(lài)的工具。

class ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None)?

僅 Windows : 此類(lèi)的實(shí)例即加載好的動(dòng)態(tài)鏈接庫,其中的函數使用 stdcall 調用約定,并且假定返回 windows 指定的 HRESULT 返回碼。 HRESULT 的值包含的信息說(shuō)明函數調用成功還是失敗,以及額外錯誤碼。 如果返回值表示失敗,會(huì )自動(dòng)拋出 OSError 異常。

在 3.3 版更改: 以前是引發(fā) WindowsError。

class ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None)?

僅 Windows: 此類(lèi)的實(shí)例即加載好的動(dòng)態(tài)鏈接庫,其中的函數使用 stdcall 調用約定,并假定默認返回 int 。

調用動(dòng)態(tài)庫導出的函數之前,Python會(huì )釋放 global interpreter lock ,并在調用后重新獲取。

class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None)?

這個(gè)類(lèi)實(shí)例的行為與 CDLL 類(lèi)似,只不過(guò) 不會(huì ) 在調用函數的時(shí)候釋放 GIL 鎖,且調用結束后會(huì )檢查 Python 錯誤碼。 如果錯誤碼被設置,會(huì )拋出一個(gè) Python 異常。

所以,它只在直接調用 Python C 接口函數的時(shí)候有用。

通過(guò)使用至少一個(gè)參數(共享庫的路徑名)調用它們,可以實(shí)例化所有這些類(lèi)。也可以傳入一個(gè)已加載的動(dòng)態(tài)鏈接庫作為 handler 參數,其他情況會(huì )調用系統底層的 dlopenLoadLibrary 函數將庫加載到進(jìn)程,并獲取其句柄。

mode 可以指定庫加載方式。詳情請參見(jiàn) dlopen(3) 手冊頁(yè)。 在 Windows 上, 會(huì )忽略 mode ,在 posix 系統上, 總是會(huì )加上 RTLD_NOW ,且無(wú)法配置。

use_errno 參數如果設置為 true,可以啟用ctypes的機制,通過(guò)一種安全的方法獲取系統的 errno 錯誤碼。 ctypes 維護了一個(gè)線(xiàn)程局部變量,它是系統 errno 的一份拷貝;如果調用了使用 use_errno=True 創(chuàng )建的外部函數, errno 的值會(huì )與 ctypes 自己拷貝的那一份進(jìn)行交換,函數執行完后立即再交換一次。

The function ctypes.get_errno() returns the value of the ctypes private copy, and the function ctypes.set_errno() changes the ctypes private copy to a new value and returns the former value.

use_last_error 參數如果設置為 true,可以在 Windows 上啟用相同的策略,它是通過(guò) Windows API 函數 GetLastError()  和 SetLastError() 管理的。 ctypes.get_last_error()ctypes.set_last_error() 可用于獲取和設置 ctypes 自己維護的 windows 錯誤碼拷貝。

winmode 參數用于在 Windows 平臺上指定庫的加載方式( 因為 mode 會(huì )被忽略)。他接受任何與 Win32 API 的 LoadLibraryEx 的標志兼容的值作為參數。省略時(shí),默認設置使用最安全的DLL加載的標志,以避免DLL劫持等問(wèn)題。傳入 DLL 的全路徑是保證正確加載庫及其依賴(lài)最安全的方法。

在 3.8 版更改: 增加了 winmode 參數。

ctypes.RTLD_GLOBAL

用于 mode 參數的標識值。在此標識不可用的系統上,它被定義為整數0。

ctypes.RTLD_LOCAL

Flag to use as mode parameter. On platforms where this is not available, it is the same as RTLD_GLOBAL.

ctypes.DEFAULT_MODE

加載動(dòng)態(tài)鏈接庫的默認模式。在 OSX 10.3 上,它是 RTLD_GLOBAL ,其余系統上是 RTLD_LOCAL 。

這些類(lèi)的實(shí)例沒(méi)有共用方法。動(dòng)態(tài)鏈接庫的導出函數可以通過(guò)屬性或者索引的方式訪(fǎng)問(wèn)。注意,通過(guò)屬性的方式訪(fǎng)問(wèn)會(huì )緩存這個(gè)函數,因而每次訪(fǎng)問(wèn)它時(shí)返回的都是同一個(gè)對象。另一方面,通過(guò)索引訪(fǎng)問(wèn),每次都會(huì )返回一個(gè)新的對象:

>>>
>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

還有下面這些屬性可用,他們的名稱(chēng)以下劃線(xiàn)開(kāi)頭,以避免和導出函數重名:

PyDLL._handle?

用于訪(fǎng)問(wèn)庫的系統句柄。

PyDLL._name?

傳入構造函數的庫名稱(chēng)。

共享庫也可以通用使用一個(gè)預制對象來(lái)加載,這種對象是 LibraryLoader 類(lèi)的實(shí)例,具體做法或是通過(guò)調用 LoadLibrary() 方法,或是通過(guò)將庫作為加載器實(shí)例的屬性來(lái)提取。

class ctypes.LibraryLoader(dlltype)?

加載共享庫的類(lèi)。 dlltype 應當為 CDLL, PyDLL, WinDLLOleDLL 類(lèi)型之一。

__getattr__() 具有特殊的行為:它允許通過(guò)將一個(gè)共享庫作為庫加載器實(shí)例的屬性進(jìn)行訪(fǎng)問(wèn)來(lái)加載它。 加載結果將被緩存,因此重復的屬性訪(fǎng)問(wèn)每次都會(huì )返回相同的庫。

LoadLibrary(name)?

加載一個(gè)共享庫到進(jìn)程中并將其返回。 此方法總是返回一個(gè)新的庫實(shí)例。

可用的預制庫加載器有如下這些:

ctypes.cdll

創(chuàng )建 CDLL 實(shí)例。

ctypes.windll

僅限 Windows:創(chuàng )建 WinDLL 實(shí)例.

ctypes.oledll

僅限 Windows:創(chuàng )建 OleDLL 實(shí)例。

ctypes.pydll

創(chuàng )建 PyDLL 實(shí)例。

要直接訪(fǎng)問(wèn) C Python api,可以使用一個(gè)現成的 Python 共享庫對象:

ctypes.pythonapi

一個(gè) PyDLL 的實(shí)例,它將 Python C API 函數作為屬性公開(kāi)。 請注意所有這些函數都應返回 C int,當然這也不是絕對的,因此你必須分配正確的 restype 屬性以使用這些函數。

引發(fā)一個(gè) 審計事件 ctypes.dlopen,附帶參數 name。

引發(fā)一個(gè)審計事件 ctypes.dlsym,附帶參數 library, name。

引發(fā)一個(gè)審計事件 ctypes.dlsym/handle,附帶參數 handle, name。

外部函數?

正如之前小節的說(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。 dstsrc 必須為整數或可被轉換為指針的 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í)例。

ctypes.wstring_at(address, size=- 1)?

此函數返回從內存地址 address 開(kāi)始的以字符串表示的寬字節字符串。 如果指定了 size,則將其用作字符串中的字符數量,否則將假定字符串以零值結尾。

引發(fā)一個(gè) 審計事件 ctypes.wstring_at,附帶參數 address, size。

數據類(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è)屬性:

value?

這個(gè)屬性包含實(shí)例的實(shí)際值。 對于整數和指針類(lèi)型,它是一個(gè)整數,對于字符類(lèi)型,它是一個(gè)單字符字符串對象或字符串,對于字符指針類(lèi)型,它是一個(gè) Python 字節串對象或字符串。

當從 ctypes 實(shí)例提取 value 屬性時(shí),通常每次會(huì )返回一個(gè)新的對象。 ctypes沒(méi)有 實(shí)現原始對象返回,它總是會(huì )構造一個(gè)新的對象。 所有其他 ctypes 對象實(shí)例也同樣如此。

基本數據類(lèi)型當作為外部函數調用結果被返回或者作為結構字段成員或數組項被提取時(shí),會(huì )透明地轉換為原生 Python 類(lèi)型。 換句話(huà)說(shuō),如果某個(gè)外部函數具有 c_char_prestype,你將總是得到一個(gè) Python 字節串對象,而 不是 一個(gè) c_char_p 實(shí)例。

基本數據類(lèi)型的子類(lèi)并 沒(méi)有 繼續此行為。 因此,如果一個(gè)外部函數的 restypec_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_int8?

代表 C 8 位 signed int 數據類(lèi)型。 通常是 c_byte 的一個(gè)別名。

class ctypes.c_int16?

代表 C 16 位 signed int 數據類(lèi)型。 通常是 c_short 的一個(gè)別名。

class ctypes.c_int32?

代表 C 32 位 signed int 數據類(lèi)型。 通常是 c_int 的一個(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_uint8?

代表 C 8 位 unsigned int 數據類(lèi)型。 通常是 c_ubyte 的一個(gè)別名。

class ctypes.c_uint16?

代表 C 16 位 unsigned int 數據類(lèi)型。 通常是 c_ushort 的一個(gè)別名。

class ctypes.c_uint32?

代表 C 32 位 unsigned int 數據類(lèi)型。 通常是 c_uint 的一個(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)。 它的值可以為 TrueFalse,并且該構造器接受任何具有邏輯值的對象。

class ctypes.HRESULT?

Windows 專(zhuān)屬:代表一個(gè) HRESULT 值,它包含某個(gè)函數或方法調用的成功或錯誤信息。

class ctypes.py_object?

代表 C PyObject* 數據類(lèi)型。 不帶參數地調用此構造器將創(chuàng )建一個(gè) NULL PyObject* 指針。

ctypes.wintypes 模塊提供了其他許多 Windows 專(zhuān)屬的數據類(lèi)型,例如 HWND, WPARAMDWORD。 還定義了一些有用的結構體例如 MSGRECT。

結構化數據類(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.lptdesctd.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 an Array.

_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ì )使指針改為指向所賦值的對象。