日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
創(chuàng)新互聯(lián)Python教程:ctypes —- Python 的外部函數(shù)庫

ctypes —- python 的外部函數(shù)庫


ctypes 是 Python 的外部函數(shù)庫。它提供了與 C 兼容的數(shù)據(jù)類型,并允許調(diào)用 DLL 或共享庫中的函數(shù)。可使用該模塊以純 Python 形式對這些庫進行封裝。

目前創(chuàng)新互聯(lián)公司已為1000+的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)絡(luò)空間、網(wǎng)站運營、企業(yè)網(wǎng)站設(shè)計、忻州網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

ctypes 教程

注:本教程中的示例代碼使用 doctest 來保證它們能正確運行。 由于有些代碼示例在 Linux, Windows 或 macOS 上的行為有所不同,它們在注釋中包含了一些 doctest 指令。

注意:部分示例代碼引用了 ctypes c_int 類型。在 sizeof(long) == sizeof(int) 的平臺上此類型是 c_long 的一個別名。所以,在程序輸出 c_long 而不是你期望的 c_int 時不必感到迷惑 —- 它們實際上是同一種類型。

載入動態(tài)連接庫

ctypes 導(dǎo)出了 cdll 對象,在 Windows 系統(tǒng)中還導(dǎo)出了 windlloledll 對象用于載入動態(tài)連接庫。

通過操作這些對象的屬性,你可以載入外部的動態(tài)鏈接庫。cdll 載入按標準的 cdecl 調(diào)用協(xié)議導(dǎo)出的函數(shù),而 windll 導(dǎo)入的庫按 stdcall 調(diào)用協(xié)議調(diào)用其中的函數(shù)。 oledll 也按 stdcall 調(diào)用協(xié)議調(diào)用其中的函數(shù),并假定該函數(shù)返回的是 Windows HRESULT 錯誤代碼,并當函數(shù)調(diào)用失敗時,自動根據(jù)該代碼甩出一個 OSError 異常。

在 3.3 版更改: 原來在 Windows 下拋出的異常類型 WindowsError 現(xiàn)在是 OSError 的一個別名。

這是一些 Windows 下的例子。注意:msvcrt 是微軟 C 標準庫,包含了大部分 C 標準函數(shù),這些函數(shù)都是以 cdecl 調(diào)用協(xié)議進行調(diào)用的。

 
 
 
 
  1. >>> from ctypes import *
  2. >>> print(windll.kernel32)
  3. >>> print(cdll.msvcrt)
  4. >>> libc = cdll.msvcrt
  5. >>>

Windows 會自動添加通常的 .dll 文件擴展名。

備注

通過 cdll.msvcrt 調(diào)用的標準 C 函數(shù),可能會導(dǎo)致調(diào)用一個過時的,與當前 Python 所不兼容的函數(shù)。因此,請盡量使用標準的 Python 函數(shù),而不要使用 msvcrt 模塊。

在 Linux 下,必須使用 包含 文件擴展名的文件名來導(dǎo)入共享庫。因此不能簡單使用對象屬性的方式來導(dǎo)入庫。因此,你可以使用方法 LoadLibrary(),或構(gòu)造 CDLL 對象來導(dǎo)入庫。

 
 
 
 
  1. >>> cdll.LoadLibrary("libc.so.6")
  2. >>> libc = CDLL("libc.so.6")
  3. >>> libc
  4. >>>

操作導(dǎo)入的動態(tài)鏈接庫中的函數(shù)

通過操作dll對象的屬性來操作這些函數(shù)。

 
 
 
 
  1. >>> from ctypes import *
  2. >>> libc.printf
  3. <_FuncPtr object at 0x...>
  4. >>> print(windll.kernel32.GetModuleHandleA)
  5. <_FuncPtr object at 0x...>
  6. >>> print(windll.kernel32.MyOwnFunction)
  7. Traceback (most recent call last):
  8. File "", line 1, in
  9. File "ctypes.py", line 239, in __getattr__
  10. func = _StdcallFuncPtr(name, self)
  11. AttributeError: function 'MyOwnFunction' not found
  12. >>>

