16.16. ctypes
— Python的外部函数库¶
ctypes
是Python的外部函数库。它提供C兼容的数据类型,并允许调用DLL或共享库中的函数。它可以用于在纯Python中包装这些库。
16.16.1. ctypes教程¶
注意:本教程中的代码示例使用 doctest
来确保它们实际工作。由于某些代码示例在Linux,Windows或Mac OS X下的行为不同,因此在注释中包含doctest指令。
注意:一些代码示例引用ctype c_int
类型。在平台上,sizeof(long) == sizeof(int)
是 c_long
的别名。所以,你不应该感到困惑,如果 c_long
打印,如果你会期望 c_int
—他们实际上是相同的类型。
16.16.1.1. 加载动态链接库¶
ctypes
导出 cdll,以及在Windows windll 和 oledll 对象上,用于加载动态链接库。
通过访问它们作为这些对象的属性来加载库。 cdll 加载使用标准 cdecl
调用约定导出函数的库,而 windll 库使用 stdcall
调用约定调用函数。 oledll 还使用 stdcall
调用约定,并假定函数返回一个Windows HRESULT
错误代码。当函数调用失败时,错误代码用于自动提出 OSError
异常。
在 3.3 版更改: 用于提高 WindowsError
的Windows错误,现在是 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上,需要指定文件名 including 作为加载库的扩展,因此属性访问不能用于加载库。应该使用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. 从加载的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 ?
File "ctypes.py", line 239, in __getattr__
func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>
注意,win32系统dll像 kernel32
和 user32
经常导出ANSI和UNICODE版本的函数。 UNICODE版本导出时附加一个 W
到名称,而ANSI版本导出时附加一个 A
到名称。对于给定模块名返回 模块句柄 的win32 GetModuleHandle
函数具有以下C原型,并且根据是否定义了UNICODE,使用宏来将它们中的一个公开为 GetModuleHandle
:
/* 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纪元以来的以秒为单位的系统时间,以及返回win32模块句柄的 GetModuleHandleA()
函数。
此示例使用NULL指针调用两个函数(None
应用作NULL指针):
>>> print(libc.time(None))
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))
0x1d000000
>>>
ctypes
尝试保护您不调用具有错误数量的参数或错误的调用约定的函数。不幸的是,这只适用于Windows。它通过在函数返回后检查堆栈来做到这一点,所以虽然出现了错误,但函数 has 被调用:
>>> 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
>>>
然而,有足够的方法来崩溃Python与 ctypes
,所以你应该小心反正。 faulthandler
模块可以帮助调试崩溃(例如,从错误的C库调用产生的分段故障)。
None
,整数,字节对象和(unicode)字符串是唯一可以直接用作这些函数调用中的参数的本地Python对象。 None
作为C NULL
指针传递,字节对象和字符串作为指针传递到包含其数据的存储器块(char *
或 wchar_t *
)。 Python整数作为平台默认C int
类型传递,它们的值被掩蔽以适合C类型。
在我们开始调用具有其他参数类型的函数之前,我们必须了解有关 ctypes
数据类型的更多信息。
16.16.1.4. 基本数据类型¶
ctypes
定义了许多基本C兼容数据类型:
ctypes类型 |
C型 |
Python类型 |
---|---|---|
|
布尔(1) |
|
|
1个字符的字节对象 |
|
|
1个字符的字符串 |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
int |
|
|
浮动 |
|
|
浮动 |
|
|
浮动 |
|
|
字节对象或 |
|
|
字符串或 |
|
|
int或 |
构造函数接受具有真值的任何对象。
所有这些类型都可以通过使用正确类型和值的可选初始化器调用它们来创建:
>>> 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()
函数(它仍然作为别名可用),以及早期ctypes版本中的 c_string()
函数。要创建包含C类型 wchar_t
的unicode字符的可变内存块,请使用 create_unicode_buffer()
函数。
16.16.1.5. 调用函数,续¶
注意printf打印到真正的标准输出通道,not 到 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. 使用您自己的自定义数据类型调用函数¶
您还可以自定义 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. 指定所需的参数类型(函数原型)¶
可以通过设置 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. 返回类型¶
默认情况下,函数假定返回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
属性,如果外部函数返回一个整数。可调用方将使用 integer 调用C函数返回,并且此调用的结果将用作函数调用的结果。这对于检查错误返回值和自动引发异常非常有用:
>>> 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来获取错误代码的字符串表示,而 returns 是一个异常。 WinError
采用可选的错误代码参数,如果没有使用,它调用 GetLastError()
来检索它。
请注意,通过 errcheck
属性可以获得更强大的错误检查机制;有关详细信息,请参阅参考手册。
16.16.1.9. 传递指针(或:通过引用传递参数)¶
有时C api函数期望 pointer 将数据类型作为参数,可能写入相应的位置,或者数据是否太大而无法通过值传递。这也称为 通过引用传递参数。
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. 结构和联合¶
结构和联合必须来自在 ctypes
模块中定义的 Structure
和 Union
基类。每个子类必须定义一个 _fields_
属性。 _fields_
必须是 2-tuples 的列表,包含 字段名称 和 字段类型。
字段类型必须是 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 可以从 class 检索,它们可用于调试,因为它们可以提供有用的信息:
>>> 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. 结构/union对齐和字节顺序¶
默认情况下,Structure和Union字段的排列方式与C编译器相同。可以通过在子类定义中指定 _pack_
类属性来覆盖此行为。它必须设置为正整数,并指定字段的最大对齐方式。这是 #pragma pack(n)
在MSVC中也做的。
ctypes
对结构和联合使用本地字节顺序。要使用非本地字节顺序构建结构,可以使用 BigEndianStructure
,LittleEndianStructure
,BigEndianUnion
和 LittleEndianUnion
基类中的一个。这些类不能包含指针字段。
16.16.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>
>>>
16.16.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, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>
16.16.1.14. 指针¶
通过在 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函数接收一个指针,而你的 know 的指针实际上指向一个数组,而不是一个单一的项目。
在幕后,pointer()
函数不仅仅是创建指针实例,它必须首先创建指针 types。这是通过 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
>>>
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
>>>
16.16.1.15. 类型转换¶
通常,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. 不完整的类型¶
不完整的类型 是其成员尚未指定的结构,联合或数组。在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
>>>
因为新的 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, end=" ")
... p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>
16.16.1.17. 回调函数¶
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
存储的值将 not 存活在不同的回调,即使这些调用是从相同的C线程。
16.16.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)
,如果已指定 -OO
,则为 c_long(2)
。
一个扩展示例,它也演示了使用指针访问由Python导出的 PyImport_FrozenModules
指针。
引用该值的文档:
这个指针被初始化为指向一个
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有一个冻结模块和一个冻结包(由负大小成员表示)的事实不是众所周知的,它只用于测试。尝试用 import __hello__
例如。
16.16.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检索子对象不会对子对象进行 copy,而是检索访问根对象的底层缓冲区的包装器对象。
另一个可能与所期望的行为不同的例子是这个:
>>> 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. 可变大小的数据类型¶
ctypes
为可变大小的数组和结构提供了一些支持。
resize()
函数可用于调整现有ctypes对象的内存缓冲区大小。该函数将对象作为第一个参数,将请求的大小(以字节为单位)作为第二个参数。内存块不能小于由对象类型指定的自然内存块,如果尝试使用 ValueError
,则会引发 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的动态性质,并且在已知的所需大小之后(根据具体情况)(重新)定义数据类型。
16.16.2. ctypes参考¶
16.16.2.3. 外国功能¶
如前面部分所述,外部函数可以作为加载的共享库的属性来访问。以这种方式创建的函数对象默认接受任意数量的参数,接受任何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. 函数原型¶
外部函数也可以通过实例化函数原型创建。函数原型类似于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调用约定的函数。该功能将在呼叫期间 not 释放GIL。
由这些工厂函数创建的函数原型可以以不同的方式实例化,这取决于调用中的参数的类型和数量:
prototype
(address)返回指定地址处的外部函数,该地址必须是整数。
prototype
(callable)从Python callable 创建一个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 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)
现在可以以这些方式调用 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
继续正常处理它对输出参数。如果你想返回一个tuple的窗口坐标而不是一个 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. 效用函数¶
-
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运算符。它返回一个新的 type 实例,它指向与 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)¶ 尝试查找库并返回路径名。 name 是没有任何前缀(如
lib
)的库名称,后缀如.so
,.dylib
或版本号(这是用于posix链接器选项-l
的形式)。如果没有找到库,则返回None
。确切的功能是系统相关的。
-
ctypes.util.
find_msvcrt
()¶ 仅限Windows:返回Python使用的VC运行时库的文件名,以及扩展模块。如果无法确定库的名称,则返回
None
。如果您需要释放内存,例如,通过扩展模块调用
free(void *)
分配内存,则必须在分配内存的同一库中使用该函数。
-
ctypes.
FormatError
([code])¶ 仅限Windows:返回错误代码 code 的文本描述。如果没有指定错误代码,则通过调用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指针类型。指针类型在内部缓存和重用,因此重复调用此函数很便宜。 type 必须是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
以确定错误代码。如果未指定 descr,则调用FormatError()
以获取错误的文本描述。在 3.3 版更改:
WindowsError
的实例用于创建。
-
ctypes.
wstring_at
(address, size=-1)¶ 此函数返回以字符串形式从内存地址 address 开始的宽字符串。如果指定 size,它将用作字符串的字符数,否则假定该字符串为零终止。
16.16.2.6. 数据类型¶
-
class
ctypes.
_CData
¶ 这个非公共类是所有ctypes数据类型的公共基类。除此之外,所有ctypes类型实例包含保存C兼容数据的存储器块;存储器块的地址由
addressof()
辅助函数返回。另一个实例变量显示为_objects
;这包含其他Python对象,需要保持活动,以防内存块包含指针。ctypes数据类型的常用方法,这些都是类方法(确切地说,它们是 metaclass 的方法):
-
from_buffer
(source[, offset])¶ 此方法返回共享 source 对象的缓冲区的ctypes实例。 source 对象必须支持可写缓冲区接口。可选的 offset 参数指定到源缓冲区的偏移量(以字节为单位);默认为零。如果源缓冲器不够大,则引发
ValueError
。
-
from_buffer_copy
(source[, offset])¶ 此方法创建一个ctypes实例,从必须可读的 source 对象缓冲区复制缓冲区。可选的 offset 参数指定以字节为单位的源缓冲区偏移量;默认为零。如果源缓冲区不够大,则会引发
ValueError
。
-
from_address
(address)¶ 此方法使用由 address 指定的内存返回一个ctypes类型实例,该内存必须是一个整数。
-
from_param
(obj)¶ 此方法将 obj 适配为ctypes类型。当外部函数的
argtypes
元组中存在类型时,使用外部函数调用中使用的实际对象调用它;它必须返回一个可以用作函数调用参数的对象。所有ctypes数据类型都有此类方法的默认实现,通常返回 obj,如果它是类型的实例。一些类型也接受其他对象。
-
in_dll
(library, name)¶ 此方法返回由共享库导出的ctypes类型实例。 name 是导出数据的符号的名称,library 是加载的共享库。
ctypes数据类型的常见实例变量:
-
_b_needsfree_
¶ 当ctypes数据实例分配了内存块本身时,此只读变量为true,否则为false。
-
_objects
¶ 此成员是
None
或包含需要保持活动的Python对象的字典,以便内存块内容保持有效。此对象仅用于调试;从不修改此字典的内容。
-
16.16.2.7. 基本数据类型¶
-
class
ctypes.
_SimpleCData
¶ 这个非公共类是所有基本ctypes数据类型的基类。这里提到它是因为它包含基本ctypes数据类型的公共属性。
_SimpleCData
是_CData
的子类,因此它继承了它们的方法和属性。不包含和不包含指针的ctypes数据类型现在可以进行酸洗。实例具有单一属性:
基本数据类型作为外部函数调用结果返回时,或者例如通过检索结构字段成员或数组项,被透明地转换为本机Python类型。换句话说,如果一个外部函数有一个 c_char_p
的 restype
,你将总是收到一个Python字节对象,not 一个 c_char_p
实例。
基本数据类型的子类 not 继承此行为。所以,如果一个外部函数 restype
是 c_void_p
的一个子类,你将从函数调用中收到这个子类的一个实例。当然,您可以通过访问 value
属性来获取指针的值。
这些是基本的ctypes数据类型:
-
class
ctypes.
c_byte
¶ 表示C
signed 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
signed int
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。在平台上,sizeof(int) == sizeof(long)
是c_long
的别名。
-
class
ctypes.
c_int64
¶ 表示C 64位
signed int
数据类型。通常是c_longlong
的别名。
-
class
ctypes.
c_long
¶ 表示C
signed long
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_longlong
¶ 表示C
signed long long
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_short
¶ 表示C
signed short
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_size_t
¶ 表示C
size_t
数据类型。
-
class
ctypes.
c_ssize_t
¶ 表示C
ssize_t
数据类型。3.2 新版功能.
-
class
ctypes.
c_ubyte
¶ 表示C
unsigned char
数据类型,它将值解释为小整数。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_uint
¶ 表示C
unsigned int
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。在平台上,sizeof(int) == sizeof(long)
是c_ulong
的别名。
-
class
ctypes.
c_uint64
¶ 表示C 64位
unsigned int
数据类型。通常是c_ulonglong
的别名。
-
class
ctypes.
c_ulong
¶ 表示C
unsigned long
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_ulonglong
¶ 表示C
unsigned long long
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
class
ctypes.
c_ushort
¶ 表示C
unsigned short
数据类型。构造函数接受可选的整数初始值;不进行溢出检查。
-
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. 结构化数据类型¶
-
class
ctypes.
Union
(*args, **kw)¶ 以本地字节顺序的联合的抽象基类。
-
class
ctypes.
BigEndianStructure
(*args, **kw)¶ 结构的抽象基类以 大端 字节顺序。
-
class
ctypes.
LittleEndianStructure
(*args, **kw)¶ 结构的抽象基类以 小端 字节顺序。
具有非本地字节顺序的结构不能包含指针类型字段或包含指针类型字段的任何其他数据类型。
-
class
ctypes.
Structure
(*args, **kw)¶ 结构的抽象基类以 native 字节顺序。
具体结构和联合类型必须通过子类化这些类型之一创建,并且至少定义一个
_fields_
类变量。ctypes
将创建允许通过直接属性访问读取和写入字段的 descriptor。这些是-
_fields_
¶ 定义结构字段的序列。项目必须是2元组或3元组。第一个项是字段的名称,第二个项指定字段的类型;它可以是任何ctypes数据类型。
对于像
c_int
这样的整数类型字段,可以给出第三个可选项。它必须是一个小的正整数定义字段的位宽度。字段名称在一个结构或联合中必须是唯一的。这不被选中,只有一个字段可以在重复名称时访问。
可以定义
_fields_
类变量 after 定义Structure子类的类语句,这允许创建直接或间接引用自身的数据类型: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_
中的名称创建新属性。-