python ctypes中文幫助文檔

   15.17。ctypes- 用於Python的外部函數庫

2.5版中的新功能。

ctypes是Python的外部函數庫。它提供C兼容的數據類型,並允許在DLL或共享庫中調用函數。它可以用於在純Python中包裝這些庫。

15.17.1。ctypes教程

注意:本教程中的代碼示例doctest用於確保它們實際工作。由於某些代碼示例在Linux,Windows或Mac OS X下的行爲不同,因此它們在註釋中包含doctest指令。

注意:某些代碼示例引用了ctypes c_int類型。此類型是c_long32位系統上類型的別名。因此,c_long如果您打算如果打印,則不應該感到困惑c_int- 它們實際上是相同的類型。

   15.17.1.1。加載動態鏈接庫

ctypes導出cdll,以及Windows windlloledll 對象,用於加載動態鏈接庫。

您可以通過訪問它們作爲這些對象的屬性來加載庫。cdll 加載使用標準cdecl調用約定導出函數的庫,而windll庫使用stdcall 調用約定調用函數。oledll還使用stdcall調用約定,並假定函數返回Windows HRESULT錯誤代碼。錯誤代碼用於WindowsError在函數調用失敗時自動引發異常。

以下是Windows的一些示例。注意,它msvcrt是包含大多數標準C函數的MS標準C庫,並使用cdecl調用約定:

>>> from ctypes import *
>>> print windll.kernel32  
<WinDLL 'kernel32', handle ... at ...>
>>> print cdll.msvcrt      
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt     
>>>

Windows會.dll自動附加常用的文件後綴。

在Linux上,需要指定包含加載庫的擴展名的文件名,因此不能使用屬性訪問來加載庫。LoadLibrary()應該使用dll加載器的 方法,或者你應該通過調用構造函數創建一個CDLL實例來加載庫:

>>> cdll.LoadLibrary("libc.so.6")  
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")       
>>> libc                           
<CDLL 'libc.so.6', handle ... at ...>
>>>

   15.17.1.2。從加載的dll訪問函數

函數作爲dll對象的屬性進行訪問:

>>> 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系統類似於kernel32並且user32經常導出ANSI以及函數的UNICODE版本。UNICODE版本將導出W並附加到名稱,而ANSI版本將導出A 並附加到名稱。win32 GetModuleHandle函數返回給定模塊名稱的 模塊句柄,具有以下C原型,並且GetModuleHandle根據是否定義了UNICODE ,使用宏來公開其中一個:

/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll不會嘗試通過魔法選擇其中一個,您必須通過指定GetModuleHandleAGetModuleHandleW 顯式訪問所需的版本,然後分別使用字符串或unicode字符串調用它。

有時,dll導出的函數名稱不是有效的Python標識符,例如"??2@YAPAXI@Z"。在這種情況下,您必須使用 getattr()檢索功能:

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

在Windows上,某些dll不按名稱導出函數,而是按順序導出函數。可以通過使用序號索引dll對象來訪問這些函數:

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

    15.17.1.3。調用函數

您可以像調用任何其他Python一樣調用這些函數。此示例使用time()函數,該函數返回自Unix紀元以來以秒爲單位的系統時間,以及GetModuleHandleA()返回win32模塊句柄的函數。

此示例使用NULL指針調用這兩個函數(None應該用作NULL指針):

>>> print libc.time(None)  
1150640792
>>> print hex(windll.kernel32.GetModuleHandleA(None))  
0x1d000000
>>>

ctypes試圖保護您不要使用錯誤的參數數量或錯誤的調用約定來調用函數。不幸的是,這僅適用於Windows。它通過在函數返回後檢查堆棧來完成此操作,因此雖然引發了錯誤,但函數被調用:

>>> windll.kernel32.GetModuleHandleA()      
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>> windll.kernel32.GetModuleHandleA(0, 0)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

stdcall使用cdecl調用約定調用函數 時會引發相同的異常,反之亦然:

>>> 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("spam")  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

要找出正確的調用約定,您必須查看C頭文件或要調用的函數的文檔。

在Windows上,ctypes使用win32結構化異常處理來防止在使用無效參數值調用函數時因常規保護錯誤而崩潰:

>>> windll.kernel32.GetModuleHandleA(32)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: exception: access violation reading 0x00000020
>>>

但是,有足夠的方法可以使Python崩潰ctypes,所以無論如何你應該小心。

None,整數,長整數,字節字符串和unicode字符串是唯一可以直接用作這些函數調用中的參數的本機Python對象。 None作爲C NULL指針傳遞,字節字符串和unicode字符串作爲指針傳遞給包含其數據( 或)的內存塊。Python整數和Python long作爲平臺默認C 類型傳遞,它們的值被屏蔽以適合C類型。char *wchar_t *int

在我們繼續使用其他參數類型調用函數之前,我們必須瞭解有關ctypes數據類型的更多信息。

   15.17.1.4。基本數據類型

ctypes 定義了許多原始C兼容的數據類型:

ctypes類型 C型 Python類型
c_bool _Bool 布爾(1)
c_char char 1個字符的字符串
c_wchar wchar_t 1個字符的unicode字符串
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 要麼 long long INT /長
c_ulonglong unsigned __int64 要麼 unsigned long long INT /長
c_float float 浮動
c_double double 浮動
c_longdouble long double 浮動
c_char_p char * (NUL終止) 字符串或 None
c_wchar_p wchar_t * (NUL終止) unicode或 None
c_void_p void * int / long或 None
  1. 構造函數接受具有真值的任何對象。

所有這些類型都可以通過使用正確類型和值的可選初始化程序調用它們來創建:

>>> c_int()
c_long(0)
>>> c_char_p("Hello, World")
c_char_p('Hello, World')
>>> c_ushort(-3)
c_ushort(65533)
>>>

由於這些類型是可變的,因此它們的值也可以在以後更改:

>>> i = c_int(42)
>>> print i
c_long(42)
>>> print i.value
42
>>> i.value = -99
>>> print i.value
-99
>>>

分配一個新的值,將指針類型的實例c_char_p, c_wchar_p以及c_void_p改變所述存儲器位置它們指向,而不是內容的內存塊(當然不是,因爲Python字符串是不可變的):

>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print c_s
c_char_p('Hi, there')
>>> print s                 # first string is unchanged
Hello, World
>>>

但是,您應該小心,不要將它們傳遞給期望指向可變內存的函數。如果你需要可變的內存塊,ctypes有一個create_string_buffer()以各種方式創建它們的 函數。可以使用raw 屬性訪問(或更改)當前內存塊內容; 如果要以NUL終止字符串的形式訪問它,請使用以下value 屬性:

>>> from ctypes import *
>>> p = create_string_buffer(3)      # create a 3 byte buffer, initialized to NUL bytes
>>> print sizeof(p), repr(p.raw)
3 '\x00\x00\x00'
>>> p = create_string_buffer("Hello")      # create a buffer containing a NUL terminated string
>>> print sizeof(p), repr(p.raw)
6 'Hello\x00'
>>> print repr(p.value)
'Hello'
>>> p = create_string_buffer("Hello", 10)  # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello\x00\x00\x00\x00\x00'
>>> p.value = "Hi"
>>> print sizeof(p), repr(p.raw)
10 'Hi\x00lo\x00\x00\x00\x00\x00'
>>>