注意:Win32 系統(tǒng)的動態(tài)庫,比如 kernel32user32,通常會同時導(dǎo)出同一個函數(shù)的 ANSI 版本和 UNICODE 版本。UNICODE 版本通常會在名字最后以 W 結(jié)尾,而 ANSI 版本的則以 A 結(jié)尾。 win32的 GetModuleHandle 函數(shù)會根據(jù)一個模塊名返回一個 模塊句柄,該函數(shù)暨同時包含這樣的兩個版本的原型函數(shù),并通過宏 UNICODE 是否定義,來決定宏 GetModuleHandle 導(dǎo)出的是哪個具體函數(shù)。

 
 
 
 
  1. /* ANSI version */
  2. HMODULE GetModuleHandleA(LPCSTR lpModuleName);
  3. /* UNICODE version */
  4. HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll 不會通過這樣的魔法手段來幫你決定選擇哪一種函數(shù),你必須顯式的調(diào)用 GetModuleHandleAGetModuleHandleW,并分別使用字節(jié)對象或字符串對象作參數(shù)。

有時候,dlls的導(dǎo)出的函數(shù)名不符合 Python 的標識符規(guī)范,比如 "??2@YAPAXI@Z"。此時,你必須使用 getattr() 方法來獲得該函數(shù)。

 
 
 
 
  1. >>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
  2. <_FuncPtr object at 0x...>
  3. >>>

Windows 下,有些 dll 導(dǎo)出的函數(shù)沒有函數(shù)名,而是通過其順序號調(diào)用。對此類函數(shù),你也可以通過 dll 對象的數(shù)值索引來操作這些函數(shù)。

 
 
 
 
  1. >>> cdll.kernel32[1]
  2. <_FuncPtr object at 0x...>
  3. >>> cdll.kernel32[0]
  4. Traceback (most recent call last):
  5. File "", line 1, in
  6. File "ctypes.py", line 310, in __getitem__
  7. func = _StdcallFuncPtr(name, self)
  8. AttributeError: function ordinal 0 not found
  9. >>>

調(diào)用函數(shù)

你可以貌似是調(diào)用其它 Python 函數(shù)那樣直接調(diào)用這些函數(shù)。在這個例子中,我們調(diào)用了 time() 函數(shù),該函數(shù)返回一個系統(tǒng)時間戳(從 Unix 時間起點到現(xiàn)在的秒數(shù)),而``GetModuleHandleA()`` 函數(shù)返回一個 win32 模塊句柄。

此函數(shù)中調(diào)用的兩個函數(shù)都使用了空指針(用 None 作為空指針):

 
 
 
 
  1. >>> print(libc.time(None))
  2. 1150640792
  3. >>> print(hex(windll.kernel32.GetModuleHandleA(None)))
  4. 0x1d000000
  5. >>>

如果你用 cdecl 調(diào)用方式調(diào)用 stdcall 約定的函數(shù),則會甩出一個異常 ValueError。反之亦然。

 
 
 
 
  1. >>> cdll.kernel32.GetModuleHandleA(None)
  2. Traceback (most recent call last):
  3. File "", line 1, in
  4. ValueError: Procedure probably called with not enough arguments (4 bytes missing)
  5. >>>
  6. >>> windll.msvcrt.printf(b"spam")
  7. Traceback (most recent call last):
  8. File "", line 1, in
  9. ValueError: Procedure probably called with too many arguments (4 bytes in excess)
  10. >>>

你必須閱讀這些庫的頭文件或說明文檔來確定它們的正確的調(diào)用協(xié)議。

在 Windows 中,ctypes 使用 win32 結(jié)構(gòu)化異常處理來防止由于在調(diào)用函數(shù)時使用非法參數(shù)導(dǎo)致的程序崩潰。

 
 
 
 
  1. >>> windll.kernel32.GetModuleHandleA(32)
  2. Traceback (most recent call last):
  3. File "", line 1, in
  4. OSError: exception: access violation reading 0x00000020
  5. >>>

然而,總有許多辦法,通過調(diào)用 ctypes 使得 Python 程序崩潰。因此,你必須小心使用。 faulthandler 模塊可以用于幫助診斷程序崩潰的原因。(比如由于錯誤的C庫函數(shù)調(diào)用導(dǎo)致的段錯誤)。

None, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls. None is passed as a C NULL pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (char* or wchar_t*). Python integers are passed as the platforms default C int type, their value is masked to fit into the C type.

在我們開始調(diào)用函數(shù)前,我們必須先了解作為函數(shù)參數(shù)的 ctypes 數(shù)據(jù)類型。

基礎(chǔ)數(shù)據(jù)類型

ctypes 定義了一些和C兼容的基本數(shù)據(jù)類型:

c_bool

_Bool

bool (1)

c_char

char

單字符字節(jié)串對象

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

int64 or long long

int

c_ulonglong

unsigned int64 or unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_t or Py_ssize_t

int

c_float

float

float

c_double

double

float

c_longdouble

long double

float

c_char_p

char (NUL terminated)

字節(jié)串對象或 None

c_wchar_p

wchar_t (NUL terminated)

字符串或 None

c_void_p

void*

int 或 None

ctypes 類型

C 類型

Python 類型

  1. 構(gòu)造函數(shù)接受任何具有真值的對象。

所有這些類型都可以通過使用正確類型和值的可選初始值調(diào)用它們來創(chuàng)建:

 
 
 
 
  1. >>> c_int()
  2. c_long(0)
  3. >>> c_wchar_p("Hello, World")
  4. c_wchar_p(140018365411392)
  5. >>> c_ushort(-3)
  6. c_ushort(65533)
  7. >>>

由于這些類型是可變的,它們的值也可以在以后更改:

 
 
 
 
  1. >>> i = c_int(42)
  2. >>> print(i)
  3. c_long(42)
  4. >>> print(i.value)
  5. 42
  6. >>> i.value = -99
  7. >>> print(i.value)
  8. -99
  9. >>>

當給指針類型的對象 c_char_p, c_wchar_p 和 c_void_p 等賦值時,將改變它們所指向的 內(nèi)存地址,而 不是 它們所指向的內(nèi)存區(qū)域的 內(nèi)容 (這是理所當然的,因為 Python 的 bytes 對象是不可變的):

 
 
 
 
  1. >>> s = "Hello, World"
  2. >>> c_s = c_wchar_p(s)
  3. >>> print(c_s)
  4. c_wchar_p(139966785747344)
  5. >>> print(c_s.value)
  6. Hello World
  7. >>> c_s.value = "Hi, there"
  8. >>> print(c_s) # the memory location has changed
  9. c_wchar_p(139966783348904)
  10. >>> print(c_s.value)
  11. Hi, there
  12. >>> print(s) # first object is unchanged
  13. Hello, World
  14. >>>

但你要注意不能將它們傳遞給會改變指針所指內(nèi)存的函數(shù)。如果你需要可改變的內(nèi)存塊,ctypes 提供了 create_string_buffer() 函數(shù),它提供多種方式創(chuàng)建這種內(nèi)存塊。當前的內(nèi)存塊內(nèi)容可以通過 raw 屬性存取,如果你希望將它作為NUL結(jié)束的字符串,請使用 value 屬性:

 
 
 
 
  1. >>> from ctypes import *
  2. >>> p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes
  3. >>> print(sizeof(p), repr(p.raw))
  4. 3 b'\x00\x00\x00'
  5. >>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string
  6. >>> print(sizeof(p), repr(p.raw))
  7. 6 b'Hello\x00'
  8. >>> print(repr(p.value))
  9. b'Hello'
  10. >>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
  11. >>> print(sizeof(p), repr(p.raw))
  12. 10 b'Hello\x00\x00\x00\x00\x00'
  13. >>> p.value = b"Hi"
  14. >>> print(sizeof(p), repr(p.raw))
  15. 10 b'Hi\x00lo\x00\x00\x00\x00\x00'
  16. >>>

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.

調(diào)用函數(shù),繼續(xù)

