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_long
32位系統上類型的別名。因此,c_long
如果您打算如果打印,則不應該感到困惑c_int
- 它們實際上是相同的類型。
15.17.1.1。加載動態鏈接庫
ctypes
導出cdll,以及Windows windll和oledll 對象,用於加載動態鏈接庫。
您可以通過訪問它們作爲這些對象的屬性來加載庫。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不會嘗試通過魔法選擇其中一個,您必須通過指定GetModuleHandleA
或GetModuleHandleW
顯式訪問所需的版本,然後分別使用字符串或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 |
- 構造函數接受具有真值的任何對象。
所有這些類型都可以通過使用正確類型和值的可選初始化程序調用它們來創建:
>>> 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
,所以這些例子只是在控制檯提示符下運行,而不是從內IDLE或PythonWin的:
>>> 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。結構和聯合
結構和聯合必須從導出Structure
和Union
其中所定義的基類ctypes
模塊。每個子類都必須定義一個_fields_
屬性。 _fields_
必須是2元組的列表 ,包含字段名稱和字段類型。
字段類型必須是ctypes
類型c_int
或任何其他派生ctypes
類型:結構,聯合,數組,指針。
下面是一個POINT結構的簡單示例,它包含兩個名爲x和y的整數 ,還顯示瞭如何在構造函數中初始化結構:
>>> from ctypes import *
>>> class POINT(Structure):
... _fields_ = [("x", c_int),
... ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print point.x, point.y
10 20
>>> point = POINT(y=5)
>>> print point.x, point.y
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
使用結構和聯合的本機字節順序。要建立與非本地字節順序結構,你可以使用一個 BigEndianStructure
,LittleEndianStructure
, 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 cell
ctypes
cell
_fields_
>>> from ctypes import *
>>> class cell(Structure):
... pass
...
>>> cell._fields_ = [("name", c_char_p),
... ("next", POINTER(cell))]
>>>
讓我們試試吧。我們創建了兩個實例cell
,並讓它們相互指向,最後跟隨指針鏈幾次:
>>> c1 = cell()
>>> c1.name = "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_FrozenModules
Python導出的 指針。
引用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 2
rc.a, rc.b = rc.b, rc.a
>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>
請注意,temp0
並且temp1
還在使用的內部緩衝器的對象rc
上述對象。因此執行將緩衝區內容複製到緩衝區中。反過來,這改變了內容。因此,最後一項任務,沒有預期的效果。rc.a = temp0
temp0
rc
temp1
rc.b = temp1
請記住,從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/ldconfig
,gcc
,和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.
CDLL
(name,mode = DEFAULT_MODE,handle = None,use_errno = False,use_last_error = False )
此類的實例表示已加載的共享庫。這些庫中的函數使用標準C調用約定,並假定返回 int
。
class ctypes.
OleDLL
(name,mode = DEFAULT_MODE,handle = None,use_errno = False,use_last_error = False )
僅限Windows:此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall
調用約定,並假定返回特定於Windows的HRESULT
代碼。 HRESULT
values包含指定函數調用是否失敗或成功的信息,以及其他錯誤代碼。如果返回值表示失敗,WindowsError
則自動引發a。
class ctypes.
WinDLL
(name,mode = DEFAULT_MODE,handle = None,use_errno = False,use_last_error = False )
僅限Windows:此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall
調用約定,並且int
默認情況下假定返回。
在Windows CE上,僅使用標準調用約定,以方便 WinDLL
並OleDLL
使用此平臺上的標準調用約定。
Python 全局解釋器鎖在調用這些庫導出的任何函數之前發佈,之後重新獲取。
class ctypes.
PyDLL
(name,mode = DEFAULT_MODE,handle = None )
CDLL
除了在函數調用期間未釋放Python GIL之外,此類的實例的行爲與實例類似,並且在函數執行之後,將檢查Python錯誤標誌。如果設置了錯誤標誌,則會引發Python異常。
因此,這僅對直接調用Python C api函數有用。
所有這些類都可以通過使用至少一個參數(共享庫的路徑名)調用它們來實例化。如果已有已加載的共享庫的現有句柄,則可以將其作爲handle
命名參數傳遞,否則使用基礎平臺dlopen
或LoadLibrary
函數將庫加載到進程中,並獲取它的句柄。
該模式參數可用於指定庫的加載方式。有關詳細信息,請參閱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_error和use_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.
LibraryLoader
(dlltype )
加載共享庫的類。 dlltype應該是一個 CDLL
,PyDLL
,WinDLL
或OleDLL
類型。
__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類型以指定外部函數的結果類型。使用None
的void
,功能不返回任何東西。
可以分配不是ctypes類型的可調用Python對象,在這種情況下假定函數返回C int
,並且將使用此整數調用callable,從而允許進一步處理或錯誤檢查。不推薦使用它,爲了更靈活的後處理或錯誤檢查,請使用ctypes數據類型, restype
併爲該errcheck
屬性分配一個callable 。
argtypes
分配ctypes類型的元組以指定函數接受的參數類型。使用stdcall
調用約定的函數只能使用與此元組的長度相同的參數數來調用; 使用C調用約定的函數也接受其他未指定的參數。
當調用外部函數時,每個實際參數都傳遞給 元組中from_param()
項的 類方法argtypes
,此方法允許將實際參數調整爲外部函數接受的對象。例如,元組中的c_char_p
項argtypes
將使用ctypes轉換規則將作爲參數傳遞的unicode字符串轉換爲字節字符串。
新增:現在可以將項目放在不是ctypes類型的argtypes中,但每個項目必須有一個from_param()
返回可用作參數的值的方法(整數,字符串,ctypes實例)。這允許定義可以將自定義對象調整爲函數參數的適配器。
errcheck
爲此屬性分配Python函數或其他可調用函數。將使用三個或更多參數調用callable:
callable
(結果,函數,參數)
result是外部函數返回的內容,由restype
屬性指定 。
func是外部函數對象本身,這允許重用相同的可調用對象來檢查或後處理幾個函數的結果。
arguments是一個包含最初傳遞給函數調用的參數的元組,這允許對所使用的參數進行特殊處理。
此函數返回的對象將從外部函數調用返回,但如果外部函數調用失敗,它還可以檢查結果值並引發異常。
異常ctypes.
ArgumentError
當外部函數調用無法轉換其中一個傳遞的參數時,會引發此異常。
15.17.2.4。函數原型
也可以通過實例化函數原型來創建外部函數。函數原型類似於C中的函數原型; 它們描述了一個函數(返回類型,參數類型,調用約定)而沒有定義實現。必須使用所需的結果類型和函數的參數類型調用工廠函數。
ctypes.
CFUNCTYPE
(restype,* argtypes,use_errno = False,use_last_error = False )
返回的函數原型創建使用標準C調用約定的函數。該功能將在通話期間釋放GIL。如果 use_errno設置爲true,則系統errno
變量的ctypes私有副本將 與errno
調用前後的實際值進行交換; use_last_error對Windows錯誤代碼執行相同操作。
版本2.6中已更改:添加了可選的use_errno和use_last_error參數。
ctypes.
WINFUNCTYPE
(restype,* argtypes,use_errno = False,use_last_error = False )
僅適用於Windows:返回的函數原型創建使用函數 stdcall
調用約定,除了Windows CE地方 WINFUNCTYPE()
是一樣的CFUNCTYPE()
。該功能將在通話期間釋放GIL。 use_errno和use_last_error具有與上面相同的含義。
ctypes.
PYFUNCTYPE
(restype,* argtypes )
返回的函數原型創建使用Python調用約定的函數。該功能在通話期間不會釋放GIL。
由這些工廠函數創建的函數原型可以以不同的方式實例化,具體取決於調用中參數的類型和數量:
prototype
(地址)返回指定地址的外部函數,該函數必須是整數。
prototype
(可贖回)從Python 可調用創建C可調用函數(回調函數)。
prototype
(func_spec [,paramflags ] )返回由共享庫導出的外部函數。func_spec必須是2元組。第一項是導出函數的名稱作爲字符串,或導出函數的序號作爲小整數。第二項是共享庫實例。
(name_or_ordinal, library)
prototype
(vtbl_index,name [,paramflags [,iid ] ] )返回將調用COM方法的外部函數。vtbl_index是虛函數表的索引,是一個小的非負整數。name是COM方法的名稱。iid是指向擴展錯誤報告中使用的接口標識符的可選指針。
COM方法使用特殊的調用約定:除了
argtypes
元組中指定的那些參數外,它們還需要指向COM接口的指針作爲第一個參數。可選的paramflags參數創建的外部函數包裝器具有比上述功能更多的功能。
paramflags必須是一個長度相同的元組
argtypes
。此元組中的每個項目都包含有關參數的更多信息,它必須是包含一個,兩個或三個項目的元組。
第一項是一個整數,包含參數的方向標誌組合:
1
指定函數的輸入參數。
2
輸出參數。外來函數填入一個值。
4
輸入參數,默認爲整數零。
可選的第二項是參數名稱爲字符串。如果指定了此參數,則可以使用命名參數調用外部函數。
可選的第三項是此參數的默認值。
此示例演示如何包裝Windows 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 GetWindowRect
api函數返回一個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.
addressof
(obj )
以整數形式返回內存緩衝區的地址。 obj必須是ctypes類型的實例。
ctypes.
alignment
(obj_or_type )
返回ctypes類型的對齊要求。obj_or_type必須是ctypes類型或實例。
ctypes.
byref
(obj [,offset ] )
返回一個指向obj的輕量級指針,該指針必須是ctypes類型的一個實例。 offset默認爲零,並且必須是將添加到內部指針值的整數。
byref(obj, offset)
對應於此C代碼:
(((char *)&obj) + offset)
返回的對象只能用作外部函數調用參數。它的行爲類似pointer(obj)
,但構造速度要快得多。
:在2.6版本中的新的偏移添加可選的參數。
ctypes.
cast
(obj,type )
此函數類似於C中的強制轉換運算符。它返回一個新的類型實例,它指向與obj相同的內存塊。 type 必須是指針類型,obj必須是可以解釋爲指針的對象。
ctypes.
create_string_buffer
(init_or_size [,size ] )
此函數創建一個可變字符緩衝區。返回的對象是ctypes數組c_char
。
init_or_size必須是一個整數,它指定數組的大小,或者是一個用於初始化數組項的字符串。
如果將字符串指定爲第一個參數,則將緩衝區設置爲大於字符串長度的一個項目,以便數組中的最後一個元素是NUL終止字符。可以將整數作爲第二個參數傳遞,如果不應使用字符串的長度,則允許指定數組的大小。
如果第一個參數是unicode字符串,則根據ctypes轉換規則將其轉換爲8位字符串。
ctypes.
create_unicode_buffer
(init_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.
memmove
(dst,src,count )
與標準C memmove庫函數相同:將計數字節從 src複製到dst。dst和src必須是可以轉換爲指針的整數或ctypes實例。
ctypes.
memset
(dst,c,count )
與標準C memset庫函數相同:使用值c的計數字節填充地址dst處的內存塊。dst必須是指定地址的整數或ctypes實例。
ctypes.
POINTER
(類型)
此工廠函數創建並返回新的ctypes指針類型。指針類型在內部被緩存和重用,因此重複調用此函數很便宜。type必須是ctypes類型。
ctypes.
pointer
(obj )
此函數創建一個指向obj的新指針實例。返回的對象屬於該類型POINTER(type(obj))
。
注意:如果您只想將指向對象的指針傳遞給外部函數調用,則應該使用byref(obj)
哪個更快。
ctypes.
resize
(obj,size )
此函數調整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.
sizeof
(obj_or_type )
返回ctypes類型或實例內存緩衝區的大小(以字節爲單位)。與C sizeof
運算符相同。
ctypes.
string_at
(地址[,大小] )
這個函數返回了從內存地址字符串地址。如果指定了size,則將其用作size,否則假定該字符串爲零終止。
ctypes.
WinError
(code = None,descr = 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_param
(obj )
此方法使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類型。換句話說,如果一個外國函數有一個 restype
的c_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 double
sizeof(long double) == sizeof(double)
c_double
版本2.6中的新功能。
類ctypes.
c_float
表示C float
數據類型。構造函數接受可選的float初始化程序。
類ctypes.
c_int
表示C 數據類型。構造函數接受可選的整數初始值設定項; 沒有進行溢出檢查。在它是別名的平臺上。signed int
sizeof(int) == sizeof(long)
c_long
類ctypes.
c_int8
表示C 8位數據類型。通常是別名 。signed int
c_byte
類ctypes.
c_int16
表示C 16位數據類型。通常是別名 。signed int
c_short
類ctypes.
c_int32
表示C 32位數據類型。通常是別名 。signed int
c_int
類ctypes.
c_int64
表示C 64位數據類型。通常是別名 。signed int
c_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 int
sizeof(int) == sizeof(long)
c_ulong
類ctypes.
c_uint8
表示C 8位數據類型。通常是別名 。unsigned int
c_ubyte
類ctypes.
c_uint16
表示C 16位數據類型。通常是別名 。unsigned int
c_ushort
類ctypes.
c_uint32
表示C 32位數據類型。通常是別名 。unsigned int
c_uint
類ctypes.
c_uint64
表示C 64位數據類型。通常是別名 。unsigned int
c_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特定的數據類型,例如HWND
,WPARAM
或DWORD
。一些有用的結構,如MSG
或RECT
定義。
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
返回指針指向的對象。分配給此屬性會將指針更改爲指向指定的對象。