create_string_buffer()函數替換了c_buffer()函數(仍可作爲別名使用),以及c_string()早期ctypes版本中的函數。要創建包含C類型的unicode字符的可變內存塊,請wchar_t使用該 create_unicode_buffer()函數。

   15.17.1.5。調用函數,續

需要注意的是的printf打印到實際的標準輸出通道,給 sys.stdout,所以這些例子只是在控制檯提示符下運行,而不是從內IDLEPythonWin的

>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("Hello, %S\n", u"World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf("%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
>>>

如前所述,除了整數,字符串和unicode字符串之外的所有Python類型都必須包裝在相應的ctypes類型中,以便它們可以轉換爲所需的C數據類型:

>>> printf("An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>

    15.17.1.6。使用自己的自定義數據類型調用函數

您還可以自定義ctypes參數轉換,以允許將您自己的類的實例用作函數參數。 ctypes查找 _as_parameter_屬性並將其用作函數參數。當然,它必須是整數,字符串或unicode之一:

>>> class Bottles(object):
...     def __init__(self, number):
...         self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf("%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>

如果您不想將實例的數據存儲在_as_parameter_ 實例變量中,則可以定義property()使數據可用的數據。

   15.17.1.7。指定必需的參數類型(函數原型)

可以通過設置argtypes屬性來指定從DLL導出的函數所需的參數類型。

argtypes必須是一系列C數據類型(這裏的printf函數可能不是一個很好的例子,因爲它取決於格式字符串需要一個可變數字和不同類型的參數,另一方面,這對於試驗這個特性非常方便) :

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

指定格式可以防止不兼容的參數類型(就像C函數的原型一樣),並嘗試將參數轉換爲有效類型:

>>> printf("%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("%s %d %f\n", "X", 2, 3)
X 2 3.000000
13
>>>

如果已經定義了自己傳遞給函數調用的from_param()類,則必須爲它們實現一個類方法,以便能夠在argtypes序列中使用它們。本from_param()類方法接收傳給函數的Python對象,它做一個類型檢測,或者是需要確保這個對象是可接受的,然後返回對象本身,它_as_parameter_不管你想傳遞的C函數屬性,或在這種情況下的論點。同樣,結果應該是整數,字符串,unicode,ctypes實例或具有_as_parameter_屬性的對象 。

   15.17.1.8。返回類型

默認情況下,假定函數返回C int類型。可以通過設置restype函數對象的屬性來指定其他返回類型。

這是一個更高級的示例,它使用strchr函數,它需要一個字符串指針和一個char,並返回一個指向字符串的指針:

>>> strchr = libc.strchr
>>> strchr("abcdef", ord("d"))  
8059983
>>> strchr.restype = c_char_p   # c_char_p is a pointer to a string
>>> strchr("abcdef", ord("d"))
'def'
>>> print strchr("abcdef", ord("x"))
None
>>>

如果要避免ord("x")上面的調用,可以設置 argtypes屬性,第二個參數將從單個字符Python字符串轉換爲C字符:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr("abcdef", "d")
'def'
>>> strchr("abcdef", "def")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print strchr("abcdef", "x")
None
>>> strchr("abcdef", "d")
'def'
>>>

restype如果外部函數返回一個整數,您還可以使用可調用的Python對象(例如函數或類)作爲屬性。將使用C函數返回的整數調用callable ,並且此調用的結果將用作函數調用的結果。這對於檢查錯誤返回值並自動引發異常非常有用:

>>> 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
WindowsError: [Errno 126] The specified module could not be found.
>>>

WinError是一個函數,它將調用Windows FormatMessage()api來獲取錯誤代碼的字符串表示,並返回一個異常。 WinError採用可選的錯誤代碼參數,如果沒有使用,則調用它 GetLastError()來檢索它。

請注意,通過該errcheck屬性可以使用更強大的錯誤檢查機制; 有關詳細信息,請參閱參考手冊

   15.17.1.9。傳遞指針(或:通過引用傳遞參數)

有時,C api函數需要將指向數據類型的指針作爲參數,可能要寫入相應的位置,或者數據太大而無法通過值傳遞。這也稱爲通過引用傳遞參數

ctypes導出byref()用於通過引用傳遞參數的函數。使用該pointer()函數可以實現相同的效果 ,但是pointer()由於它構造了一個真正的指針對象,所以工作量更大,因此byref()如果您不需要Python本身的指針對象,則使用它會更快:

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer('\000' * 32)
>>> print i.value, f.value, repr(s.value)
0 0.0 ''
>>> libc.sscanf("1 3.14 Hello", "%d %f %s",
...             byref(i), byref(f), s)
3
>>> print i.value, f.value, repr(s.value)
1 3.1400001049 'Hello'
>>>

   15.17.1.10。結構和聯合

結構和聯合必須從導出StructureUnion 其中所定義的基類ctypes模塊。每個子類都必須定義一個_fields_屬性。 _fields_必須是2元組的列表 ,包含字段名稱字段類型

字段類型必須是ctypes類型c_int或任何其他派生ctypes類型:結構,聯合,數組,指針。

下面是一個POINT結構的簡單示例,它包含兩個名爲xy的整數 ,還顯示瞭如何在構造函數中初始化結構:

>>> 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>
ValueError: too many initializers
>>>

但是,您可以構建更復雜的結構。通過將結構用作字段類型,結構本身可以包含其他結構。

這是一個RECT結構,它包含兩個名爲upperleft和 lowerright的POINT

>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print rc.upperleft.x, rc.upperleft.y
0 5
>>> print rc.lowerright.x, rc.lowerright.y
0 0
>>>

嵌套結構也可以通過以下幾種方式在構造函數中初始化:

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

字段描述符 S可從被檢索,它們可用於調試有用,因爲它們可以提供有用的信息:

>>> print POINT.x
<Field type=c_long, ofs=0, size=4>
>>> print POINT.y
<Field type=c_long, ofs=4, size=4>
>>>

警告

 

ctypes不支持通過值將具有位字段的聯合或結構傳遞給函數。雖然這可能適用於32位x86,但是不能保證庫在一般情況下工作。具有位字段的聯合和結構應始終通過指針傳遞給函數。

   15.17.1.11。結構/聯合對齊和字節順序

默認情況下,Structure和Union字段的對齊方式與C編譯器的方式相同。可以通過_pack_在子類定義中指定類屬性來覆蓋此行爲 。必須將其設置爲正整數,並指定字段的最大對齊方式。這也是MSVC中的做法。#pragma pack(n)

ctypes使用結構和聯合的本機字節順序。要建立與非本地字節順序結構,你可以使用一個 BigEndianStructureLittleEndianStructure, BigEndianUnion,和LittleEndianUnion基類。這些類不能包含指針字段。

   15.17.1.12。結構和聯合中的位字段

可以創建包含位字段的結構和聯合。位字段僅適用於整數字段,位寬指定爲_fields_元組中的第三項:

>>> class Int(Structure):
...     _fields_ = [("first_16", c_int, 16),
...                 ("second_16", c_int, 16)]
...
>>> print Int.first_16
<Field type=c_long, ofs=0:0, bits=16>
>>> print Int.second_16
<Field type=c_long, ofs=0:16, bits=16>
>>>

   15.17.1.13。數組

數組是序列,包含固定數量的相同類型的實例。

創建數組類型的推薦方法是將數據類型與正整數相乘:

TenPointsArrayType = POINT * 10

這是一個有點人爲的數據類型的例子,一個包含4個POINT和其他東西的結構:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class MyStruct(Structure):
...     _fields_ = [("a", c_int),
...                 ("b", c_float),
...                 ("point_array", POINT * 4)]
>>>
>>> print len(MyStruct().point_array)
4
>>>

通過調用類以通常的方式創建實例:

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

上面的代碼打印了一系列行,因爲數組內容被初始化爲零。0 0

也可以指定正確類型的初始化器:

>>> from ctypes import *
>>> TenIntegers = c_int * 10
>>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> print ii
<c_long_Array_10 object at 0x...>
>>> for i in ii: print i,
...
1 2 3 4 5 6 7 8 9 10
>>>

   15.17.1.14。指針

通過pointer()ctypes類型上調用函數 來創建指針實例:

>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>>

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

>>> pi.contents
c_long(42)
>>>

注意,ctypes沒有OOR(原始對象返回),每次檢索屬性時它都會構造一個新的等效對象:

>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>

將另一個c_int實例分配給指針的contents屬性會導致指針指向存儲它的內存位置:

>>> i = c_int(99)
>>> pi.contents = i
>>> pi.contents
c_long(99)
>>>

指針實例也可以用整數索引:

>>> pi[0]
99
>>>

分配整數索引會更改指向的值:

>>> print i
c_long(99)
>>> pi[0] = 22
>>> print i
c_long(22)
>>>

也可以使用不同於0的索引,但您必須知道自己在做什麼,就像在C中一樣:您可以訪問或更改任意內存位置。通常,如果從C函數接收指針,則只使用此功能,並且您知道指針實際指向的是數組而不是單個項。

在幕後,該pointer()函數不僅僅是創建指針實例,還必須首先創建指針類型。這是通過POINTER()接受任何ctypes類型的函數完成的,並返回一個新類型:

>>> PI = POINTER(c_int)
>>> PI
<class 'ctypes.LP_c_long'>
>>> PI(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: expected c_long instead of int
>>> PI(c_int(42))
<ctypes.LP_c_long object at 0x...>
>>>

調用不帶參數的指針類型會創建NULL指針。 NULL指針有一個False布爾值:

>>> null_ptr = POINTER(c_int)()
>>> print bool(null_ptr)
False
>>>

ctypes檢查NULL何時解除引用指針(但解除引用無效的非NULL指針會使Python崩潰):

>>> null_ptr[0]
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

>>> null_ptr[0] = 1234
Traceback (most recent call last):
    ....
ValueError: NULL pointer access
>>>

   15.17.1.15。輸入轉換

通常,ctypes會進行嚴格的類型檢查。這意味着,如果您 POINTER(c_int)argtypes函數列表中或在結構定義中具有成員字段的類型,則只接受完全相同類型的實例。此規則有一些例外,其中ctypes接受其他對象。例如,您可以傳遞兼容的數組實例而不是指針類型。因此,對於POINTER(c_int),ctypes接受一個c_int數組:

>>> class Bar(Structure):
...     _fields_ = [("count", c_int), ("values", POINTER(c_int))]
...
>>> bar = Bar()
>>> bar.values = (c_int * 3)(1, 2, 3)
>>> bar.count = 3
>>> for i in range(bar.count):
...     print bar.values[i]
...
1
2
3
>>>

此外,如果函數參數顯式聲明爲指針類型(例如POINTER(c_int))in argtypes,則可以將指向類型的對象(c_int在本例中)傳遞給函數。ctypes將自動應用所需的byref()轉換。

要將POINTER類型字段設置爲NULL,您可以指定None

>>> bar.values = None
>>>

有時您會遇到不兼容類型的實例。在C中,您可以將一種類型轉換爲另一種類型。 ctypes提供cast()可以以相同方式使用的功能。Bar上面定義的結構接受 其字段的POINTER(c_int)指針或c_int數組values,但不接受其他類型的實例:

>>> bar.values = (c_byte * 4)()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance
>>>

對於這些情況,該cast()功能很方便,cast還支持將python中的id(obj)即對象地址還原爲對象

cast()函數可用於將ctypes實例轉換爲指向不同ctypes數據類型的指針。 cast()採用兩個參數,一個或者可以轉換爲某種指針的ctypes對象,以及一個ctypes指針類型。它返回第二個參數的實例,它引用與第一個參數相同的內存塊:

>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>

所以,cast()可以用來分配給結構的values字段Bar

>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print bar.values[0]
0
>>>

   15.17.1.16。不完全類型

不完整類型是其成員尚未指定的結構,聯合或數組。在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
>>>

因爲new 語句本身不可用。在,我們可以 在類語句之後定義類並設置屬性:class cellctypescell_fields_

>>> from ctypes import *
>>> class cell(Structure):
...     pass
...
>>> cell._fields_ = [("name", c_char_p),
...                  ("next", POINTER(cell))]
>>>

讓我們試試吧。我們創建了兩個實例cell,並讓它們相互指向,最後跟隨指針鏈幾次:

>>> c1 = cell()
>>> c1.name = "foo"
>>> c2 = cell()
>>> c2.name = "bar"
>>> c1.next = pointer(c2)
>>> c2.next = pointer(c1)
>>> p = c1
>>> for i in range(8):
...     print p.name,
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

   15.17.1.17。回調函數

ctypes允許從Python callables創建C可調用函數指針。這些有時稱爲回調函數

首先,您必須爲回調函數創建一個類,該類知道調用約定,返回類型以及此函數將接收的參數的數量和類型。

CFUNCTYPE工廠函數使用普通的cdecl調用約定爲回調函數創建類型,在Windows上,WINFUNCTYPE工廠函數使用stdcall調用約定爲回調函數創建類型。

這兩個工廠函數都以結果類型作爲第一個參數調用,而回調函數將期望的參數類型作爲其餘參數。

我將在這裏展示一個使用標準C庫qsort() 函數的示例,它用於在回調函數的幫助下對項目進行排序。 qsort()將用於對整數數組進行排序:

>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

qsort()必須使用指向要排序的數據的指針,數據數組中的項數,一個項的大小以及指向比較函數(回調)的指針來調用。然後使用兩個指向項目的指針調用回調,如果第一個項目小於第二個項目,它必須返回一個負整數,如果它們相等則返回零,然後返回一個正整數。

所以我們的回調函數接收指向整數的指針,並且必須返回一個整數。首先我們type爲回調函數創建:

>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>

對於回調函數的第一個實現,我們只是打印我們得到的參數,並返回0(增量開發;-):

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a, b
...     return 0
...
>>>

創建C可調用回調:

>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

我們準備好了:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) 
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
>>>

我們知道如何訪問指針的內容,所以讓我們重新定義我們的回調:

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a[0], b[0]
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

以下是我們在Windows上獲得的內容:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) 
py_cmp_func 7 1
py_cmp_func 33 1
py_cmp_func 99 1
py_cmp_func 5 1
py_cmp_func 7 5
py_cmp_func 33 5
py_cmp_func 99 5
py_cmp_func 7 99
py_cmp_func 33 99
py_cmp_func 7 33
>>>

很有趣的是,在linux上,sort函數看起來效率更高,它做的比較少:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func)  
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 5 7
py_cmp_func 1 7
>>>

啊,我們差不多完成了!最後一步是實際比較這兩個項並返回一個有用的結果:

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a[0], b[0]
...     return a[0] - b[0]
...
>>>

最終在Windows上運行:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) 
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 5 99
py_cmp_func 1 99
py_cmp_func 33 7
py_cmp_func 1 33
py_cmp_func 5 33
py_cmp_func 5 7
py_cmp_func 1 7
py_cmp_func 5 1
>>>

在Linux上:

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

很有趣的是,Windows qsort()功能需要比Linux版本更多的比較!

我們可以輕鬆檢查,我們的數組現在排序:

>>> for i in ia: print i,
...
1 5 7 33 99
>>>

注意

 

CFUNCTYPE()只要從C代碼中使用對象,請確保保留對對象的引用。ctypes沒有,如果你不這樣做,它們可能被垃圾收集,在回調時崩潰你的程序。

另外,請注意,如果在Python控件之外創建的線程中調用回調函數(例如,通過調用回調的外部代碼),ctypes會在每次調用時創建一個新的虛擬Python線程。這種行爲是對大多數的目的是正確的,但它意味着存儲的值threading.local不會在不同的回調生存,即使這些電話是從同一個C的線。

   15.17.1.18。訪問從dll導出的值

一些共享庫不僅導出函數,還導出變量。Python庫本身的一個示例是Py_OptimizeFlag,一個設置爲0,1或2的整數,具體取決於啓動時給出的-O-OO標誌。

ctypes可以使用in_dll()類型的類方法訪問這樣的值。 pythonapi是一個預定義的符號,可以訪問Python C api:

>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
>>> print opt_flag
c_long(0)
>>>

如果解釋器已經啓動-O,則樣本將打印c_long(1),或者c_long(2)如果-OO已經指定。

一個擴展示例也演示了指針的使用,可以訪問PyImport_FrozenModulesPython導出的 指針。

引用Python文檔:此指針初始化爲指向“struct _frozen”記錄的數組,由其成員全部爲NULL或零的記錄終止。導入凍結模塊時,將在此表中搜索它。第三方代碼可以使用此方法來提供動態創建的凍結模塊集合。

所以操縱這個指針甚至可以證明是有用的。爲了限制示例大小,我們僅顯示如何使用以下方法讀取此表ctypes

>>> from ctypes import *
>>>
>>> class struct_frozen(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("code", POINTER(c_ubyte)),
...                 ("size", c_int)]
...
>>>

我們已經定義了數據類型,因此我們可以獲得指向表的指針:struct _frozen

>>> FrozenTable = POINTER(struct_frozen)
>>> table = FrozenTable.in_dll(pythonapi, "PyImport_FrozenModules")
>>>

因爲table是一個記錄pointer數組struct_frozen,我們可以迭代它,但我們必須確保我們的循環終止,因爲指針沒有大小。遲早它可能會因訪問衝突或其他原因而崩潰,所以當我們點擊NULL條目時最好突破循環:

>>> for item in table:
...     print item.name, item.size
...     if item.name is None:
...         break
...
__hello__ 104
__phello__ -104
__phello__.spam 104
None 0
>>>

標準Python具有凍結模塊和凍結包(由負大小成員指示)的事實並不爲人所知,它僅用於測試。例如嘗試一下。import __hello__

   15.17.1.19。驚喜

在某些邊緣情況下ctypes,您可能會發現實際情況以外的其他情況。

請考慮以下示例:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class RECT(Structure):
...     _fields_ = ("a", POINT), ("b", POINT)
...
>>> p1 = POINT(1, 2)
>>> p2 = POINT(3, 4)
>>> rc = RECT(p1, p2)
>>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
1 2 3 4
>>> # now swap the two points
>>> rc.a, rc.b = rc.b, rc.a
>>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
3 4 3 4
>>>

嗯。我們當然希望打印最後一份聲明。發生了什麼?以下是上述行的步驟:3 4 1 2rc.a, rc.b = rc.b, rc.a

>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>

請注意,temp0並且temp1還在使用的內部緩衝器的對象rc上述對象。因此執行將緩衝區內容複製到緩衝區中。反過來,這改變了內容。因此,最後一項任務,沒有預期的效果。rc.a = temp0temp0rctemp1rc.b = temp1

請記住,從Structure,Unions和Arrays中檢索子對象不會複製子對象,而是檢索訪問根對象底層緩衝區的包裝器對象。

另一個可能與預期不同的例子是:

>>> s = c_char_p()
>>> s.value = "abc def ghi"
>>> s.value
'abc def ghi'
>>> s.value is s.value
False
>>>

爲什麼打印False?ctypes實例是包含內存塊的對象以及訪問內存內容的一些描述符。在內存塊中存儲Python對象不會存儲對象本身,而是存儲對象contents的對象。再次訪問內容每次構造一個新的Python對象!

   15.17.1.20。可變大小的數據類型

ctypes 爲可變大小的數組和結構提供了一些支持。

resize()函數可用於調整現有ctypes對象的內存緩衝區的大小。該函數將對象作爲第一個參數,並將請求的大小(以字節爲單位)作爲第二個參數。內存塊不能小於對象類型指定的自然內存塊,ValueError如果嘗試,則引發a :

>>> short_array = (c_short * 4)()
>>> print sizeof(short_array)
8
>>> resize(short_array, 4)
Traceback (most recent call last):
    ...
ValueError: minimum size is 8
>>> resize(short_array, 32)
>>> sizeof(short_array)
32
>>> sizeof(type(short_array))
8
>>>

這很好很好,但是如何訪問此數組中包含的其他元素?由於類型仍然只知道4個元素,因此訪問其他元素時會出錯:

>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
    ...
IndexError: invalid index
>>>

使用可變大小數據類型的另一種方法ctypes是使用Python的動態特性,並且在已知所需大小之後(根據具體情況)重新定義數據類型。

  15.17.2。ctypes引用

  15.17.2.1。查找共享庫

使用編譯語言編程時,在編譯/鏈接程序時以及程序運行時都會訪問共享庫。

find_library()函數的目的是以類似於編譯器的方式定位庫(在具有多個版本的共享庫的平臺上應該加載最新版本),而ctypes庫加載器就像程序運行時一樣,並直接調用運行時加載程序。

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

ctypes.util.find_library姓名

嘗試查找庫並返回路徑名。 名字是不一樣的任何前綴庫名的lib,標的相同.so.dylib或版本號(這是用於POSIX鏈接器選項的形式-l)。如果找不到庫,則返回None

確切的功能取決於系統。

在Linux上,find_library()嘗試運行外部程序(/sbin/ldconfiggcc,和objdump)找到庫文件。它返回庫文件的文件名。這裏有些例子:

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

在OS X上,find_library()嘗試幾個預定義的命名方案和路徑來定位庫,如果成功則返回完整路徑名:

>>> 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()沿系統搜索路徑進行搜索,並返回完整路徑名,但由於沒有預定義的命名方案,因此調用find_library("c")將失敗並返回None

如果包裝共享庫ctypes,它可以更好地確定在開發時共享庫的名字,並硬編碼到封裝模塊,而不是使用find_library()定位在運行時庫。

   15.17.2.2。加載共享庫

有幾種方法可以將共享庫加載到Python進程中。一種方法是實例化以下類之一:

class ctypes.CDLLnamemode = DEFAULT_MODEhandle = Noneuse_errno = Falseuse_last_error = False 

此類的實例表示已加載的共享庫。這些庫中的函數使用標準C調用約定,並假定返回 int

class ctypes.OleDLLnamemode = DEFAULT_MODEhandle = Noneuse_errno = Falseuse_last_error = False 

僅限Windows:此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall調用約定,並假定返回特定於Windows的HRESULT代碼。 HRESULT values包含指定函數調用是否失敗或成功的信息,以及其他錯誤代碼。如果返回值表示失敗,WindowsError則自動引發a。

class ctypes.WinDLLnamemode = DEFAULT_MODEhandle = Noneuse_errno = Falseuse_last_error = False 

僅限Windows:此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall調用約定,並且int默認情況下假定返回。

在Windows CE上,僅使用標準調用約定,以方便 WinDLLOleDLL使用此平臺上的標準調用約定。

Python 全局解釋器鎖在調用這些庫導出的任何函數之前發佈,之後重新獲取。

class ctypes.PyDLLnamemode = DEFAULT_MODEhandle = None 

CDLL除了在函數調用期間釋放Python GIL之外,此類的實例的行爲與實例類似,並且在函數執行之後,將檢查Python錯誤標誌。如果設置了錯誤標誌,則會引發Python異常。

因此,這僅對直接調用Python C api函數有用。

所有這些類都可以通過使用至少一個參數(共享庫的路徑名)調用它們來實例化。如果已有已加載的共享庫的現有句柄,則可以將其作爲handle命名參數傳遞,否則使用基礎平臺dlopenLoadLibrary 函數將庫加載到進程中,並獲取它的句柄。

模式參數可用於指定庫的加載方式。有關詳細信息,請參閱dlopen(3)聯機幫助頁。在Windows上,模式被忽略。在posix系統上,始終添加RTLD_NOW,並且不可配置。

use_errno參數,當設置爲true,使一個ctypes機制,允許訪問該系統errno以安全的方式錯誤號。 ctypes維護系統errno 變量的線程局部副本; 如果在函數調用與ctypes私有副本交換之前調用用函數調用創建的外部函數,use_errno=True則在 errno函數調用之後立即發生相同的操作。

該函數ctypes.get_errno()返回ctypes私有副本的值,該函數將ctypes私有副本ctypes.set_errno()更改爲新值並返回前一個值。

use_last_error參數,設置爲true時,使能由所述管理Windows錯誤代碼相同的機制GetLastError()和 SetLastError()Windows API函數; ctypes.get_last_error()並 ctypes.set_last_error()用於請求和更改Windows錯誤代碼的ctypes私有副本。

新的2.6版:use_last_erroruse_errno可選參數添加。

ctypes.RTLD_GLOBAL

用作模式參數的標誌。在此標誌不可用的平臺上,它被定義爲整數零。

ctypes.RTLD_LOCAL

用作模式參數的標誌。在沒有此功能的平臺上,它與RTLD_GLOBAL相同。

ctypes.DEFAULT_MODE

用於加載共享庫的默認模式。在OSX 10.3上,這是 RTLD_GLOBAL,否則它與RTLD_LOCAL相同。

這些類的實例沒有公共方法。共享庫導出的函數可以作爲屬性或索引進行訪問。請注意,通過屬性訪問函數會緩存結果,因此每次重複訪問它都會返回相同的對象。另一方面,通過索引訪問它每次都會返回一個新對象:

>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

可以使用以下公共屬性,它們的名稱以下劃線開頭,以便不與導出的函數名衝突:

PyDLL._handle

用於訪問庫的系統句柄。

PyDLL._name

在構造函數中傳遞的庫的名稱。

還可以使用其中一個預製對象(LibraryLoader通過調用 LoadLibrary()方法)或通過將庫檢索爲加載程序實例的屬性來加載共享庫。

class ctypes.LibraryLoaderdlltype 

加載共享庫的類。 dlltype應該是一個 CDLLPyDLLWinDLLOleDLL類型。

__getattr__()具有特殊行爲:它允許通過將其作爲庫加載器實例的屬性訪問來加載共享庫。結果是緩存的,因此重複的屬性訪問每次都返回相同的庫。

LoadLibrary名字

將共享庫加載到進程中並返回它。此方法始終返回庫的新實例。

這些預製庫加載器可用:

ctypes.cdll

創建CDLL實例。

ctypes.windll

僅限Windows:創建WinDLL實例。

ctypes.oledll

僅限Windows:創建OleDLL實例。

ctypes.pydll

創建PyDLL實例。

爲了直接訪問C Python api,可以使用現成的Python共享庫對象:

ctypes.pythonapi

它的一個實例PyDLL將Python C API函數公開爲屬性。請注意,假設所有這些函數都返回C int,這當然不總是事實,因此您必須分配正確的restype屬性才能使用這些函數。

   15.17.2.3。外來函數

如前一節所述,外部函數可以作爲加載的共享庫的屬性進行訪問。默認情況下以這種方式創建的函數對象接受任意數量的參數,接受任何ctypes數據實例作爲參數,並返回庫加載器指定的默認結果類型。他們是私人班級的實例:

ctypes._FuncPtr

C可調用外部函數的基類。

外部函數的實例也是C兼容的數據類型; 它們代表C函數指針。

可以通過分配外部函數對象的特殊屬性來自定義此行爲。

restype

指定ctypes類型以指定外部函數的結果類型。使用Nonevoid,功能不返回任何東西。

可以分配不是ctypes類型的可調用Python對象,在這種情況下假定函數返回C int,並且將使用此整數調用callable,從而允許進一步處理或錯誤檢查。不推薦使用它,爲了更靈活的後處理或錯誤檢查,請使用ctypes數據類型, restype併爲該errcheck屬性分配一個callable 。

argtypes

分配ctypes類型的元組以指定函數接受的參數類型。使用stdcall調用約定的函數只能使用與此元組的長度相同的參數數來調用; 使用C調用約定的函數也接受其他未指定的參數。

當調用外部函數時,每個實際參數都傳遞給 元組中from_param()項的 類方法argtypes,此方法允許將實際參數調整爲外部函數接受的對象。例如,元組中的c_char_pargtypes將使用ctypes轉換規則將作爲參數傳遞的unicode字符串轉換爲字節字符串。

新增:現在可以將項目放在不是ctypes類型的argtypes中,但每個項目必須有一個from_param()返回可用作參數的值的方法(整數,字符串,ctypes實例)。這允許定義可以將自定義對象調整爲函數參數的適配器。

errcheck

爲此屬性分配Python函數或其他可調用函數。將使用三個或更多參數調用callable:

callable結果函數參數

result是外部函數返回的內容,由restype屬性指定 。

func是外部函數對象本身,這允許重用相同的可調用對象來檢查或後處理幾個函數的結果。

arguments是一個包含最初傳遞給函數調用的參數的元組,這允許對所使用的參數進行特殊處理。

此函數返回的對象將從外部函數調用返回,但如果外部函數調用失敗,它還可以檢查結果值並引發異常。

異常ctypes.ArgumentError

當外部函數調用無法轉換其中一個傳遞的參數時,會引發此異常。

   15.17.2.4。函數原型

也可以通過實例化函數原型來創建外部函數。函數原型類似於C中的函數原型; 它們描述了一個函數(返回類型,參數類型,調用約定)而沒有定義實現。必須使用所需的結果類型和函數的參數類型調用工廠函數。

ctypes.CFUNCTYPErestype* argtypesuse_errno = Falseuse_last_error = False 

返回的函數原型創建使用標準C調用約定的函數。該功能將在通話期間釋放GIL。如果 use_errno設置爲true,則系統errno變量的ctypes私有副本將 與errno調用前後的實際值進行交換; use_last_error對Windows錯誤代碼執行相同操作。

版本2.6中已更改:添加了可選的use_errnouse_last_error參數。

ctypes.WINFUNCTYPErestype* argtypesuse_errno = Falseuse_last_error = False 

僅適用於Windows:返回的函數原型創建使用函數 stdcall調用約定,除了Windows CE地方 WINFUNCTYPE()是一樣的CFUNCTYPE()。該功能將在通話期間釋放GIL。 use_errnouse_last_error具有與上面相同的含義。

ctypes.PYFUNCTYPErestype* argtypes 

返回的函數原型創建使用Python調用約定的函數。該功能在通話期間不會釋放GIL。

由這些工廠函數創建的函數原型可以以不同的方式實例化,具體取決於調用中參數的類型和數量:

prototype地址

返回指定地址的外部函數,該函數必須是整數。

prototype可贖回

從Python 可調用創建C可調用函數(回調函數)。

prototypefunc_spec [,paramflags ] )