注意 printf 將打印到真正標準輸出設(shè)備,而*不是* sys.stdout,因此這些實例只能在控制臺提示符下工作,而不能在 IDLEPythonWin 中運行。

 
 
 
 
  1. >>> printf = libc.printf
  2. >>> printf(b"Hello, %s\n", b"World!")
  3. Hello, World!
  4. 14
  5. >>> printf(b"Hello, %S\n", "World!")
  6. Hello, World!
  7. 14
  8. >>> printf(b"%d bottles of beer\n", 42)
  9. 42 bottles of beer
  10. 19
  11. >>> printf(b"%f bottles of beer\n", 42.5)
  12. Traceback (most recent call last):
  13. File "", line 1, in
  14. ArgumentError: argument 2: TypeError: Don't know how to convert parameter 2
  15. >>>

正如前面所提到過的,除了整數(shù)、字符串以及字節(jié)串之外,所有的 Python 類型都必須使用它們對應(yīng)的 ctypes 類型包裝,才能夠被正確地轉(zhuǎn)換為所需的C語言類型。

 
 
 
 
  1. >>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
  2. An int 1234, a double 3.140000
  3. 31
  4. >>>

使用自定義的數(shù)據(jù)類型調(diào)用函數(shù)

你也可以通過自定義 ctypes 參數(shù)轉(zhuǎn)換方式來允許自定義類型作為參數(shù)。 ctypes 會尋找 _as_parameter_ 屬性并使用它作為函數(shù)參數(shù)。當然,它必須是數(shù)字、字符串或者二進制字符串:

 
 
 
 
  1. >>> class Bottles:
  2. ... def __init__(self, number):
  3. ... self._as_parameter_ = number
  4. ...
  5. >>> bottles = Bottles(42)
  6. >>> printf(b"%d bottles of beer\n", bottles)
  7. 42 bottles of beer
  8. 19
  9. >>>

如果你不想把實例的數(shù)據(jù)存儲到 _as_parameter_ 屬性??梢酝ㄟ^定義 property 函數(shù)計算出這個屬性。

指定必選參數(shù)的類型(函數(shù)原型)

可以通過設(shè)置 argtypes 屬性的方法指定從 DLL 中導(dǎo)出函數(shù)的必選參數(shù)類型。

argtypes 必須是一個 C 數(shù)據(jù)類型的序列 (這里的 printf 可能不是個好例子,因為它是變長參數(shù),而且每個參數(shù)的類型依賴于格式化字符串,不過嘗試這個功能也很方便):

 
 
 
 
  1. >>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
  2. >>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2)
  3. String 'Hi', Int 10, Double 2.200000
  4. 37
  5. >>>

指定數(shù)據(jù)類型可以防止不合理的參數(shù)傳遞(就像 C 函數(shù)的原型),并且會自動嘗試將參數(shù)轉(zhuǎn)換為需要的類型:

 
 
 
 
  1. >>> printf(b"%d %d %d", 1, 2, 3)
  2. Traceback (most recent call last):
  3. File "", line 1, in
  4. ArgumentError: argument 2: TypeError: wrong type
  5. >>> printf(b"%s %d %f\n", b"X", 2, 3)
  6. X 2 3.000000
  7. 13
  8. >>>

如果你想通過自定義類型傳遞參數(shù)給函數(shù),必須實現(xiàn) from_param() 類方法,才能夠?qū)⒋俗远x類型用于 argtypes 序列。from_param() 類方法接受一個 Python 對象作為函數(shù)輸入,它應(yīng)該進行類型檢查或者其他必要的操作以保證接收到的對象是合法的,然后返回這個對象,或者它的 _as_parameter_ 屬性,或者其他你想要傳遞給 C 函數(shù)的參數(shù)。這里也一樣,返回的結(jié)果必須是整型、字符串、二進制字符串、 ctypes 類型,或者一個具有 _as_parameter_ 屬性的對象。

返回類型

By default functions are assumed to return the C int type. Other return types can be specified by setting the restype attribute of the function object.

這是個更高級的例子,它調(diào)用了 strchr 函數(shù),這個函數(shù)接收一個字符串指針以及一個字符作為參數(shù),返回另一個字符串指針。

 
 
 
 
  1. >>> strchr = libc.strchr
  2. >>> strchr(b"abcdef", ord("d"))
  3. 8059983
  4. >>> strchr.restype = c_char_p # c_char_p is a pointer to a string
  5. >>> strchr(b"abcdef", ord("d"))
  6. b'def'
  7. >>> print(strchr(b"abcdef", ord("x")))
  8. None
  9. >>>

