On this page
ctypes — Python 的外部函数库
ctypes是 Python 的外部函数库。它提供 C 兼容的数据类型,并允许在 DLL 或共享库中调用函数。它可以用于将这些库包装在纯 Python 中。
ctypes tutorial
注意:本教程中的代码示例使用doctest来确保它们确实有效。由于某些代码示例在 Linux,Windows 或 Mac OS X 下的行为不同,因此它们在 Comments 中包含 doctest 指令。
注意:一些代码示例引用了 ctypes c_int类型。在sizeof(long) == sizeof(int)
的平台上,它是c_long的别名。因此,如果您希望c_int,则不要混淆c_long的打印-它们实际上是相同的类型。
加载动态链接库
ctypes导出* cdll ,并在 Windows 上导出 windll 和 oledll *对象,以加载动态链接库。
pass将库作为这些对象的属性进行访问来加载它们。 * cdll 加载使用标准cdecl
调用约定导出函数的库,而 windll *使用stdcall
调用约定调用函数。 * oledll *也使用stdcall
调用约定,并假定函数返回 Windows HRESULT
错误代码。错误代码用于在函数调用失败时自动引发OSError异常。
在版本 3.3 中进行了更改:Windows 错误曾经引发WindowsError,现在是OSError的别名。
以下是 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
文件后缀。
Note
passcdll.msvcrt
访问标准 C 库将使用该库的过时版本,该版本可能与 Python 所使用的版本不兼容。尽可能使用本机 Python Function,或者导入并使用msvcrt
模块。
在 Linux 上,需要指定文件名包括extensions 以加载库,因此属性访问不能用于加载库。应该使用 dll 加载器的LoadLibrary()
方法,或者应该pass调用构造函数创建 CDLL 实例来加载库:
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")
>>> libc
<CDLL 'libc.so.6', handle ... at ...>
>>>
从加载的 dll 访问Function
函数作为 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
>>>
请注意,诸如kernel32
和user32
之类的 win32 系统 dll 通常会导出函数的 ANSI 以及 UNICODE 版本。导出的 UNICODE 版本的名称后带有W
,而导出的 ANSI 版本的名称后带有A
。 win32 GetModuleHandle
函数为给定的模块名称返回模块句柄,具有以下 C 原型,并且根据是否定义了 UNICODE,使用宏将其中一个作为GetModuleHandle
公开:
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
- windll *不会try魔术地选择它们之一,必须pass显式指定
GetModuleHandleA
或GetModuleHandleW
来访问所需的版本,然后分别使用字节或字符串对象来调用它。
有时,dll 导出的函数名称不是有效的 Python 标识符,例如"??2@YAPAXI@Z"
。在这种情况下,您必须使用getattr()来检索函数:
>>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
<_FuncPtr object at 0x...>
>>>
在 Windows 上,某些 dll 导出Function不是按名称而是按序。可以pass用序号索引 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
>>>
Calling functions
您可以像调用其他任何 Python 一样调用这些函数。此示例使用time()
函数(返回自 Unix 纪元以来以秒为单位的系统时间)和GetModuleHandleA()
函数(返回 Win32 模块句柄)。
本示例使用NULL
指针调用这两个函数(应将None
用作NULL
指针):
>>> print(libc.time(None))
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
>>>
当您使用cdecl
调用约定调用stdcall
函数时,会引发ValueError,反之亦然:
>>> cdll.kernel32.GetModuleHandleA(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with not enough arguments (4 bytes missing)
>>>
>>> windll.msvcrt.printf(b"spam")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>
要找出正确的调用约定,您必须查看 C 头文件或要调用的函数的文档。
在 Windows 上,ctypes使用 win32 结构化异常处理来防止使用无效参数值调用函数时由于一般保护错误而导致崩溃:
>>> windll.kernel32.GetModuleHandleA(32)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>
但是,有足够的方法可以使ctypes使 Python 崩溃。因此,无论如何,您都应该小心。 faulthandler模块有助于调试崩溃(例如,由于错误的 C 库调用而产生的分段错误)。
None
,整数,字节对象和(unicode)字符串是唯一可以直接用作这些函数调用中的参数的本机 Python 对象。 None
作为 C NULL
指针传递,字节对象和字符串作为指针传递到包含其数据(char *
或wchar_t *
)的存储块。 Python 整数作为平台默认的 C int
类型传递,其值被屏蔽以适合 C 类型。
在 continue 使用其他参数类型调用函数之前,我们必须了解有关ctypes数据类型的更多信息。
基本数据类型
ctypes定义了许多与 C 兼容的原始数据类型:
ctypes type | C type | Python type |
---|---|---|
c_bool | _Bool |
bool (1) |
c_char | char |
1 个字符字节对象 |
c_wchar | wchar_t |
1-character string |
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_size_t | size_t |
int |
c_ssize_t | ssize_t 或Py_ssize_t |
int |
c_float | float |
float |
c_double | double |
float |
c_longdouble | long double |
float |
c_char_p | char * (NUL 已终止) |
字节对象或None |
c_wchar_p | wchar_t * (NUL 已终止) |
字符串或None |
c_void_p | void * |
int 或None |
- 构造函数接受具有真值的任何对象。
所有这些类型都可以pass使用正确类型和值的可选初始化程序来调用来创建:
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> 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_wchar_p(s)
>>> print(c_s)
c_wchar_p(139966785747344)
>>> print(c_s.value)
Hello World
>>> c_s.value = "Hi, there"
>>> print(c_s) # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s) # first object is unchanged
Hello, World
>>>
但是,您应注意不要将它们传递给期望指向可变内存的指针的函数。如果您需要可变的内存块,则 ctypes 具有create_string_buffer()函数,该函数可以pass各种方式创建它们。可以使用raw
属性访问(或更改)当前存储块的内容;如果要以 NUL 终止的字符串访问它,请使用value
属性:
>>> from ctypes import *
>>> p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string
>>> print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
>>> print(sizeof(p), repr(p.raw))
10 b'Hello\x00\x00\x00\x00\x00'
>>> p.value = b"Hi"
>>> print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
>>>
create_string_buffer()函数替代了c_buffer()
函数(仍可作为别名使用)以及早期 ctypes 版本中的c_string()
函数。要创建包含 C 类型wchar_t
的 Unicode 字符的可变存储器块,请使用create_unicode_buffer()函数。
通话Function,continue
请注意,printf 可以打印到实际的标准输出通道,而不是sys.stdout,因此不能在* IDLE 或 PythonWin *内部运行:
>>> printf = libc.printf
>>> printf(b"Hello, %s\n", b"World!")
Hello, World!
14
>>> printf(b"Hello, %S\n", "World!")
Hello, World!
14
>>> printf(b"%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf(b"%f bottles of beer\n", 42.5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2
>>>
如前所述,除整数,字符串和字节对象外,所有 Python 类型都必须包装在其对应的ctypes类型中,以便可以将它们转换为所需的 C 数据类型:
>>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>
使用您自己的自定义数据类型的调用Function
您还可以自定义ctypes参数转换,以允许将自己的类的实例用作函数参数。 ctypes查找_as_parameter_
属性,并将其用作函数参数。当然,它必须是整数,字符串或字节之一:
>>> class Bottles:
... def __init__(self, number):
... self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf(b"%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>
如果您不想将实例的数据存储在_as_parameter_
实例变量中,则可以定义一个property,以便根据要求提供该属性。
指定所需的参数类型(函数原型)
pass设置argtypes
属性,可以指定从 DLL 导出的函数所需的参数类型。
argtypes
必须是 C 数据类型的序列(在这里printf
函数可能不是一个好例子,因为它根据格式字符串需要一个可变的数字和不同类型的参数,另一方面,对此进行实验非常方便 Feature):
>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>>
指定格式可以防止参数类型不兼容(就像 C 函数的原型一样),并try将参数转换为有效类型:
>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf(b"%s %d %f\n", b"X", 2, 3)
X 2 3.000000
13
>>>
如果定义了自己的传递给函数调用的类,则必须实现from_param()
类方法,它们才能在argtypes
序列中使用。 from_param()
类方法接收传递给函数调用的 Python 对象,它应进行类型检查或确保该对象可接受的所有必要操作,然后返回对象本身,其_as_parameter_
属性或要作为对象传递的任何对象这种情况下的 C 函数参数。同样,结果应为整数,字符串,字节,ctypes实例或具有_as_parameter_
属性的对象。
Return types
默认情况下,假定函数返回 C int
类型。可以pass设置函数对象的restype
属性来指定其他返回类型。
这是一个更高级的示例,它使用strchr
函数,该函数需要一个字符串指针和一个 char,然后返回指向字符串的指针:
>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))
8059983
>>> strchr.restype = c_char_p # c_char_p is a pointer to a string
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>
如果要避免上述ord("x")
调用,可以设置argtypes
属性,第二个参数将从单个字符的 Python 字节对象转换为 C 字符:
>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
>>>
如果外部函数返回整数,则还可以将可调用的 Python 对象(例如,函数或类)用作restype
属性。可调用对象将使用 C 函数返回的* integer *进行调用,并且此调用的结果将用作函数调用的结果。这对于检查错误返回值并自动引发异常很有用:
>>> GetModuleHandle = windll.kernel32.GetModuleHandleA
>>> def ValidHandle(value):
... if value == 0:
... raise WinError()
... return value
...
>>>
>>> GetModuleHandle.restype = ValidHandle
>>> GetModuleHandle(None)
486539264
>>> GetModuleHandle("something silly")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>
WinError
是一个函数,它将调用 Windows FormatMessage()
api 以获取错误代码的字符串表示形式,并“返回”异常。 WinError
带有一个可选的错误代码参数,如果不使用该参数,它将调用GetLastError()进行检索。
请注意,passerrcheck
属性可以使用更强大的错误检查机制。有关详细信息,请参见参考手册。
传递指针(或:pass引用传递参数)
有时,C api 函数希望将指针指向数据类型作为参数,可能会写入相应的位置,或者如果数据太大而无法按值传递。这也称为pass引用传递参数。
ctypes导出byref()函数,该函数用于pass引用传递参数。使用pointer()函数可以实现相同的效果,尽管pointer()可以完成很多工作,因为它构造了一个 true 的指针对象,因此如果您不需要 Python 本身中的指针对象,可以更快地使用byref():
>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer(b'\000' * 32)
>>> print(i.value, f.value, repr(s.value))
0 0.0 b''
>>> libc.sscanf(b"1 3.14 Hello", b"%d %f %s",
... byref(i), byref(f), s)
3
>>> print(i.value, f.value, repr(s.value))
1 3.1400001049 b'Hello'
>>>
结构和联合
结构和联合必须从ctypes模块中定义的Structure和UnionBase Class 派生。每个子类必须定义一个_fields_
属性。 _fields_
必须是* 2-tuples 的列表,其中包含 field name 和 field type *。
字段类型必须是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>
TypeError: too many initializers
>>>
但是,您可以构建更复杂的结构。pass将结构用作字段类型,结构本身可以包含其他结构。
这是一个 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
>>>
嵌套结构还可以pass几种方式在构造函数中初始化:
>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))
字段descriptor可以从* class *检索,它们对于调试很有用,因为它们可以提供有用的信息:
>>> print(POINT.x)
<Field type=c_long, ofs=0, size=4>
>>> print(POINT.y)
<Field type=c_long, ofs=4, size=4>
>>>
Warning
ctypes不支持按值将具有位字段的并集或结构传递给函数。尽管这可以在 32 位 x86 上运行,但不能保证库在一般情况下都可以正常工作。具有位域的并集和结构应始终pass指针传递给函数。
结构/联合对齐和字节 Sequences
默认情况下,Structure 和 Union 字段以与 C 编译器相同的方式对齐。pass在子类定义中指定_pack_
类属性,可以覆盖此行为。必须将其设置为正整数,并指定字段的最大对齐方式。 #pragma pack(n)
在 MSVC 中也是如此。
ctypes使用本机字节 Sequences 表示结构和联合。要以 nonlocal 字节 Sequences 构建结构,可以使用BigEndianStructure,LittleEndianStructure,BigEndianUnion
和LittleEndianUnion
Base Class 之一。这些类不能包含指针字段。
结构和联合中的位字段
可以创建包含位字段的结构和联合。位字段仅适用于整数字段,位宽指定为_fields_
Tuples 的第三项:
>>> 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>
>>>
Arrays
数组是序列,包含固定数量的相同类型的实例。
建议的创建数组类型的方法是将数据类型与正整数相乘:
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
>>>
pass调用该类以通常的方式创建实例:
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, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>
Pointers
指针实例是pass对ctypes类型调用pointer()函数来创建的:
>>> 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 函数收到一个指针,并且您知道该指针实际上指向一个数组而不是单个项目时,才使用此Function。
在幕后,pointer()函数的作用不仅仅是创建指针实例,还必须首先创建指针* types *。这是passPOINTER()函数完成的,该函数接受任何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
>>>
Type conversions
通常,ctypes 进行严格的类型检查。这意味着,如果函数的argtypes
列表中包含POINTER(c_int)
或作为结构定义中成员字段的类型,则仅接受类型完全相同的实例。该规则有一些 exception,其中 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
>>>
另外,如果在argtypes
中将函数参数明确语句为指针类型(例如POINTER(c_int)
),则可以将指向类型的对象(在这种情况下为c_int
)传递给函数。在这种情况下,ctypes 将自动应用所需的byref()转换。
要将 POINTER 类型字段设置为NULL
,可以分配None
:
>>> bar.values = None
>>>
有时您有不兼容类型的实例。在 C 中,可以将一种类型转换为另一种类型。 ctypes提供了cast()函数,可以以相同的方式使用。上面定义的Bar
结构的values
字段接受POINTER(c_int)
指针或c_int数组,但不接受其他类型的实例:
>>> 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()函数可用于将 ctypes 实例转换为指向不同 ctypes 数据类型的指针。 cast()带有两个参数,一个 ctypes 对象(可以转换为某种类型的指针或可以将其转换为某种类型的指针),以及一个 ctypes 指针类型。它返回第二个参数的实例,该实例引用与第一个参数相同的内存块:
>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>
因此,cast()可用于将结构分配给Bar
的values
字段:
>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0
>>>
Incomplete Types
不完整的类型是尚未指定其成员的结构,联合或数组。在 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
>>>
因为新的class cell
在类语句本身中不可用。在ctypes中,我们可以定义cell
类并稍后在 class 语句之后设置_fields_
属性:
>>> from ctypes import *
>>> class cell(Structure):
... pass
...
>>> cell._fields_ = [("name", c_char_p),
... ("next", POINTER(cell))]
>>>
让我们try一下。我们创建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, end=" ")
... p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>
Callback functions
ctypes允许从 Python 可调用对象创建 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))
>>>
首先,这是一个简单的回调,显示了它传递的值:
>>> def py_cmp_func(a, b):
... print("py_cmp_func", a[0], b[0])
... return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>
The result:
>>> 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]
...
>>>
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func))
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>
正如我们可以轻松检查的那样,我们的数组现在已排序:
>>> for i in ia: print(i, end=" ")
...
1 5 7 33 99
>>>
函数工厂可以用作装饰器工厂,因此我们也可以这样写:
>>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
... def py_cmp_func(a, b):
... print("py_cmp_func", a[0], b[0])
... return a[0] - b[0]
...
>>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func)
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>
Note
确保保留对CFUNCTYPE()对象的引用,只要它们是从 C 代码中使用的即可。 ctypes不会,如果您不这样做,它们可能会被垃圾回收,并在进行回调时使程序崩溃。
另外,请注意,如果在 Python 控件之外创建的线程中调用了回调函数(例如,pass调用回调的外来代码),则 ctypes 在每次调用时都会创建一个新的虚拟 Python 线程。此行为在大多数情况下都是正确的,但是这意味着用threading.local存储的值将无法在不同的回调中保留,即使这些调用是从同一 C 线程进行的也是如此。
访问从 dll 导出的值
一些共享库不仅导出函数,而且还导出变量。 Python 库本身的一个示例是Py_OptimizeFlag,它是设置为 0、1 或 2 的整数,具体取决于启动时给出的-O或-OO标志。
ctypes可以使用该类型的in_dll()
类方法访问此类值。 * pythonapi *是 sched 义的符号,可以访问 Python C api:
>>> opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
>>> print(opt_flag)
c_long(0)
>>>
如果解释器以-O开头,则 samples 将打印c_long(1)
,如果指定了-OO则将打印c_long(2)
。
一个扩展的示例也演示了如何使用指针来访问 Python 导出的PyImport_FrozenModules指针。
将文档报价为该值:
Note
该指针被初始化为指向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
是struct_frozen
记录数组的pointer
,因此我们可以对其进行迭代,但是我们只需要确保循环终止,因为指针没有大小。迟早它可能会因访问冲突或其他原因而崩溃,因此最好在遇到NULL
条目时跳出循环:
>>> for item in table:
... if item.name is None:
... break
... print(item.name.decode("ascii"), item.size)
...
_frozen_importlib 31764
_frozen_importlib_external 41499
__hello__ 161
__phello__ -161
__phello__.spam 161
>>>
标准 Python 具有冻结的模块和冻结的软件包(由否定的size
成员表示)的事实是众所周知的,它仅用于测试。例如,使用import __hello__
进行try。
Surprises
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
并没有达到预期的效果。
请记住,从“结构”,“联合”和“数组”中检索子对象不会“复制”子对象,而是会检索访问根对象的基础缓冲区的包装对象。
另一个行为可能与预期不同的示例是:
>>> s = c_char_p()
>>> s.value = b"abc def ghi"
>>> s.value
b'abc def ghi'
>>> s.value is s.value
False
>>>
Note
从c_char_p实例化的对象只能将其值设置为字节或整数。
为什么打印False
? ctypes 实例是包含一个内存块以及一些访问内存内容的descriptor的对象。将 Python 对象存储在内存块中并不存储对象本身,而是存储对象的contents
。再次访问内容每次都会构造一个新的 Python 对象!
大小可变的数据类型
ctypes为可变大小的数组和结构提供了一些支持。
resize()函数可用于调整现有 ctypes 对象的内存缓冲区大小。该函数将对象作为第一个参数,并将请求的字节大小作为第二个参数。不能使内存块小于由对象类型指定的自然内存块,如果try这样做,将引发ValueError:
>>> 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 的动态特性,并根据具体情况在已知所需大小之后重新定义数据类型。
ctypes reference
查找共享库
使用编译语言编程时,在编译/链接程序以及运行程序时将访问共享库。
find_library()
函数的目的是按照与编译器或运行时加载程序类似的方式定位库(在具有多个版本的共享库的平台上,应加载最新版本),而 ctypes 库加载器的行为类似于程序运行,然后直接调用运行时加载程序。
ctypes.util
模块提供了有助于确定要加载的库的Function。
ctypes.util.
find_library
(* name *)- try查找库并返回路径名。 * name 是库名称,没有任何前缀,如 lib *,后缀如
.so
,.dylib
或版本号(这是 posix 链接器选项-l
的形式)。如果找不到库,则返回None
。
- try查找库并返回路径名。 * name 是库名称,没有任何前缀,如 lib *,后缀如
确切的Function取决于系统。
在 Linux 上,find_library()
try运行外部程序(/sbin/ldconfig
,gcc
,objdump
和ld
)以查找库文件。它返回库文件的文件名。
在版本 3.6 中更改:在 Linux 上,如果无法pass任何其他方式找到库,则在搜索库时将使用环境变量LD_LIBRARY_PATH
的值。
这里有些例子:
>>> 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()
try几种 sched 义的命名方案和路径来查找库,如果成功,则返回完整的路径名:
>>> 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()
沿系统搜索路径搜索,并返回完整路径名,但是由于没有 sched 义的命名方案,因此find_library("c")
之类的调用将失败并返回None
。
加载共享库
有几种方法可以将共享库加载到 Python 进程中。一种方法是实例化以下类之一:
-
- class *
ctypes.
CDLL
(* name , mode = DEFAULT_MODE , handle = None , use_errno = False , use_last_error = False , winmode = 0 *)
- 此类的实例表示已加载的共享库。这些库中的函数使用标准的 C 调用约定,并假定返回
int
。
- class *
-
- class *
ctypes.
OleDLL
(* name , mode = DEFAULT_MODE , handle = None , use_errno = False , use_last_error = False , winmode = 0 *)
- class *
在 3.3 版中进行了更改:WindowsError曾经被提出过。
-
- class *
ctypes.
WinDLL
(* name , mode = DEFAULT_MODE , handle = None , use_errno = False , use_last_error = False , winmode = 0 *)
- 仅限 Windows:此类的实例表示已加载的共享库,这些库中的函数使用
stdcall
调用约定,并且默认情况下假定返回int
。
- class *
在 Windows CE 上,仅使用标准调用约定,为方便起见,WinDLL和OleDLL在此平台上使用标准调用约定。
在调用这些库导出的任何函数之前,已释放 Python 全局解释器锁,此后重新获取。
-
- class *
ctypes.
PyDLL
(* name , mode = DEFAULT_MODE , handle = None *)
- 此类的实例的行为类似于CDLL实例,不同之处在于在函数调用期间未释放 Python GIL,并且在函数执行后检查了 Python 错误标志。如果设置了错误标志,则会引发 Python 异常。
- class *
因此,这仅对直接调用 Python C api 函数有用。
pass使用至少一个参数(共享库的路径名)调用它们,可以实例化所有这些类。如果您具有已加载的共享库的现有句柄,则可以将其作为handle
命名参数传递,否则将使用基础平台dlopen
或LoadLibrary
函数将库加载到进程中并为其获取句柄。
mode 参数可用于指定如何加载库。有关详细信息,请查阅 dlopen(3) 联机帮助页。在 Windows 上, mode *被忽略。在 posix 系统上,始终添加 RTLD_NOW,并且不可配置。
use_errno *参数设置为 true 时,将启用 ctypes 机制,该机制允许以安全的方式访问系统errno错误号。 ctypes维护系统errno变量的线程本地副本;如果您调用用
use_errno=True
创建的外部函数,则函数调用之前的errno值将与 ctypes 私有副本交换,在函数调用之后立即发生同样的情况。
函数ctypes.get_errno()返回 ctypes 私有副本的值,函数ctypes.set_errno()将 ctypes 私有副本更改为新值并返回以前的值。
- use_last_error *参数设置为 true 时,将为 Windows 错误代码启用相同的机制,该机制由GetLastError()和
SetLastError()
Windows API 函数 Management; ctypes.get_last_error()和ctypes.set_last_error()用于请求和更改 Windows 错误代码的 ctypes 专用副本。
在 Windows 上,* winmode 参数用于指定库的加载方式(因为 mode *被忽略)。它采用对 Win32 API LoadLibraryEx
标志参数有效的任何值。Ellipsis时,默认设置是使用导致最安全的 DLL 加载的标志,以避免 DLL 劫持等问题。将完整路径传递给 DLL 是确保加载正确的库和依赖项的最安全方法。
在 3.8 版中进行了更改:添加了* winmode *参数。
ctypes.
RTLD_GLOBAL
- 标记用作* mode *参数。在此标志不可用的平台上,它定义为整数零。
ctypes.
RTLD_LOCAL
- 标记用作* mode 参数。在不可用的平台上,它与 RTLD_GLOBAL *相同。
ctypes.
DEFAULT_MODE
- 用于加载共享库的默认模式。在 OSX 10.3 上,这是* RTLD_GLOBAL ,否则与 RTLD_LOCAL *相同。
这些类的实例没有公共方法。共享库导出的函数可以作为属性或按索引访问。请注意,pass属性访问函数会缓存结果,因此每次重复访问它都会返回相同的对象。另一方面,每次pass索引访问它都会返回一个新对象:
>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6") # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False
以下公共属性可用,其名称以下划线开头,以免与导出的函数名称冲突:
PyDLL.
_handle
- 用于访问库的系统句柄。
PyDLL.
_name
- 在构造函数中传递的库的名称。
共享库也可以pass使用预制对象之一(即LibraryLoader类的实例)来加载,方法是调用LoadLibrary()
方法,或者pass将库检索为加载器实例的属性。
getattr()具有特殊的行为:它允许pass将共享库作为库加载器实例的属性进行访问来加载共享库。结果被缓存,因此重复的属性访问每次都返回相同的库。
LoadLibrary
(* name *)- 将共享库加载到进程中并返回。此方法始终返回该库的新实例。
这些预制的库装载器可用:
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
属性才能使用这些函数。
- PyDLL实例,将 Python C API 函数公开为属性。请注意,所有这些函数都假定返回 C
pass这些对象中的任何一个加载库都会产生一个带有字符串参数name
的auditing event ctypes.dlopen
,该参数用于加载库。
访问已加载的库中的函数会引发审核事件ctypes.dlsym
,该事件带有参数library
(库对象)和name
(符号的名称为字符串或整数)。
Foreign functions
如上一节所述,可以将外部函数作为已加载共享库的属性进行访问。默认情况下,以这种方式创建的函数对象接受任意数量的参数,接受任何 ctypes 数据实例作为参数,并返回由库加载器指定的默认结果类型。它们是私有类的实例:
- 类别
ctypes.
_FuncPtr
- C 可调用的外部函数的 Base Class。
外部函数的实例也是 C 兼容的数据类型。它们代表 C 函数指针。
可以pass分配给外部函数对象的特殊属性来定制此行为。
restype
- 分配 ctypes 类型以指定外部函数的结果类型。将
None
用于void
,该函数不返回任何内容。
- 分配 ctypes 类型以指定外部函数的结果类型。将
可以分配一个非 ctypes 类型的可调用 Python 对象,在这种情况下,该函数假定返回 C int
,并且将使用该整数调用该可调用对象,从而允许进行进一步的处理或错误检查。不建议使用此方法,为了更灵活地进行后期处理或错误检查,请使用 ctypes 数据类型作为restype并将可调用项分配给errcheck属性。
argtypes
- 分配一个 ctypes 类型的 Tuples 以指定该函数接受的参数类型。使用
stdcall
调用约定的函数只能使用与此 Tuples 的长度相同数量的参数来调用。使用 C 调用约定的函数也接受其他未指定的参数。
- 分配一个 ctypes 类型的 Tuples 以指定该函数接受的参数类型。使用
调用外部函数时,每个实际参数都传递给argtypesTuples 中各项的from_param()
类方法,该方法允许将实际参数调整为该外部函数接受的对象。例如,argtypesTuples 中的c_char_p项将使用 ctypes 转换规则将作为参数传递的字符串转换为 bytes 对象。
新增:现在可以将项目放入不是 ctypes 类型的 argtype 中,但是每个项目必须具有from_param()
方法,该方法返回可用作参数的值(整数,字符串,ctypes 实例)。这允许定义适配器,这些适配器可以将自定义对象用作Function参数。
errcheck
- 为该属性分配一个 Python 函数或另一个可调用的函数。该可调用对象将使用三个或更多参数进行调用:
callable
(结果,Function,参数)- *result* is what the foreign function returns, as specified by the `restype` attribute\.
func *是外部函数对象本身,这允许重用相同的可调用对象来检查或后期处理多个函数的结果。
arguments *是一个 Tuples,包含最初传递给函数调用的参数,这可以使所使用参数的行为专门化。
该函数返回的对象将从外部函数调用中返回,但是如果外部函数调用失败,它也可以检查结果值并引发异常。
- exception
ctypes.
ArgumentError
- 当外部函数调用无法转换传递的参数之一时,将引发此异常。
在 Windows 上,当外部函数调用引发系统异常(例如,由于访问冲突)时,将捕获该异常并将其替换为合适的 Python 异常。此外,将引发带有参数code
的审核事件ctypes.seh_exception
,从而允许审核钩子用其自身替换异常。
调用外部函数调用的某些方法可能会引发带有参数function pointer
和arguments
的审核事件ctypes.call_function
。
Function prototypes
外部函数也可以pass实例化函数原型来创建。函数原型类似于 C 中的函数原型。他们在不定义实现的情况下描述了一个函数(返回类型,参数类型,调用约定)。必须使用所需的结果类型和函数的参数类型来调用工厂函数,并且可以将其用作装饰器工厂,并pass@wrapper
语法将其应用于函数。有关示例,请参见Callback functions。
ctypes.
CFUNCTYPE
(* restype ,* argtypes , use_errno = False , use_last_error = False *)ctypes.
WINFUNCTYPE
(* restype ,* argtypes , use_errno = False , use_last_error = False *)- 仅限 Windows:返回的函数原型创建使用
stdcall
调用约定的函数,但在 Windows CE 上,WINFUNCTYPE()与CFUNCTYPE()相同。该函数将在调用过程中释放 GIL。 * use_errno 和 use_last_error *具有与上述相同的含义。
- 仅限 Windows:返回的函数原型创建使用
ctypes.
PYFUNCTYPE
(* restype ,* argtypes *)- 返回的函数原型创建使用 Python 调用约定的函数。该函数将在通话期间不*释放 GIL。
这些工厂函数创建的函数原型可以pass不同的方式实例化,具体取决于调用中参数的类型和数量:
Note
prototype
(地址)在指定的地址处返回一个外部函数,该函数必须为整数。
prototype
(* callable *)从 Python * callable *创建一个 C 可调用函数(回调函数)。
prototype
(* func_spec * [,* paramflags *])返回共享库导出的外部函数。 * func_spec *必须为 2Tuples
(name_or_ordinal, library)
。第一项是导出函数的名称(以字符串形式)或导出函数的序号(以小整数形式)。第二项是共享库实例。prototype
(* vtbl_index , name * [,* paramflags * [,* iid *]])返回将调用 COM 方法的外部函数。 * vtbl_index *是虚拟函数表的索引,它是一个小的非负整数。 * name *是 COM 方法的名称。 * iid *是指向扩展的错误报告中使用的接口标识符的可选指针。
COM 方法使用一种特殊的调用约定:除了argtypes
Tuples 中指定的那些参数外,它们还需要一个指向 COM 接口的指针作为第一个参数。
可选的* paramflags *参数创建的外部函数包装器具有比上述Function更多的Function。
- paramflags *必须是与
argtypes
相同长度的 Tuples。
该 Tuples 中的每个项目都包含有关参数的更多信息,它必须是包含一个,两个或三个项目的 Tuples。
第一项是一个整数,其中包含参数的方向标志的组合:
Note
1
指定函数的 Importing 参数。
2
输出参数。外来函数填写一个值。
4
Importing 参数,默认为整数零。
可选的第二项是参数名称,为字符串。如果指定了此选项,则可以使用命名参数来调用外部函数。
可选的第三项是此参数的默认值。
本示例演示如何包装 Windows MessageBoxW
函数,使其支持默认参数和命名参数。 Windows 头文件中的 C 语句是这样的:
WINUSERAPI int WINAPI
MessageBoxW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType);
这是ctypes的包装:
>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCWSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)
现在可以pass以下方式调用MessageBox
外部函数:
>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")
第二个示例演示输出参数。 win32 GetWindowRect
函数pass将指定窗口的尺寸复制到调用者必须提供的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)
>>>
具有输出参数的函数将在一个值存在时自动返回输出参数值,或者在一个以上的函数中包含输出参数值的 Tuples,因此 GetWindowRect 函数现在在调用时返回一个 RECT 实例。
输出参数可以与errcheck
协议结合使用,以进行进一步的输出处理和错误检查。 win32 GetWindowRect
api 函数返回BOOL
表示成功或失败,因此该函数可以执行错误检查,并在 api 调用失败时引发异常:
>>> def errcheck(result, func, args):
... if not result:
... raise WinError()
... return args
...
>>> GetWindowRect.errcheck = errcheck
>>>
如果errcheck
函数返回的 Tuples 参数不变,则ctypescontinue 对输出参数进行常规处理。如果要返回一个窗口坐标 Tuples 而不是一个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
>>>
Utility functions
ctypes.
addressof
(* obj *)- 以整数形式返回内存缓冲区的地址。 * obj *必须是 ctypes 类型的实例。
用参数obj
引发auditing event ctypes.addressof
。
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)
,但是构造要快得多。
ctypes.
cast
(* obj , type *)- 此函数类似于 C 中的强制转换运算符。它返回* type 的新实例,该实例指向与 obj *相同的内存块。 * type 必须是指针类型,而 obj *必须是可以解释为指针的对象。
ctypes.
create_string_buffer
(* init_or_size , size = None *)- 此函数创建一个可变字符缓冲区。返回的对象是c_char的 ctypes 数组。
- init_or_size *必须是用于指定数组大小的整数,或者是用于初始化数组项的字节对象。
如果将字节对象指定为第一个参数,则使缓冲区长度比其长度大一个项目,以便数组中的最后一个元素为 NUL 终止字符。可以将整数作为第二个参数传递,如果不应使用字节的长度,则该整数允许指定数组的大小。
用参数init
,size
引发auditing event ctypes.create_string_buffer
。
ctypes.
create_unicode_buffer
(* init_or_size , size = None *)- 此函数创建一个可变的 Unicode 字符缓冲区。返回的对象是c_wchar的 ctypes 数组。
- init_or_size *必须是用于指定数组大小的整数,或者是用于初始化数组项的字符串。
如果将字符串指定为第一个参数,则使缓冲区比字符串的长度大一个项目,以使数组中的最后一个元素为 NUL 终止字符。可以将整数作为第二个参数传递,如果不使用字符串的长度,则允许指定数组的大小。
用参数init
,size
引发auditing event ctypes.create_unicode_buffer
。
ctypes.
DllCanUnloadNow
( )- 仅限 Windows:此函数是一个钩子,允许使用 ctypes 实现进程内 COM 服务器。 _ctypes 扩展 dll 导出的 DllCanUnloadNow 函数调用它。
ctypes.
DllGetClassObject
( )- 仅限 Windows:此函数是一个钩子,允许使用 ctypes 实现进程内 COM 服务器。从
_ctypes
扩展 dll 导出的 DllGetClassObject 函数中调用它。
- 仅限 Windows:此函数是一个钩子,允许使用 ctypes 实现进程内 COM 服务器。从
ctypes.util.
find_library
(* name *)- try查找库并返回路径名。 * name *是库名称,没有任何前缀,如
lib
,后缀如.so
,.dylib
或版本号(这是 posix 链接器选项-l
的格式)。如果找不到库,则返回None
。
- try查找库并返回路径名。 * name *是库名称,没有任何前缀,如
确切的Function取决于系统。
ctypes.util.
find_msvcrt
( )- 仅限 Windows:返回 Python 和扩展模块使用的 VC 运行时库的文件名。如果无法确定库的名称,则返回
None
。
- 仅限 Windows:返回 Python 和扩展模块使用的 VC 运行时库的文件名。如果无法确定库的名称,则返回
例如,如果您需要释放内存(由扩展模块pass调用free(void *)
分配),则在分配内存的同一库中使用该函数很重要。
ctypes.
FormatError
([* code *])- 仅限 Windows:返回错误代码* code *的文本描述。如果未指定错误代码,则pass调用 Windows api 函数 GetLastError 使用最后一个错误代码。
ctypes.
GetLastError
( )- 仅 Windows:返回 Windows 在调用线程中设置的最后一个错误代码。此函数直接调用 Windows GetLastError()函数,它不返回错误代码的 ctypes-private 副本。
ctypes.
get_errno
( )- 返回调用线程中系统errno变量的 ctypes-private 副本的当前值。
引发不带参数的auditing event ctypes.get_errno
。
ctypes.
get_last_error
( )- 仅 Windows:在调用线程中返回系统
LastError
变量的 ctypes-private 副本的当前值。
- 仅 Windows:在调用线程中返回系统
引发不带参数的auditing event ctypes.get_last_error
。
ctypes.
memmove
(* dst , src , count *)- 与标准 C memmove 库函数相同:将* count 个字节从 src 复制到 dst *。 * dst 和 src *必须是可以转换为指针的整数或 ctypes 实例。
ctypes.
memset
(* dst , c , count *)- 与标准 C memset 库函数相同:用* count 个值 c 填充地址 dst *处的内存块。 * dst *必须是指定地址的整数,或者是 ctypes 实例。
ctypes.
POINTER
(* type *)- 此工厂函数创建并返回新的 ctypes 指针类型。指针类型在内部缓存和重用,因此重复调用此函数很便宜。 * type *必须是 ctypes 类型。
ctypes.
pointer
(* obj *)- 此函数创建一个新的指针实例,指向* obj *。返回的对象的类型为
POINTER(type(obj))
。
- 此函数创建一个新的指针实例,指向* obj *。返回的对象的类型为
注意:如果只想将指向对象的指针传递给外部函数调用,则应使用byref(obj)
,这要快得多。
ctypes.
resize
(* obj , size *)- 此函数调整* obj *的内部内存缓冲区的大小,该内存必须是 ctypes 类型的实例。如
sizeof(type(obj))
所示,不可能使缓冲区小于对象类型的原始大小,但是可以扩大缓冲区。
- 此函数调整* obj *的内部内存缓冲区的大小,该内存必须是 ctypes 类型的实例。如
ctypes.
set_errno
(* value *)- 将调用线程中系统errno变量的 ctypes-private 副本的当前值设置为* value *并返回前一个值。
用参数errno
引发auditing event ctypes.set_errno
。
ctypes.
set_last_error
(* value *)- 仅限 Windows:将调用线程中系统
LastError
变量的 ctypes-private 副本的当前值设置为* value *,并返回先前的值。
- 仅限 Windows:将调用线程中系统
用参数error
引发auditing event ctypes.set_last_error
。
ctypes.
sizeof
(* obj_or_type *)- 返回 ctypes 类型或实例内存缓冲区的大小(以字节为单位)。与 C
sizeof
运算符相同。
- 返回 ctypes 类型或实例内存缓冲区的大小(以字节为单位)。与 C
ctypes.
string_at
(* address , size = -1 *)- 此函数返回以内存地址* address *开始的 C 字符串作为字节对象。如果指定了大小,则将其用作大小,否则假定字符串以零结尾。
用参数address
,size
引发auditing event ctypes.string_at
。
ctypes.
WinError
(* code = None , descr = None *)- 仅 Windows:此函数可能是 ctypes 中命名最差的东西。它创建 OSError 的实例。如果未指定* code ,则调用
GetLastError
以确定错误代码。如果未指定 descr *,则调用FormatError()以获取错误的文本描述。
- 仅 Windows:此函数可能是 ctypes 中命名最差的东西。它创建 OSError 的实例。如果未指定* code ,则调用
在版本 3.3 中更改:曾经创建的WindowsError实例。
ctypes.
wstring_at
(* address , size = -1 *)- 此函数返回以内存地址* address 开头的宽字符串。如果指定 size *,则将其用作字符串的字符数,否则假定该字符串以零结尾。
用参数address
,size
引发auditing event ctypes.wstring_at
。
Data types
- 类别
ctypes.
_CData
- 此非公共类是所有 ctypes 数据类型的通用 Base Class。除其他外,所有 ctypes 类型实例都包含一个存储与 C 兼容的数据的内存块。 addressof() helper 函数返回该存储块的地址。另一个实例变量公开为_objects;它包含其他 Python 对象,以防内存块包含指针。
ctypes 数据类型的通用方法,这些都是类方法(确切地说,它们是metaclass的方法):
from_buffer
(源 [,偏移])- 该方法返回一个共享* source *对象缓冲区的 ctypes 实例。 * source 对象必须支持可写缓冲区接口。可选的 offset *参数指定到源缓冲区的偏移量,以字节为单位;默认值为零。如果源缓冲区不够大,则会引发ValueError。
用参数pointer
,size
,offset
引发auditing event ctypes.cdata/buffer
。
from_buffer_copy
(源 [,偏移])- 此方法创建一个 ctypes 实例,从必须可读的* source 对象缓冲区中复制缓冲区。可选的 offset *参数指定到源缓冲区的偏移量,以字节为单位;默认值为零。如果源缓冲区不够大,则会引发ValueError。
用参数pointer
,size
,offset
引发auditing event ctypes.cdata/buffer
。
from_address
(地址)- 此方法使用* address *指定的内存返回 ctypes 类型实例,该内存必须为整数。
此方法以及其他间接调用此方法的方法,将引发带有参数address
的auditing event ctypes.cdata
。
from_param
(* obj *)- 该方法使* obj *适应 ctypes 类型。当外部函数的
argtypes
Tuples 中存在类型时,将使用在外部函数调用中使用的实际对象来调用它;它必须返回一个可用作函数调用参数的对象。
- 该方法使* obj *适应 ctypes 类型。当外部函数的
所有 ctypes 数据类型都有该类方法的默认实现,如果该类型的实例,则通常返回* obj *。某些类型也接受其他对象。
in_dll
(Library,名称)- 此方法返回由共享库导出的 ctypes 类型实例。 * name 是导出数据的符号的名称, library *是已加载的共享库。
ctypes 数据类型的常见实例变量:
_b_base_
- 有时,ctypes 数据实例并不拥有它们所包含的存储块,而是共享一个基础对象的存储块的一部分。 b_base只读成员是拥有内存块的根 ctypes 对象。
_b_needsfree_
- 当 ctypes 数据实例已分配了内存块本身时,此只读变量为 true,否则为 false。
_objects
- 该成员是
None
或包含需要保持活动状态的 Python 对象的字典,以便使内存块内容保持有效。该对象仅用于调试;切勿修改该词典的内容。
- 该成员是
基本数据类型
- 类别
ctypes.
_SimpleCData
- 此非公共类是所有基本 ctypes 数据类型的 Base Class。这里提到它是因为它包含基本 ctypes 数据类型的公共属性。 _SimpleCData是_CData的子类,因此它继承了它们的方法和属性。现在可以腌制不包含指针的 ctypes 数据类型。
实例具有单个属性:
value
- 此属性包含实例的实际值。对于整数和指针类型,它是整数,对于字符类型,它是单个字符字节对象或字符串,对于字符指针类型,它是 Python 字节对象或字符串。
从 ctypes 实例检索value
属性时,通常每次都会返回一个新对象。 ctypes 不实现原始对象的返回,总是构造一个新对象。所有其他 ctypes 对象实例也是如此。
基本数据类型在作为外部函数调用结果返回时,或者例如pass检索结构字段成员或数组项而返回时,将透明地转换为本机 Python 类型。换句话说,如果外部函数的restype
为c_char_p,您将始终收到 Python 字节对象,而不是c_char_p实例。
基本数据类型的子类不会继承此行为。因此,如果外部函数restype
是c_void_p的子类,则您将从函数调用中收到此子类的实例。当然,您可以pass访问value
属性来获取指针的值。
这些是基本的 ctypes 数据类型:
类别
ctypes.
c_byte
- 表示 C
signed char
数据类型,并将该值解释为小整数。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_char
- 表示 C
char
数据类型,并将该值解释为单个字符。构造函数接受可选的字符串初始值设定项,字符串的长度必须恰好是一个字符。
- 表示 C
类别
ctypes.
c_char_p
- 当 C
char *
数据类型指向以零结尾的字符串时,表示它。对于也可能指向二进制数据的通用字符指针,必须使用POINTER(c_char)
。构造函数接受整数地址或字节对象。
- 当 C
类别
ctypes.
c_double
- 表示 C
double
数据类型。构造函数接受一个可选的 float 初始化器。
- 表示 C
类别
ctypes.
c_longdouble
- 表示 C
long double
数据类型。构造函数接受一个可选的 float 初始化器。在sizeof(long double) == sizeof(double)
的平台上,它是c_double的别名。
- 表示 C
类别
ctypes.
c_float
- 表示 C
float
数据类型。构造函数接受一个可选的 float 初始化器。
- 表示 C
类别
ctypes.
c_int
- 表示 C
signed int
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。在sizeof(int) == sizeof(long)
的平台上,它是c_long的别名。
- 表示 C
类别
ctypes.
c_int8
- 表示 C 8 位
signed int
数据类型。通常是c_byte的别名。
- 表示 C 8 位
类别
ctypes.
c_int16
- 表示 C 16 位
signed int
数据类型。通常是c_short的别名。
- 表示 C 16 位
类别
ctypes.
c_int32
- 表示 C 32 位
signed int
数据类型。通常是c_int的别名。
- 表示 C 32 位
类别
ctypes.
c_int64
- 表示 C 64 位
signed int
数据类型。通常是c_longlong的别名。
- 表示 C 64 位
类别
ctypes.
c_long
- 表示 C
signed long
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_longlong
- 表示 C
signed long long
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_short
- 表示 C
signed short
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_size_t
- 表示 C
size_t
数据类型。
- 表示 C
类别
ctypes.
c_ssize_t
- 表示 C
ssize_t
数据类型。
- 表示 C
3.2 版中的新Function。
类别
ctypes.
c_ubyte
- 表示 C
unsigned char
数据类型,它将值解释为小整数。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_uint
- 表示 C
unsigned int
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。在sizeof(int) == sizeof(long)
的平台上,它是c_ulong的别名。
- 表示 C
类别
ctypes.
c_uint8
- 表示 C 8 位
unsigned int
数据类型。通常是c_ubyte的别名。
- 表示 C 8 位
类别
ctypes.
c_uint16
- 表示 C 16 位
unsigned int
数据类型。通常是c_ushort的别名。
- 表示 C 16 位
类别
ctypes.
c_uint32
- 表示 C 32 位
unsigned int
数据类型。通常是c_uint的别名。
- 表示 C 32 位
类别
ctypes.
c_uint64
- 表示 C 64 位
unsigned int
数据类型。通常是c_ulonglong的别名。
- 表示 C 64 位
类别
ctypes.
c_ulong
- 表示 C
unsigned long
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_ulonglong
- 表示 C
unsigned long long
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_ushort
- 表示 C
unsigned short
数据类型。构造函数接受一个可选的整数初始化器;没有溢出检查完成。
- 表示 C
类别
ctypes.
c_void_p
- 表示 C
void *
类型。该值表示为整数。构造函数接受一个可选的整数初始化器。
- 表示 C
类别
ctypes.
c_wchar
- 表示 C
wchar_t
数据类型,并将该值解释为单个字符的 unicode 字符串。构造函数接受可选的字符串初始值设定项,字符串的长度必须恰好是一个字符。
- 表示 C
类别
ctypes.
c_wchar_p
- 表示 C
wchar_t *
数据类型,该数据类型必须是指向零终止的宽字符串的指针。构造函数接受整数地址或字符串。
- 表示 C
类别
ctypes.
c_bool
- 表示 C
bool
数据类型(更准确地说,是 C99 的_Bool
)。它的值可以是True
或False
,并且构造函数接受具有真值的任何对象。
- 表示 C
类别
ctypes.
HRESULT
- 仅限 Windows:代表
HRESULT
值,其中包含函数或方法调用的成功或错误信息。
- 仅限 Windows:代表
类别
ctypes.
py_object
- 表示 C PyObject *数据类型。在不带参数的情况下调用它会创建一个
NULL
PyObject *指针。
- 表示 C PyObject *数据类型。在不带参数的情况下调用它会创建一个
ctypes.wintypes
模块提供了许多其他 Windows 特定的数据类型,例如HWND
,WPARAM
或DWORD
。还定义了一些有用的结构,例如MSG
或RECT
。
结构化数据类型
类别
ctypes.
Union
( *args , * kw *)- 联合的抽象 Base Class,其本机字节 Sequences。
类别
ctypes.
BigEndianStructure
( *args , * kw *)-
- big endian *字节 Sequences 的结构的抽象 Base Class。
-
类别
ctypes.
LittleEndianStructure
( *args , * kw *)- 以* little endian *字节 Sequences 排列的结构的抽象 Base Class。
具有 nonlocal 字节 Sequences 的结构不能包含指针类型字段,也不能包含任何其他包含指针类型字段的数据类型。
- 类别
ctypes.
Structure
( *args , * kw *)- 结构以“本机”字节 Sequences 的抽象 Base Class。
必须pass子类化这些类型之一来创建具体的结构和联合类型,并至少定义一个fields类变量。 ctypes将创建descriptor,它们允许pass直接属性访问来读取和写入字段。这些是
_fields_
- 定义结构字段的序列。这些项目必须是 2Tuples 或 3Tuples。第一项是字段的名称,第二项指定字段的类型;它可以是任何 ctypes 数据类型。
对于诸如c_int的整数类型字段,可以给出第三个可选项目。它必须是一个小的正整数,用于定义字段的位宽。
字段名称在一个结构或联合中必须唯一。未选中此选项,重复名称时只能访问一个字段。
可以在定义结构子类的类语句之后*定义fields类变量,这允许创建直接或间接引用自身的数据类型:
class List(Structure):
pass
List._fields_ = [("pnext", POINTER(List)),
...
]
但是,必须在首次使用类型之前定义fields类变量(创建实例,在其上调用sizeof(),依此类推)。以后对fields类变量的分配将引发 AttributeError。
可以定义结构类型的子子类,它们继承 Base Class 的字段以及子子类中定义的fields(如果有)。
_pack_
_anonymous_
此变量中列出的字段必须是结构或联合类型字段。 ctypes将以结构类型创建 Descriptors,该 Descriptors 允许直接访问嵌套字段,而无需创建结构或联合字段。
这是示例类型(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)
可以定义结构的子子类,它们继承 Base Class 的字段。如果子类定义具有单独的fields变量,则在其中指定的字段将附加到 Base Class 的字段。
结构和联合构造函数接受位置参数和关键字参数。位置参数用于初始化成员字段,其 Sequences 与它们在fields中出现的 Sequences 相同。构造函数中的关键字参数被解释为属性分配,因此它们将使用相同的名称初始化fields,或为fields中不存在的名称创建新的属性。
数组和指针
- 类别
ctypes.
Array
(** args *)- 数组的抽象 Base Class。
创建具体数组类型的推荐方法是将任何ctypes数据类型与正整数相乘。另外,您可以继承此类型并定义length和type类变量。数组元素可以使用标准下标和切片访问进行读写。对于切片读取,结果对象本身不是Array。
_length_
- 一个正整数,指定数组中的元素数。下标超出范围会导致IndexError。将由len()返回。
_type_
- 指定数组中每个元素的类型。
数组子类构造函数接受位置参数,该位置参数用于按 Sequences 初始化元素。
- 类别
ctypes.
_Pointer
- 指针的私有抽象 Base Class。
具体的指针类型是pass使用要指向的类型调用POINTER()来创建的;这是由pointer()自动完成的。
如果指针指向数组,则可以使用标准下标和切片访问来读取和写入其元素。指针对象没有大小,因此len()将引发TypeError。负下标将在指针之前(如 C 中)从内存中读取,并且超出范围的下标可能会因访问冲突而崩溃(如果幸运的话)。
_type_
- 指定指向的类型。
contents
- 返回指针指向的对象。分配给该属性会更改指针以指向分配的对象。