返回由共享庫導出的外部函數。func_spec必須是2元組。第一項是導出函數的名稱作爲字符串,或導出函數的序號作爲小整數。第二項是共享庫實例。(name_or_ordinal, library)

prototypevtbl_indexname [,paramflags [,iid ] ] )

返回將調用COM方法的外部函數。vtbl_index是虛函數表的索引,是一個小的非負整數。name是COM方法的名稱。iid是指向擴展錯誤報告中使用的接口標識符的可選指針。

COM方法使用特殊的調用約定:除了argtypes元組中指定的那些參數外,它們還需要指向COM接口的指針作爲第一個參數。

可選的paramflags參數創建的外部函數包裝器具有比上述功能更多的功能。

paramflags必須是一個長度相同的元組argtypes

此元組中的每個項目都包含有關參數的更多信息,它必須是包含一個,兩個或三個項目的元組。

第一項是一個整數,包含參​​數的方向標誌組合:

1

指定函數的輸入參數。

2

輸出參數。外來函數填入一個值。

4

輸入參數,默認爲整數零。

可選的第二項是參數名稱爲字符串。如果指定了此參數,則可以使用命名參數調用外部函數。

可選的第三項是此參數的默認值。

此示例演示如何包裝Windows MessageBoxA函數,以便它支持默認參數和命名參數。Windows頭文件中的C聲明是這樣的:

WINUSERAPI int WINAPI
MessageBoxA(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType);

這是包裝ctypes

>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)
>>>

現在可以通過以下方式調用MessageBox外部函數:

>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")
>>>

第二個例子演示了輸出參數。win32 GetWindowRect 函數通過將指定窗口的尺寸複製到RECT調用者必須提供的結構中來檢索它們的尺寸 。這是C聲明:

WINUSERAPI BOOL WINAPI
GetWindowRect(
     HWND hWnd,
     LPRECT lpRect);

這是包裝ctypes

>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError
>>> from ctypes.wintypes import BOOL, HWND, RECT
>>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
>>> paramflags = (1, "hwnd"), (2, "lprect")
>>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)
>>>

具有輸出參數的函數將自動返回輸出參數值(如果存在單個參數值)或者包含輸出參數值的元組(如果有多個),則GetWindowRect函數現在會在調用時返回RECT實例。

輸出參數可以與errcheck協議組合以進行進一步的輸出處理和錯誤檢查。win32 GetWindowRectapi函數返回一個BOOL指示成功或失敗的信號,因此該函數可以執行錯誤檢查,並在api調用失敗時引發異常:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     return args
...
>>> GetWindowRect.errcheck = errcheck
>>>

如果errcheck函數返回參數tuple,它接收不變,ctypes繼續對輸出參數進行的正常處理。如果要返回窗口座標而不是RECT實例的元組 ,可以檢索函數中的字段並返回它們,將不再進行正常處理:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     rc = args[1]
...     return rc.left, rc.top, rc.bottom, rc.right
...
>>> GetWindowRect.errcheck = errcheck
>>>

   15.17.2.5。實用功能

