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
返回指针指向的对象。分配给此属性会将指针更改为指向指定的对象。