如果希望避免上述的 ord("x") 調(diào)用,可以設(shè)置 argtypes 屬性,第二個參數(shù)就會將單字符的 Python 二進制字符對象轉(zhuǎn)換為 C 字符:

 
 
 
 
  1. >>> strchr.restype = c_char_p
  2. >>> strchr.argtypes = [c_char_p, c_char]
  3. >>> strchr(b"abcdef", b"d")
  4. 'def'
  5. >>> strchr(b"abcdef", b"def")
  6. Traceback (most recent call last):
  7. File "", line 1, in
  8. ArgumentError: argument 2: TypeError: one character string expected
  9. >>> print(strchr(b"abcdef", b"x"))
  10. None
  11. >>> strchr(b"abcdef", b"d")
  12. 'def'
  13. >>>

如果外部函數(shù)返回了一個整數(shù),你也可以使用要給可調(diào)用的 Python 對象(比如函數(shù)或者類)作為 restype 屬性的值。將會以 C 函數(shù)返回的 整數(shù) 對象作為參數(shù)調(diào)用這個可調(diào)用對象,執(zhí)行后的結(jié)果作為最終函數(shù)返回值。這在錯誤返回值校驗和自動拋出異常等方面比較有用。

 
 
 
 
  1. >>> GetModuleHandle = windll.kernel32.GetModuleHandleA
  2. >>> def ValidHandle(value):
  3. ... if value == 0:
  4. ... raise WinError()
  5. ... return value
  6. ...
  7. >>>
  8. >>> GetModuleHandle.restype = ValidHandle
  9. >>> GetModuleHandle(None)
  10. 486539264
  11. >>> GetModuleHandle("something silly")
  12. Traceback (most recent call last):
  13. File "", line 1, in
  14. File "", line 3, in ValidHandle
  15. OSError: [Errno 126] The specified module could not be found.
  16. >>>

WinError 函數(shù)可以調(diào)用 Windows 的 FormatMessage() API 獲取錯誤碼的字符串說明,然后 返回 一個異常。 WinError 接收一個可選的錯誤碼作為參數(shù),如果沒有的話,它將調(diào)用 GetLastError() 獲取錯誤碼。

請注意,使用 errcheck 屬性可以實現(xiàn)更強大的錯誤檢查手段;詳情請見參考手冊。

傳遞指針(或以引用方式傳遞形參)

有時候 C 函數(shù)接口可能由于要往某個地址寫入值,或者數(shù)據(jù)太大不適合作為值傳遞,從而希望接收一個 指針 作為數(shù)據(jù)參數(shù)類型。這和 傳遞參數(shù)引用 類似。

ctypes 暴露了 byref() 函數(shù)用于通過引用傳遞參數(shù),使用 pointer() 函數(shù)也能達到同樣的效果,只不過 pointer() 需要更多步驟,因為它要先構(gòu)造一個真實指針對象。所以在 Python 代碼本身不需要使用這個指針對象的情況下,使用 byref() 效率更高。

 
 
 
 
  1. >>> i = c_int()
  2. >>> f = c_float()
  3. >>> s = create_string_buffer(b'\000' * 32)
  4. >>> print(i.value, f.value, repr(s.value))
  5. 0 0.0 b''
  6. >>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s",
  7. ... byref(i), byref(f), s)
  8. 3
  9. >>> print(i.value, f.value, repr(s.value))
  10. 1 3.1400001049 b'Hello'
  11. >>>

結(jié)構(gòu)體和聯(lián)合

結(jié)構(gòu)體和聯(lián)合必須繼承自 ctypes 模塊中的 Structure 和 Union 。子類必須定義 _fields_ 屬性。 _fields_ 是一個二元組列表,二元組中包含 field namefield type 。

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

這是一個簡單的 POINT 結(jié)構(gòu)體,它包含名稱為 xy 的兩個變量,還展示了如何通過構(gòu)造函數(shù)初始化結(jié)構(gòu)體。

 
 
 
 
  1. >>> from ctypes import *
  2. >>> class POINT(Structure):
  3. ... _fields_ = [("x", c_int),
  4. ... ("y", c_int)]
  5. ...
  6. >>> point = POINT(10, 20)
  7. >>> print(point.x, point.y)
  8. 10 20
  9. >>> point = POINT(y=5)
  10. >>> print(point.x, point.y)
  11. 0 5
  12. >>> POINT(1, 2, 3)
  13. Traceback (most recent call last):
  14. File "", line 1, in
  15. TypeError: too many initializers
  16. >>>