ctypes.addressofobj 

以整數形式返回內存緩衝區的地址。 obj必須是ctypes類型的實例。

ctypes.alignmentobj_or_type 

返回ctypes類型的對齊要求。obj_or_type必須是ctypes類型或實例。

ctypes.byrefobj [,offset ] )

返回一個指向obj的輕量級指針,該指針必須是ctypes類型的一個實例。 offset默認爲零,並且必須是將添加到內部指針值的整數。

byref(obj, offset) 對應於此C代碼:

(((char *)&obj) + offset)

返回的對象只能用作外部函數調用參數。它的行爲類似pointer(obj),但構造速度要快得多。

:在2.6版本中的新偏移添加可選的參數。

ctypes.castobjtype 

此函數類似於C中的強制轉換運算符。它返回一個新的類型實例,它指向與obj相同的內存塊。 type 必須是指針類型,obj必須是可以解釋爲指針的對象。

ctypes.create_string_bufferinit_or_size [,size ] )

此函數創建一個可變字符緩衝區。返回的對象是ctypes數組c_char

init_or_size必須是一個整數,它指定數組的大小,或者是一個用於初始化數組項的字符串。

如果將字符串指定爲第一個參數,則將緩衝區設置爲大於字符串長度的一個項目,以便數組中的最後一個元素是NUL終止字符。可以將整數作爲第二個參數傳遞,如果不應使用字符串的長度,則允許指定數組的大小。

