16.16. ctypes
- Python的外部函数库¶
ctypes
是Python的外部函数库。它提供C兼容的数据类型,并允许调用DLL或共享库中的函数。它可以用于在纯Python中包装这些库。
16.16.1. ctypes 教程 ¶
注意:本教程中的代码示例使用doctest
确保它们实际工作。由于一些代码示例在Linux,Windows或Mac OS X下的行为不同,因此在注释中包含doctest指令。
注意:一些代码示例引用ctypes c_int
类型。在sizeof(long) == sizeof(int)
的平台上,它是c_long
。因此,如果打印c_long
,如果您希望c_int
,它们实际上是相同的类型,那么您不应该感到困惑。
16.16.1.1. 加载动态链接库¶
ctypes
exports the cdll, and on Windows windll and oledll objects, for loading dynamic link libraries.
你可以通过访问这些对象的属性来加载库。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
文件后缀。
注意
通过cdll.msvcrt
访问标准C库将使用库的过期版本,这可能与Python使用的版本不兼容。在可能的情况下,使用本机Python功能,或者导入并使用msvcrt
模块。
在Linux上,需要指定文件名(包括)以加载库,因此属性访问不能用于加载库。应该使用dll加载器的LoadLibrary()
方法,或者通过调用构造函数创建CDLL的实例来加载库:
>>> cdll.LoadLibrary("libc.so.6")
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")
>>> libc
<CDLL 'libc.so.6', handle ... at ...>
>>>
16.16.1.2. Accessing functions from loaded dlls¶
函数作为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 ?
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原型,并且使用宏来将其中之一暴露为GetModuleHandle
取决于是否定义UNICODE:
/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
windll不会试图通过魔法选择其中一个,您必须显式地指定GetModuleHandleA
或GetModuleHandleW
来访问所需的版本,然后调用它分别与字节或字符串对象。
有时,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 ?
File "ctypes.py", line 310, in __getitem__
func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>
16.16.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 ?
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 ?
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>
当您使用cdecl
调用约定调用stdcall
函数时,也会出现相同的异常,反之亦然:
>>> cdll.kernel32.GetModuleHandleA(None)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
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 ?
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 ?
OSError: exception: access violation reading 0x00000020
>>>
然而,有足够的方法来使用ctypes
来崩溃Python,所以你应该小心。faulthandler
模块有助于调试崩溃(例如,从错误的C库调用产生的分割错误)。
None
,整数,字节对象和(unicode)字符串是唯一可以直接用作这些函数调用中的参数的本机Python对象。None
作为C NULL
指针传递,字节对象和字符串作为指针传递到包含数据的内存块(char t5 > *
或wchar_t *
)。Python整数作为平台默认的C int
类型传递,它们的值被掩蔽以适合C类型。
在我们开始调用其他参数类型的函数之前,我们必须了解有关ctypes
数据类型的更多信息。
16.16.1.4. 基本数据类型¶
ctypes
定义了许多基本C兼容数据类型:
ctypes类型 | C型 | Python类型 |
---|---|---|
c_bool | _Bool | 布尔(1) |
c_char | char | 1个字符的字节对象 |
c_wchar | wchar_t | 1个字符的字符串 |
c_byte | char | int |
c_ubyte | 无符号 char | int |
c_short | short | int |
c_ushort | 无符号 短 | int |
c_int | int | int |
c_uint | 无符号 int | int |
c_long | long | int |
c_ulong | 无符号 长 | int |
c_longlong | __int64 或长 长 | int |
c_ulonglong | 无符号 __ int64 或无符号 长 长 t6 > | int |
c_size_t | size_t | int |
c_ssize_t | ssize_t or Py_ssize_t | int |
c_float | float | 浮动 |
c_double | double | 浮动 |
c_longdouble | long double | 浮动 |
c_char_p | char * (NUL terminated) | 字节对象或None |
c_wchar_p | wchar_t * (NUL terminated) | 字符串或None |
c_void_p | void * | int或None |
- 构造函数接受具有真值的任何对象。
所有这些类型都可以通过使用正确类型和值的可选初始化器来调用它们来创建:
>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_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_wchar_p(s)
>>> print(c_s)
c_wchar_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print(c_s)
c_wchar_p('Hi, there')
>>> print(s) # first object 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 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()
函数(它仍然可用作别名)以及c_string()(它用于早期的ctypes发行版)
。要创建包含C类型wchar_t
的unicode字符的可变内存块,请使用create_unicode_buffer()
函数。
16.16.1.5. Calling functions, continued¶
注意,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 ?
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
>>>
16.16.1.6. Calling functions with your own custom data types¶
您还可以自定义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
,该属性使请求中的属性可用。
16.16.1.7. Specifying the required argument types (function prototypes)¶
可以通过设置argtypes
属性来指定从DLL导出的函数所需的参数类型。
argtypes
必须是C数据类型的序列(printf
函数在这里可能不是一个好的例子,因为它需要一个可变数量和不同类型的参数,具体取决于格式字符串,另一方面这是相当方便的实验这个功能):
>>> 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函数的原型),并尝试将参数转换为有效的类型:
>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
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对象,它应该执行typecheck或任何需要确保此对象是可被接受的工作,然后返回对象本身,其_as_parameter_
属性,或者在这种情况下你想要作为C函数参数传递的任何东西。同样,结果应为整数,字符串,字节,ctypes
实例或具有_as_parameter_
属性的对象。
16.16.1.8. Return types¶
默认情况下,函数假定返回C int
类型。可以通过设置函数对象的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 char:
>>> 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 ?
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函数调用结束后会使用其返回的整数作为参数调用这个Py可调用对象,而其返回值作为函数调用的返回值.这有助于检查错误返回值并自动引发异常:
>>> 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 ?
File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>
WinError
是一个函数,它将调用Windows FormatMessage()
api以获取错误代码的字符串表示形式,并且返回异常。WinError
采用可选的错误代码参数,如果没有使用,则调用GetLastError()
来检索它。
请注意,通过errcheck
属性可以获得更强大的错误检查机制;有关详细信息,请参阅参考手册。
16.16.1.9. 传递指针或通过引用传递参数¶
有时,一个C api函数期望一个指针作为参数的数据类型,可能想要写入相应的位置,或者因数据太大而无法通过值传递。这也称为通过引用传递参数。
ctypes
导出byref()
函数,用于通过引用传递参数。使用pointer()
函数可以实现相同的效果,虽然pointer()
做了很多工作,因为它构造了一个真正的指针对象,因此如果你不需要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'
>>>
16.16.1.10. Structures and unions¶
结构和联合必须继承自ctypes
模块中定义的Structure
和Union
基类。每个子类必须定义一个_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 ?
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))
可以从类检索descriptor,它们可用于调试,因为它们可以提供有用的信息:
>>> 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上工作,但它不能保证库在一般情况下工作。应该始终以指针的形式将带位域的共用体和结构体传递给函数。
16.16.1.11. 结构体/共用体的对齐方式和字节序 ¶
默认情况下,结构体和共用体字段以与C编译器相同的方式对齐。可以通过在子类定义中指定_pack_
类属性来覆盖此行为。它必须设置为正整数,并指定字段的最大对齐方式。这与在MSVC中#pragma pack(n)
功能一样
ctypes
使用结构和联合的本机字节顺序。要使用非本地字节顺序构建结构,可以使用BigEndianStructure
,LittleEndianStructure
,BigEndianUnion
和LittleEndianUnion
这些类不能包含指针字段。
16.16.1.12. Bit fields in structures and unions¶
可以创建包含位字段的结构和联合。位字段仅可用于整数字段,位宽指定为_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>
>>>
16.16.1.13. Arrays¶
数组是序列,包含固定数量的相同类型的实例。
创建数组类型的推荐方法是将数据类型与正整数相乘:
TenPointsArrayType = POINT * 10
这里有一个有点人工的数据类型的例子,结构体包含4个POINTs其他东西:
>>> 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, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>
16.16.1.14. Pointers¶
通过调用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函数接收指针,并且知道指针实际指向数组而不是单个项目,则只使用此功能。
在幕后,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 ?
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
>>>
当解除引用指针(但解除引用无效的非NULL
指针会导致Python崩溃)时,ctypes
会检查NULL
:
>>> 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
>>>
16.16.1.15. Type conversions¶
通常,ctypes执行严格的类型检查。这意味着,如果在函数的argtypes
列表中有POINTER(c_int)
,或者在结构体定义中作为成员字段的类型,则实参只能接受相同类型的实例。此规则有一些例外,其中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 ?
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
>>>
16.16.1.16. 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 ?
File "<stdin>", line 2, in cell
NameError: name 'cell' is not defined
>>>
因为新的类 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, end=" ")
... p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>
16.16.1.17. 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)
>>>
结果:
>>> 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代码使用的。ctypes
不会,如果不这样做,它们可能会被垃圾回收,在回调时会导致程序崩溃。
另外,请注意,如果在Python控制之外创建的线程中调用回调函数(例如,由调用回调的外部代码),ctypes在每次调用时创建一个新的伪Python线程。这种行为对于大多数目的是正确的,但是它意味着存储在threading.local
的值将不在不同的回调中存活,即使这些调用是从同一个C线程。
16.16.1.18. Accessing values exported from dlls¶
一些共享库不仅导出函数,它们也导出变量。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)
>>>
If the interpreter would have been started with -O
, the sample would have printed c_long(1)
, or c_long(2)
if -OO
would have been specified.
一个扩展示例也演示了使用指针访问由Python导出的PyImport_FrozenModules
指针。
引用该值的文档:
This pointer is initialized to point to an array ofstruct _frozen
records, terminated by one whose members are all NULL or zero. When a frozen module is imported, it is searched in this table. 第三方代码可以利用此技巧提供一个动态创建的静态模块集合.
所以操纵这个指针甚至可以证明是有用的。为了限制示例大小,我们仅显示如何使用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:
... 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有一个冻结模块和一个冻结包(由负大小成员表示)的事实不是众所周知的,它只用于测试。请尝试使用import __ hello __
。
16.16.1.19. 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
。发生了什么?Here are the steps of the rc.a, rc.b = rc.b, rc.a
line above:
>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>
注意,temp0
和temp1
是仍在使用上述rc
对象的内部缓冲区的对象。So executing rc.a = temp0
copies the buffer contents of temp0
into rc
‘s buffer. 这反过来改变temp1
的内容。因此,最后一个分配rc.b = temp1
没有预期的效果。
请记住,从结构体,联合和数组检索子对象不会复制子对象,而是检索访问根对象的底层缓冲区的包装器对象。
另一个可能与预期不同的例子是:
>>> s = c_char_p()
>>> s.value = "abc def ghi"
>>> s.value
'abc def ghi'
>>> s.value is s.value
False
>>>
为什么要打印False
?ctypes实例是包含内存块和一些descriptor访问内存内容的对象。在内存块中存储Python对象不会存储对象本身,而是存储对象的contents
。再次访问内容每次构造一个新的Python对象!
16.16.1.20. Variable-sized data types¶
ctypes
为可变大小的数组和结构提供了一些支持。
resize()
函数可用于调整现有ctypes对象的内存缓冲区大小。该函数将对象作为第一个参数,将请求的大小(以字节为单位)作为第二个参数。内存块不能小于由对象类型指定的自然内存块,如果尝试这种情况,则会引发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
>>>
这是好和罚款,但如何访问该数组中包含的其他元素?由于type仍然只知道大约4个元素,我们得到访问其他元素的错误:
>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
...
IndexError: invalid index
>>>
使用ctypes
来使用可变大小数据类型的另一种方法是使用Python的动态性质,并且在已知的所需大小之后(根据具体情况)(重新)定义数据类型。
16.16.2. ctypes参考¶
16.16.2.3. Foreign functions¶
如上一节所述,外部函数可以作为加载的共享库的属性来访问。以这种方式创建的函数对象默认接受任意数量的参数,接受任何ctypes数据实例作为参数,并返回由库加载器指定的默认结果类型。它们是私有类的实例:
- class
ctypes.
_FuncPtr
¶ C可调外部函数的基类。
外部函数的实例也是C兼容的数据类型;它们表示C函数指针。
这种行为可以通过分配外部函数对象的特殊属性来定制。
-
restype
¶ 分配ctypes类型以指定外部函数的结果类型。对
void
使用None
,此函数不返回任何内容。可以分配一个不是ctypes类型的可调用的Python对象,在这种情况下,函数被假定为返回一个C
int
,并且可调用将被调用这个整数,允许进一步处理或错误检查。为了更灵活的后处理或错误检查,使用ctypes数据类型作为restype
,并为errcheck
属性指定一个可调用对象,因此不推荐使用。
-
argtypes
¶ 分配ctypes类型的元组以指定函数接受的参数类型。使用
stdcall
调用约定的函数只能使用与该元组的长度相同数量的参数进行调用;使用C调用约定的函数也接受附加的、未指定的参数。当调用外部函数时,每个实际参数被传递到
argtypes
元组中项目的from_param()
类方法,此方法允许将实际参数适配外部函数接受的对象。例如,argtypes
元组中的c_char_p
项目将使用ctypes转换规则将作为参数传递的字符串转换为字节对象。新:现在可以将项目放在不是ctypes类型的argtypes中,但每个项目必须有一个
from_param()
方法,该方法返回一个可用作参数的值(integer,string,ctypes实例)。这允许定义可以将自定义对象作为函数参数的适配器。
-
- exception
ctypes.
ArgumentError
¶ 当外部函数调用无法转换所传递的参数之一时,会引发此异常。
16.16.2.4. Function prototypes¶
外部函数也可以通过实例化函数原型创建。函数原型与C中的函数原型相似;它们描述一个函数(返回类型,参数类型,调用约定),而不定义实现。必须使用所需的结果类型和函数的参数类型调用工厂函数。
-
ctypes.
CFUNCTYPE
(restype, *argtypes, use_errno=False, use_last_error=False)¶ 返回的函数原型创建使用标准C调用约定的函数。该功能将在通话期间释放GIL。如果use_errno设置为True,系统
errno
变量的ctypes私有副本与调用前后的真实errno
use_last_error对Windows错误代码执行相同操作。
-
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
(address)Returns a foreign function at the specified address which must be an integer.
prototype
(callable)Create a C callable function (a callback function) from a Python callable.
prototype
(func_spec[, paramflags])Returns a foreign function exported by a shared library. func_spec must be a 2-tuple
(name_or_ordinal, library)
. The first item is the name of the exported function as string, or the ordinal of the exported function as small integer. The second item is the shared library instance.
prototype
(vtbl_index, name[, paramflags[, iid]])Returns a foreign function that will call a COM method. vtbl_index is the index into the virtual function table, a small non-negative integer. name is name of the COM method. iid is an optional pointer to the interface identifier which is used in extended error reporting.
COM methods use a special calling convention: They require a pointer to the COM interface as first argument, in addition to those parameters that are specified in the
argtypes
tuple.The optional paramflags parameter creates foreign function wrappers with much more functionality than the features described above.
paramflags must be a tuple of the same length as
argtypes
.Each item in this tuple contains further information about a parameter, it must be a tuple containing one, two, or three items.
The first item is an integer containing a combination of direction flags for the parameter:
- 1
- Specifies an input parameter to the function.
- 2
- Output parameter. The foreign function fills in a value.
- 4
- Input parameter which defaults to the integer zero.
The optional second item is the parameter name as string. If this is specified, the foreign function can be called with named parameters.
The optional third item is the default value for this parameter.
此示例演示如何包装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
函数返回参数元组,它接收不变,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
>>>
16.16.2.5. Utility functions¶
-
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)
,但是构造要快得多。
-
ctypes.
cast
(obj, type)¶ 这个函数类似于C中的cast操作符号。它返回一个类型的新实例,它指向与obj相同的内存块。type必须是指针类型,obj必须是可以解释为指针的对象。
-
ctypes.
create_string_buffer
(init_or_size, size=None)¶ 此函数创建一个可变字符缓冲区。返回的对象是
c_char
的ctypes数组。init_or_size必须是一个指定数组大小的整数,或者一个用于初始化数组项的字节对象。
如果一个字节对象被指定为第一个参数,则缓冲区被设置为大于其长度的一个项目,使得数组中的最后一个元素是一个NUL终止字符。整数可以作为第二个参数传递,这允许指定数组的大小,如果不应该使用字节的长度。
-
ctypes.
create_unicode_buffer
(init_or_size, size=None)¶ 此函数创建一个可变的unicode字符缓冲区。返回的对象是
c_wchar
的ctypes数组。init_or_size必须是指定数组大小的整数,或者用于初始化数组项的字符串。
如果一个字符串被指定为第一个参数,则缓冲区被设置为大于字符串长度的一个项目,以便数组中的最后一个元素是一个NUL终止字符。整数可以作为第二个参数传递,如果不使用字符串的长度,则允许指定数组的大小。
-
ctypes.
DllCanUnloadNow
()¶ 仅限Windows:此函数是一个允许使用ctypes实现进程内COM服务器的钩子。它从DllCanUnloadNow函数调用_ctypes扩展dll导出。
-
ctypes.
DllGetClassObject
()¶ 仅限Windows:此函数是一个允许使用ctypes实现进程内COM服务器的钩子。它从DllGetClassObject函数调用,
_ctypes
扩展dll导出。
-
ctypes.util.
find_library
(name)¶ 尝试找到一个库并返回一个路径名。名称是没有任何前缀的库名称,例如
lib
,后缀为.so
,.dylib
这是用于posix链接器选项-l
如果没有找到库,则返回None
。确切的功能是系统相关的。
-
ctypes.util.
find_msvcrt
()¶ 仅限Windows:返回由Python使用的VC运行时库的文件名,以及扩展模块。如果无法确定库的名称,则返回
None
。如果您需要释放内存,例如,通过调用
free(void *)
的扩展模块分配内存,你在同一个库中使用分配内存的函数。
-
ctypes.
FormatError
([code])¶ 仅限Windows:返回错误代码代码的文本描述。如果没有指定错误代码,则通过调用Windows api函数GetLastError来使用最后一个错误代码。
-
ctypes.
GetLastError
()¶ 仅限Windows:返回在调用线程中由Windows设置的最后一个错误代码。此函数直接调用Windows GetLastError()函数,它不返回错误代码的ctypes-private副本。
-
ctypes.
get_last_error
()¶ 仅限Windows:返回调用线程中系统
LastError
变量的ctypes-private副本的当前值。
-
ctypes.
memmove
(dst, src, count)¶ 与标准C memmove库函数相同:将count个字节从src复制到dst。dst和src必须是可以转换为指针的整数或ctypes实例。
-
ctypes.
memset
(dst, c, count)¶ 与标准C memset库函数相同:使用值c的count字节填充地址dst处的存储器块。 dst必须是指定地址的整数,或ctypes实例。
-
ctypes.
POINTER
(type)¶ 这个工厂函数创建并返回一个新的ctypes指针类型。指针类型在内部缓存和重用,因此重复调用此函数很便宜。类型必须是ctypes类型。
-
ctypes.
pointer
(obj)¶ 此函数创建一个新指针实例,指向obj。返回的对象类型为
POINTER(type(obj))
。注意:如果你只想传递一个指向外部函数调用的对象,你应该使用
byref(obj)
。
-
ctypes.
resize
(obj, size)¶ 此函数调整obj的内部存储器缓冲区,它必须是ctypes类型的实例。不可能使缓冲区小于对象类型的本机大小,如
sizeof(type(obj))
给出的,但是可以放大缓冲区。
-
ctypes.
set_last_error
(value)¶ 仅限Windows:将调用线程中系统
LastError
变量的ctypes-private副本的当前值设置为value,并返回上一个值。
-
ctypes.
sizeof
(obj_or_type)¶ 返回ctypes类型或实例内存缓冲区的大小(以字节为单位)。与C
sizeof
操作符相同。
-
ctypes.
string_at
(address, size=-1)¶ 此函数返回从内存地址address开始的C字符串作为字节对象。如果指定size,则将其用作大小,否则字符串假定为零终止。
-
ctypes.
WinError
(code=None, descr=None)¶ 仅Windows:这个函数可能是ctypes中最糟糕的事情。它创建一个OSError的实例。如果未指定code,则调用
GetLastError
来确定错误代码。If descr is not specified,FormatError()
is called to get a textual description of the error.在版本3.3中更改:用于创建
WindowsError
的实例。
-
ctypes.
wstring_at
(address, size=-1)¶ 此函数返回以字符串形式从内存地址地址开始的宽字符串。如果指定size,则将其用作字符串的字符数,否则假定字符串为零终止。
16.16.2.6. Data types¶
- class
ctypes.
_CData
¶ 这个非公共类是所有ctypes数据类型的公共基类。除此之外,所有ctypes类型实例都包含保存C兼容数据的存储器块;存储器块的地址由
addressof()
帮助函数返回。另一个实例变量显示为_objects
;这包含其他Python对象需要保持活动,以防内存块包含指针。ctypes数据类型的常用方法,这些都是类方法(准确地说,它们是metaclass):
-
from_buffer
(source[, offset])¶ 此方法返回共享源对象的缓冲区的ctypes实例。源对象必须支持可写缓冲区接口。可选的offset参数指定以字节为单位的源缓冲区偏移量;默认为零。如果源缓冲区不够大,则会引发
ValueError
。
-
from_buffer_copy
(source[, offset])¶ 此方法创建一个ctypes实例,从必须可读的源对象缓冲区复制缓冲区。可选的offset参数指定以字节为单位的源缓冲区偏移量;默认为零。如果源缓冲区不够大,则会引发
ValueError
。
-
from_address
(address)¶ 此方法使用由address指定的内存返回ctypes类型实例,该内存必须是整数。
-
from_param
(obj)¶ 此方法将obj修改为ctypes类型。当外部函数的
argtypes
元组中存在类型时,使用外部函数调用中使用的实际对象来调用它;它必须返回一个可以用作函数调用参数的对象。所有ctypes数据类型都具有此类方法的默认实现,如果这是类型的实例,则通常返回obj。一些类型也接受其他对象。
-
in_dll
(library, name)¶ 此方法返回由共享库导出的ctypes类型实例。name是导出数据的符号的名称,库是加载的共享库。
ctypes数据类型的常用实例变量:
-
_b_needsfree_
¶ 当ctypes数据实例分配了内存块本身时,此只读变量为true,否则为false。
-
_objects
¶ 此成员为
None
或包含需要保持活动的Python对象的字典,以便内存块内容保持有效。此对象仅用于调试;从不修改此字典的内容。
-
16.16.2.7. Fundamental data types¶
- class
ctypes.
_SimpleCData
¶ 这个非公共类是所有基本ctypes数据类型的基类。这里提到它是因为它包含基本ctypes数据类型的公共属性。
_SimpleCData
是_CData
的子类,因此它继承了它们的方法和属性。不包含和不包含指针的ctypes数据类型现在可以进行酸洗。实例具有单一属性:
基本数据类型作为外部函数调用结果返回时,或者例如通过检索结构体字段成员或数组项,将被透明地转换为本机Python类型。换句话说,如果外部函数有c_char_p
的restype
,您将总是收到一个Python字节对象,而不是 一个 c_char_p
实例。
基本数据类型的子类不会继承此行为。因此,如果外部函数restype
是c_void_p
的子类,您将从函数调用中收到此子类的实例。当然,您可以通过访问value
属性来获取指针的值。
这些是基本的ctypes数据类型:
- class
ctypes.
c_byte
¶ 表示C
有符号 char
数据类型,并将该值解释为小整数。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_char
¶ 表示C
char
数据类型,并将该值解释为单个字符。构造函数接受一个可选的字符串初始化器,字符串的长度必须是一个字符。
- class
ctypes.
c_char_p
¶ 表示指向零终止字符串时的C
char *
数据类型。对于也可能指向二进制数据的通用字符指针,必须使用POINTER(c_char)
。构造函数接受一个整数地址或一个字节对象。
- class
ctypes.
c_double
¶ 表示C
double
数据类型。构造函数接受可选的浮点初始值。
- class
ctypes.
c_longdouble
¶ 表示C
long double
数据类型。构造函数接受可选的浮点初始值。在sizeof(long double) == sizeof(double) 是
c_double
的别名。
- class
ctypes.
c_float
¶ 表示C
float
数据类型。构造函数接受可选的浮点初始值。
- class
ctypes.
c_int
¶ 表示C
有符号的 int
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。在sizeof(int) == sizeof(long)
的平台上,它是c_long
。
- class
ctypes.
c_int64
¶ 表示C 64位
签名 int
数据类型。通常为c_longlong
的别名。
- class
ctypes.
c_long
¶ 表示C
签名 long
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_longlong
¶ 表示C
签署的 长 长
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_short
¶ 表示C
签名的 short
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_size_t
¶ 表示C
size_t
数据类型。
- class
ctypes.
c_ssize_t
¶ 表示C
ssize_t
数据类型。版本3.2中的新功能。
- class
ctypes.
c_ubyte
¶ 表示C
无符号 char
数据类型,它将该值解释为小整数。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_uint
¶ 表示C
无符号 int
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。在sizeof(int) == sizeof(long)
的平台上,它是c_ulong
。
- class
ctypes.
c_uint64
¶ 表示C 64位
无符号 int
数据类型。通常为c_ulonglong
的别名。
- class
ctypes.
c_ulong
¶ 表示C
无符号 长
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_ulonglong
¶ 表示C
无符号 长 长
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_ushort
¶ 表示C
无符号 短
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
- class
ctypes.
c_void_p
¶ 表示C
void *
类型。该值表示为整数。构造函数接受可选的整数初始值。
- class
ctypes.
c_wchar
¶ 表示C
wchar_t
数据类型,并将该值解释为单个字符unicode字符串。构造函数接受一个可选的字符串初始化器,字符串的长度必须是一个字符。
- class
ctypes.
c_wchar_p
¶ 表示C
wchar_t *
数据类型,它必须是指向零终止宽字符串的指针。构造函数接受整数地址或字符串。
- class
ctypes.
c_bool
¶ 表示C
bool
数据类型(更准确地说,来自C99的_Bool
)。它的值可以是True
或False
,构造函数接受任何具有真值的对象。
- class
ctypes.
HRESULT
¶ 仅限Windows:表示
HRESULT
值,其中包含函数或方法调用的成功或错误信息。
- class
ctypes.
py_object
¶ 表示C
PyObject *
数据类型。不带参数调用此函数会创建NULL
PyObject *
指针。
ctypes.wintypes
模块提供了一些其他Windows特定的数据类型,例如HWND
,WPARAM
或DWORD
。还定义了一些有用的结构,例如MSG
或RECT
。
16.16.2.8. Structured data types¶
- class
ctypes.
Union
(*args, **kw)¶ 以本地字节顺序的联合的抽象基类。
- class
ctypes.
BigEndianStructure
(*args, **kw)¶ 大端字节字节顺序中结构的抽象基类。
- class
ctypes.
LittleEndianStructure
(*args, **kw)¶ 小端字节字节顺序中结构的抽象基类。
具有非本地字节顺序的结构不能包含指针类型字段或包含指针类型字段的任何其他数据类型。
- class
ctypes.
Structure
(*args, **kw)¶ 本机字节顺序中结构的抽象基类。
具体的结构体和共用体类型必须通过子类化这些类型之一创建,并且至少定义一个
_fields_
类变量。ctypes
将创建descriptor,允许通过直接属性访问读取和写入字段。这些是-
_fields_
¶ 定义结构体字段的序列。项目必须是2元组或3元组。第一项是字段的名称,第二项指定字段的类型;它可以是任何ctypes数据类型。
对于整数类型字段,如
c_int
,可以给出第三个可选项。它必须是一个小的正整数定义字段的位宽度。字段名称在一个结构体或共用体中必须是唯一的。这不检查,名称重复时只能访问一个字段。
可以在定义结构体子类的类语句之后定义 t>之后的
_fields_
类变量,这允许创建直接或间接引用自身的数据类型:class List(Structure): pass List._fields_ = [("pnext", POINTER(List)), ... ]
然而,
_fields_
类变量必须在第一次使用类型之前定义(创建实例,调用sizeof()
,等等)。稍后对_fields_
类变量的赋值将引发AttributeError。可以定义结构体类型的子类,它们继承基类的字段加上子类中定义的
_fields_
(如果有)。
-
_anonymous_
¶ 列出未命名(匿名)字段的名称的可选序列。当分配
_fields_
时,必须已定义_anonymous_
,否则它将不起作用。此变量中列出的字段必须是结构体或共用体类型字段。
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_
中不存在的名称创建新的属性。-