當然,你可以構(gòu)造更復(fù)雜的結(jié)構(gòu)體。一個結(jié)構(gòu)體可以通過設(shè)置 type 字段包含其他結(jié)構(gòu)體或者自身。

這是以一個 RECT 結(jié)構(gòu)體,他包含了兩個 POINT ,分別叫 upperleftlowerright:

 
 
 
 
  1. >>> class RECT(Structure):
  2. ... _fields_ = [("upperleft", POINT),
  3. ... ("lowerright", POINT)]
  4. ...
  5. >>> rc = RECT(point)
  6. >>> print(rc.upperleft.x, rc.upperleft.y)
  7. 0 5
  8. >>> print(rc.lowerright.x, rc.lowerright.y)
  9. 0 0
  10. >>>

嵌套結(jié)構(gòu)體可以通過幾種方式構(gòu)造初始化:

 
 
 
 
  1. >>> r = RECT(POINT(1, 2), POINT(3, 4))
  2. >>> r = RECT((1, 2), (3, 4))

可以通過 獲取字段 descriptor ,它能提供很多有用的調(diào)試信息。

 
 
 
 
  1. >>> print(POINT.x)
  2. >>> print(POINT.y)
  3. >>>

警告

ctypes 不支持帶位域的結(jié)構(gòu)體、聯(lián)合以值的方式傳給函數(shù)。這可能在 32 位 x86 平臺上可以正常工作,但是對于一般情況,這種行為是未定義的。帶位域的結(jié)構(gòu)體、聯(lián)合應(yīng)該總是通過指針傳遞給函數(shù)。

結(jié)構(gòu)體/聯(lián)合字段對齊及字節(jié)順序

默認情況下,結(jié)構(gòu)體和聯(lián)合的字段與 C 的字節(jié)對齊是一樣的。也可以在定義子類的時候指定類的 _pack_ 屬性來覆蓋這種行為。 它必須設(shè)置為一個正整數(shù),表示字段的最大對齊字節(jié)。這和 MSVC 中的 #pragma pack(n) 功能一樣。

ctypes 中的結(jié)構(gòu)體和聯(lián)合使用的是本地字節(jié)序。要使用非本地字節(jié)序,可以使用 BigEndianStructure, LittleEndianStructure, BigEndianUnion, and LittleEndianUnion 作為基類。這些類不能包含指針字段。

結(jié)構(gòu)體和聯(lián)合中的位域

結(jié)構(gòu)體和聯(lián)合中是可以包含位域字段的。位域只能用于整型字段,位長度通過 _fields_ 中的第三個參數(shù)指定:

 
 
 
 
  1. >>> class Int(Structure):
  2. ... _fields_ = [("first_16", c_int, 16),
  3. ... ("second_16", c_int, 16)]
  4. ...
  5. >>> print(Int.first_16)
  6. >>> print(Int.second_16)
  7. >>>

數(shù)組

數(shù)組是一個序列,包含指定個數(shù)元素,且必須類型相同。

創(chuàng)建數(shù)組類型的推薦方式是使用一個類型乘以一個正數(shù):

 
 
 
 
  1. TenPointsArrayType = POINT * 10

下面是一個構(gòu)造的數(shù)據(jù)案例,結(jié)構(gòu)體中包含了4個 POINT 和一些其他東西。

 
 
 
 
  1. >>> from ctypes import *
  2. >>> class POINT(Structure):
  3. ... _fields_ = ("x", c_int), ("y", c_int)
  4. ...
  5. >>> class MyStruct(Structure):
  6. ... _fields_ = [("a", c_int),
  7. ... ("b", c_float),
  8. ... ("point_array", POINT * 4)]
  9. >>>
  10. >>> print(len(MyStruct().point_array))
  11. 4
  12. >>>