如果第一個參數是unicode字符串,則根據ctypes轉換規則將其轉換爲8位字符串。

ctypes.create_unicode_bufferinit_or_size [,size ] )

此函數創建一個可變的unicode字符緩衝區。返回的對象是ctypes數組c_wchar

init_or_size必須是指定數組大小的整數,或者是用於初始化數組項的unicode字符串。

如果將unicode字符串指定爲第一個參數,則將緩衝區設置爲大於字符串長度的一個項目,以便數組中的最後一個元素是NUL終止字符。可以將整數作爲第二個參數傳遞,如果不應使用字符串的長度,則允許指定數組的大小。

如果第一個參數是8位字符串,則根據ctypes轉換規則將其轉換爲unicode字符串。

ctypes.DllCanUnloadNow()

僅限Windows:此函數是一個鉤子,允許使用ctypes實現進程內COM服務器。從DllCanUnloadNow函數調用_ctypes擴展dll導出。

ctypes.DllGetClassObject()

僅限Windows:此函數是一個鉤子,允許使用ctypes實現進程內COM服務器。它是從_ctypes擴展dll導出的DllGetClassObject函數調用的。

ctypes.util.find_library名字

嘗試查找庫並返回路徑名。 name是沒有任何前綴的庫名,如lib後綴.so.dylib或版本號(這是用於posix鏈接器選項的形式-l)。如果找不到庫,則返回None

確切的功能取決於系統。

在版本2.6中更改:僅限Windows:find_library("m")find_library("c")將調用的結果返回到find_msvcrt()

ctypes.util.find_msvcrt()

僅限Windows:返回Python使用的VC運行時庫的文件名,以及擴展模塊。如果無法確定庫的名稱,None則返回。

如果需要釋放內存,例如,由擴展模塊調用的內存,則必須在分配內存的同一庫中使用該功能。free(void *)

版本2.6中的新功能。

ctypes.FormatError([ code ] )

僅適用於Windows:返回的錯誤代碼的文本描述代碼。如果未指定錯誤代碼,則通過調用Windows api函數GetLastError來使用上一個錯誤代碼。

ctypes.GetLastError()

僅限Windows:返回Windows在調用線程中設置的最後一個錯誤代碼。此函數直接調用Windows GetLastError()函數,它不返回錯誤代碼的ctypes-private副本。

ctypes.get_errno()

返回errno調用線程中系統變量的ctypes-private副本的當前值 。

版本2.6中的新功能。

ctypes.get_last_error()

僅限Windows:返回LastError調用線程中系統變量的ctypes-private副本的當前值 。

版本2.6中的新功能。

ctypes.memmovedstsrccount 

與標準C memmove庫函數相同:將計數字節從 src複製到dstdstsrc必須是可以轉換爲指針的整數或ctypes實例。

ctypes.memsetdstccount 

與標準C memset庫函數相同:使用值c的計數字節填充地址dst處的內存塊。dst必須是指定地址的整數或ctypes實例。

ctypes.POINTER類型

此工廠函數創建並返回新的ctypes指針類型。指針類型在內部被緩存和重用,因此重複調用此函數很便宜。type必須是ctypes類型。

ctypes.pointerobj 

此函數創建一個指向obj的新指針實例。返回的對象屬於該類型POINTER(type(obj))

注意:如果您只想將指向對象的指針傳遞給外部函數調用,則應該使用byref(obj)哪個更快。

ctypes.resizeobjsize 

此函數調整obj的內部內存緩衝區的大小,obj必須是ctypes類型的實例。如下所示,不可能使緩衝區小於對象類型的本機大小sizeof(type(obj)),但可以放大緩衝區。

ctypes.set_conversion_mode編碼錯誤

此函數設置ctypes對象在8位字符串和unicode字符串之間進行轉換時使用的規則。 encoding必須是指定編碼的字符串,如'utf-8''mbcs'錯誤必須是一個字符串,指定編碼/解碼錯誤的錯誤處理。可能的值的實例是"strict""replace",或"ignore"

set_conversion_mode()返回包含先前轉換規則的2元組。在Windows上,初始轉換規則在其他系統上。('mbcs', 'ignore')('ascii', 'strict')

ctypes.set_errno

errno 調用線程中系統變量的ctypes-private副本的當前值設置爲value並返回先前的值。

版本2.6中的新功能。

ctypes.set_last_error

僅限Windows:將LastError調用線程中系統變量的ctypes-private副本的當前值設置 爲value並返回先前的值。

版本2.6中的新功能。

ctypes.sizeofobj_or_type 

返回ctypes類型或實例內存緩衝區的大小(以字節爲單位)。與C sizeof運算符相同。

ctypes.string_at地址[,大小] )

這個函數返回了從內存地址字符串地址。如果指定了size,則將其用作size,否則假定該字符串爲零終止。

ctypes.WinErrorcode = Nonedescr = None 

僅限Windows:此函數可能是ctypes中最糟糕的名稱。它創建了一個WindowsError實例。如果未指定代碼, GetLastError則調用以確定錯誤代碼。如果descr未指定,FormatError()則調用以獲取錯誤的文本描述。

ctypes.wstring_at地址[,大小] )

這個函數返回了從內存地址寬字符串 地址爲unicode字符串。如果指定了size,則將其用作字符串的字符數,否則假定該字符串爲零終止。

   15.17.2.6。數據類型

ctypes._CData

這個非公共類是所有ctypes數據類型的公共基類。除此之外,所有ctypes類型實例都包含一個保存C兼容數據的內存塊; addressof()輔助函數返回內存塊的地址 。另一個實例變量暴露爲 _objects; 這包含其他需要保持活動的Python對象,以防內存塊包含指針。

ctypes數據類型的常用方法,這些都是類方法(確切地說,它們是元類的方法):

from_buffer來源[,偏移] )

此方法返回共享對象的緩衝區的ctypes實例 。所述對象必須支持可寫緩衝器接口。可選的offset參數指定源緩衝區的偏移量(以字節爲單位); 默認值爲零。如果源緩衝區不夠大,ValueError則會引發a。

版本2.6中的新功能。

from_buffer_copy來源[,偏移] )

此方法創建一個ctypes實例,從對象緩衝區複製緩衝區,該 緩衝區必須是可讀的。可選的offset 參數指定源緩衝區的偏移量(以字節爲單位); 默認值爲零。如果源緩衝區不夠大,ValueError則會引發a。

版本2.6中的新功能。

from_address地址

此方法使用address指定的內存返回ctypes類型實例,該內存 必須是整數。

from_paramobj 

此方法使obj適應ctypes類型。當外部函數的argtypes元組中存在類型時,使用外部函數調用中使用的實際對象調用它; 它必須返回一個可以用作函數調用參數的對象。

所有ctypes數據類型都具有此類方法的默認實現,如果是類型的實例,則通常返回obj。某些類型也接受其他對象。

in_dll圖書館名稱

此方法返回由共享庫導出的ctypes類型實例。name是導出數據的符號的名稱,library 是加載的共享庫。

ctypes數據類型的常見實例變量:

_b_base_

有時ctypes數據實例不擁有它們包含的內存塊,而是共享基礎對象的部分內存塊。所述 _b_base_只讀構件是根ctypes的對象擁有該存儲器塊。

_b_needsfree_

當ctypes數據實例已分配內存塊本身時,此只讀變量爲true,否則爲false。

_objects

該成員None或者是包含需要保持活動的Python對象的字典,以便內存塊內容保持有效。該對象僅用於調試; 永遠不要修改這本詞典的內容。

   15.17.2.7。基本數據類型

ctypes._SimpleCData

這個非公共類是所有基本ctypes數據類型的基類。這裏提到它是因爲它包含基本ctypes數據類型的公共屬性。 _SimpleCData是它的子類 _CData,因此它繼承了它們的方法和屬性。

在版本2.6中更改:現在可以對不包含指針但不包含指針的ctypes數據類型進行pickle。

實例具有單個屬性:

value

該屬性包含實例的實際值。對於整數和指針類型,它是一個整數,對於字符類型,它是單個字符串,對於字符指針類型,它是Python字符串或unicode字符串。

value屬性從一個ctypes實例獲取,通常是一個新的對象,每次返回。 ctypes沒有實現原來的目標回報率,總是一個新的對象被創建。所有其他ctypes對象實例也是如此。

基本數據類型作爲外部函數調用結果返回時,或者例如通過檢索結構字段成員或數組項,將透明地轉換爲本機Python類型。換句話說,如果一個外國函數有一個 restypec_char_p,你總是會收到一個Python字符串, 不是一個c_char_p實例。

基本數據類型的子類不會繼承此行爲。因此,如果外部函數restype是其子類c_void_p,您將從函數調用中接收此子類的實例。當然,您可以通過訪問value屬性來獲取指針的值。

這些是基本的ctypes數據類型:

ctypes.c_byte

表示C 數據類型,並將該值解釋爲小整數。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。signed char

ctypes.c_char

表示C char數據類型,並將該值解釋爲單個字符。構造函數接受可選的字符串初始值設定項,字符串的長度必須恰好是一個字符。

ctypes.c_char_p

當它指向以零結尾的字符串時表示C 數據類型。對於也可能指向二進制數據的通用字符指針, 必須使用。構造函數接受整數地址或字符串。char *POINTER(c_char)

ctypes.c_double

表示C double數據類型。構造函數接受可選的float初始化程序。

ctypes.c_longdouble