和平常一樣,通過調(diào)用它創(chuàng)建實例:

 
 
 
 
  1. arr = TenPointsArrayType()
  2. for pt in arr:
  3. print(pt.x, pt.y)

以上代碼會打印幾行 0 0 ,因為數(shù)組內(nèi)容被初始化為 0.

也能通過指定正確類型的數(shù)據(jù)來初始化:

 
 
 
 
  1. >>> from ctypes import *
  2. >>> TenIntegers = c_int * 10
  3. >>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  4. >>> print(ii)
  5. >>> for i in ii: print(i, end=" ")
  6. ...
  7. 1 2 3 4 5 6 7 8 9 10
  8. >>>

指針

可以將 ctypes 類型數(shù)據(jù)傳入 pointer() 函數(shù)創(chuàng)建指針:

 
 
 
 
  1. >>> from ctypes import *
  2. >>> i = c_int(42)
  3. >>> pi = pointer(i)
  4. >>>

指針實例擁有 contents 屬性,它返回指針指向的真實對象,如上面的 i 對象:

 
 
 
 
  1. >>> pi.contents
  2. c_long(42)
  3. >>>

注意 ctypes 并沒有 OOR (返回原始對象), 每次訪問這個屬性時都會構(gòu)造返回一個新的相同對象:

 
 
 
 
  1. >>> pi.contents is i
  2. False
  3. >>> pi.contents is pi.contents
  4. False
  5. >>>

將這個指針的 contents 屬性賦值為另一個 c_int 實例將會導(dǎo)致該指針指向該實例的內(nèi)存地址:

 
 
 
 
  1. >>> i = c_int(99)
  2. >>> pi.contents = i
  3. >>> pi.contents
  4. c_long(99)
  5. >>>

指針對象也可以通過整數(shù)下標進行訪問:

 
 
 
 
  1. >>> pi[0]
  2. 99
  3. >>>

通過整數(shù)下標賦值可以改變指針所指向的真實內(nèi)容:

 
 
 
 
  1. >>> print(i)
  2. c_long(99)
  3. >>> pi[0] = 22
  4. >>> print(i)
  5. c_long(22)
  6. >>>

使用 0 以外的索引也是合法的,但是你必須確保知道自己為什么這么做,就像 C 語言中: 你可以訪問或者修改任意內(nèi)存內(nèi)容。 通常只會在函數(shù)接收指針是才會使用這種特性,而且你 知道 這個指針指向的是一個數(shù)組而不是單個值。

內(nèi)部細節(jié), pointer() 函數(shù)不只是創(chuàng)建了一個指針實例,它首先創(chuàng)建了一個指針 類型 。這是通過調(diào)用 POINTER() 函數(shù)實現(xiàn)的,它接收 ctypes 類型為參數(shù),返回一個新的類型:

 
 
 
 
  1. >>> PI = POINTER(c_int)
  2. >>> PI
  3. >>> PI(42)
  4. Traceback (most recent call last):
  5. File "", line 1, in
  6. TypeError: expected c_long instead of int
  7. >>> PI(c_int(42))
  8. >>>

無參調(diào)用指針類型可以創(chuàng)建一個 NULL 指針。 NULL 指針的布爾值是 False

 
 
 
 
  1. >>> null_ptr = POINTER(c_int)()
  2. >>> print(bool(null_ptr))
  3. False
  4. >>>

解引用指針的時候, ctypes 會幫你檢測是否指針為 NULL (但是解引用無效的 非 NULL 指針仍會導(dǎo)致 Python 崩潰):

 
 
 
 
  1. >>> null_ptr[0]
  2. Traceback (most recent call last):
  3. ....
  4. ValueError: NULL pointer access
  5. >>>
  6. >>> null_ptr[0] = 1234
  7. Traceback (most recent call last):
  8. ....
  9. ValueError: NULL pointer access
  10. >>>

類型轉(zhuǎn)換

通常情況下, ctypes 具有嚴格的類型檢查。這代表著, 如果在函數(shù) argtypes 網(wǎng)頁名稱:創(chuàng)新互聯(lián)Python教程:ctypes —- Python 的外部函數(shù)庫
文章地址:http://www.dlmjj.cn/article/dpccije.html