表示C 數據類型。構造函數接受可選的float初始化程序。在它是別名的平臺上。long doublesizeof(long double) == sizeof(double)c_double

版本2.6中的新功能。

ctypes.c_float

表示C float數據類型。構造函數接受可選的float初始化程序。

ctypes.c_int

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。在它是別名的平臺上。signed intsizeof(int) == sizeof(long)c_long

ctypes.c_int8

表示C 8位數據類型。通常是別名 。signed intc_byte

ctypes.c_int16

表示C 16位數據類型。通常是別名 。signed intc_short

ctypes.c_int32

表示C 32位數據類型。通常是別名 。signed intc_int

ctypes.c_int64

表示C 64位數據類型。通常是別名 。signed intc_longlong

ctypes.c_long

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。signed long

ctypes.c_longlong

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。signed long long

ctypes.c_short

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。signed short

ctypes.c_size_t

表示C size_t數據類型。

ctypes.c_ssize_t

表示C ssize_t數據類型。

版本2.7中的新功能。

ctypes.c_ubyte

表示C 數據類型,它將值解釋爲小整數。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。unsigned char

ctypes.c_uint

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。在它是別名的平臺上。unsigned intsizeof(int) == sizeof(long)c_ulong

ctypes.c_uint8

表示C 8位數據類型。通常是別名 。unsigned intc_ubyte

ctypes.c_uint16

表示C 16位數據類型。通常是別名 。unsigned intc_ushort

ctypes.c_uint32

表示C 32位數據類型。通常是別名 。unsigned intc_uint

ctypes.c_uint64

表示C 64位數據類型。通常是別名 。unsigned intc_ulonglong

ctypes.c_ulong

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。unsigned long

ctypes.c_ulonglong

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。unsigned long long

ctypes.c_ushort

表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。unsigned short

ctypes.c_void_p

表示C 類型。該值表示爲整數。構造函數接受可選的整數初始值設定項。void *

ctypes.c_wchar

表示C wchar_t數據類型,並將該值解釋爲單個字符的unicode字符串。構造函數接受可選的字符串初始值設定項,字符串的長度必須恰好是一個字符。

ctypes.c_wchar_p

表示C 數據類型,該數據類型必須是指向以零結尾的寬字符串的指針。構造函數接受整數地址或字符串。wchar_t *

ctypes.c_bool

表示C bool數據類型(更準確地說,_Bool來自C99)。它的值可以是True或者False,構造函數接受任何具有真值的對象。

版本2.6中的新功能。

ctypes.HRESULT

僅限Windows:表示一個HRESULT值,其中包含函數或方法調用的成功或錯誤信息。

ctypes.py_object

表示C 數據類型。在沒有參數的情況下調用它會創建一個指針。PyObject *NULL PyObject *

ctypes.wintypes模塊提供了相當長的一段其他Windows特定的數據類型,例如HWNDWPARAMDWORD。一些有用的結構,如MSGRECT定義。

   15.17.2.8。結構化數據類型

class ctypes.Union* args** kw 

原始字節順序的聯合的抽象基類。

class ctypes.BigEndianStructure* args** kw 

大端字節順序結構的抽象基類。

class ctypes.LittleEndianStructure* args** kw 

小端字節順序結構的抽象基類。

具有非本機字節順序的結構不能包含指針類型字段或包含指針類型字段的任何其他數據類型。

class ctypes.Structure* args** kw 

本機字節順序的結構的抽象基類。

必須通過繼承其中一種類型來創建具體結構和聯合類型,並至少定義一個_fields_類變量。ctypes將創建描述符 s,允許通過直接屬性訪問來讀取和寫入字段。這些是

_fields_

定義結構字段的序列。項目必須是2元組或3元組。第一項是字段的名稱,第二項指定字段的類型; 它可以是任何ctypes數據類型。

對於整數類型字段c_int,可以給出第三個可選項。它必須是一個小的正整數,用於定義字段的位寬。

字段名稱在一個結構或聯合中必須是唯一的。未選中此選項,重複名稱時只能訪問一個字段。

可以在定義Structure子類的類語句之後定義_fields_類變量,這允許創建直接或間接引用自身的數據類型:

class List(Structure):
    pass
List._fields_ = [("pnext", POINTER(List)),
                 ...
                ]

_fields_類變量但是,必須被定義在第一次使用之前的類型(創建一個實例,sizeof()被稱爲其上,等等)。稍後對_fields_類變量的賦值將引發AttributeError。

可以定義結構類型的子類,它們繼承基類的字段以及_fields_子子類中定義的字段(如果有的話)。

_pack_

一個可選的小整數,允許覆蓋實例中結構字段的對齊方式。 _pack_必須在_fields_分配時定義,否則它將無效。

_anonymous_

一個可選序列,列出未命名(匿名)字段的名稱。 _anonymous_必須在_fields_分配時定義,否則它將無效。

此變量中列出的字段必須是結構或聯合類型字段。 ctypes將在結構類型中創建允許直接訪問嵌套字段的描述符,而無需創建結構或聯合字段。

這是一個示例類型(Windows):

class _U(Union):
    _fields_ = [("lptdesc", POINTER(TYPEDESC)),
                ("lpadesc", POINTER(ARRAYDESC)),
                ("hreftype", HREFTYPE)]

class TYPEDESC(Structure):
    _anonymous_ = ("u",)
    _fields_ = [("u", _U),
                ("vt", VARTYPE)]

TYPEDESC結構描述了COM數據類型,該vt字段指定哪個聯合字段有效。由於該u字段被定義爲匿名字段,因此現在可以直接從TYPEDESC實例訪問成員。td.lptdesc並且td.u.lptdesc 是等價的,但前者更快,因爲它不需要創建臨時聯合實例:

td = TYPEDESC()
td.vt = VT_PTR
td.lptdesc = POINTER(some_type)
td.u.lptdesc = POINTER(some_type)

可以定義結構的子類,它們繼承基類的字段。如果子類定義具有單獨的 _fields_變量,則在此指定的字段將附加到基類的字段中。

結構和聯合構造函數接受位置和關鍵字參數。位置參數用於按照出現的順序初始化成員字段_fields_。構造函數中的關鍵字參數被解釋爲屬性賦值,因此它們將_fields_使用相同的名稱進行初始化 ,或者爲不存在的名稱創建新屬性_fields_

   15.17.2.9。數組和指針

class ctypes.Array* args 

數組的抽象基類。

創建具體數組類型的推薦方法是將任何ctypes數據類型與正整數相乘 。或者,您可以繼承此類型並定義_length__type_類變量。可以使用標準下標和切片訪問來讀取和寫入數組元素; 對於切片讀取,所得到的物體是 本身Array

_length_

一個正整數,指定數組中元素的數量。超出範圍的下標導致IndexError。將由返回len()

_type_

指定數組中每個元素的類型。

數組子類構造函數接受位置參數,用於按順序初始化元素。

ctypes._Pointer

指針的私有抽象基類。

通過POINTER()使用將指向的類型調用來創建具體指針類型; 這是由自動完成的 pointer()

如果指針指向數組,則可以使用標準下標和切片訪問來讀取和寫入其元素。指針對象沒有大小,因此len()會提高TypeError。否定下標將在指針之前從內存中讀取(如在C中),並且超出範圍的下標可能會因訪問衝突而崩潰(如果您很幸運)。

_type_

指定指向的類型。

contents

返回指針指向的對象。分配給此屬性會將指針更改爲指向指定的對象。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章