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.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
>>>

注意,像kernel32user32这样的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不会试图通过魔法选择其中一个,您必须显式地指定GetModuleHandleAGetModuleHandleW来访问所需的版本,然后调用它分别与字节或字符串对象。

有时,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_charchar1个字符的字节对象
c_wcharwchar_t1个字符的字符串
c_bytecharint
c_ubyte无符号 charint
c_shortshortint
c_ushort无符号 int
c_intintint
c_uint无符号 intint
c_longlongint
c_ulong无符号 int
c_longlong__int64 int
c_ulonglong无符号 __ int64无符号 长 t6 >int
c_size_tsize_tint
c_ssize_tssize_t or Py_ssize_tint
c_floatfloat浮动
c_doubledouble浮动
c_longdoublelong double浮动
c_char_pchar *(NUL terminated)字节对象或None
c_wchar_pwchar_t *(NUL terminated)字符串或None
c_void_pvoid *int或None
  1. 构造函数接受具有真值的任何对象。

所有这些类型都可以通过使用正确类型和值的可选初始化器来调用它们来创建:

>>> 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_pc_wchar_pc_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模块中定义的StructureUnion基类。每个子类必须定义一个_fields_属性。_fields_必须是包含字段名称字段类型2元组的列表。

字段类型必须是ctypes类型,如c_int或任何其他派生的ctypes类型:结构体,共用体,数组,指针

这里有一个POINT结构体的简单例子,它包含两个整数,分别名为xy,同时也展示了如何在构造函数中初始化一个结构体:

>>> 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结构体,它包含两个名为upperleftlowerright的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使用结构和联合的本机字节顺序。要使用非本地字节顺序构建结构,可以使用BigEndianStructureLittleEndianStructureBigEndianUnionLittleEndianUnion这些类不能包含指针字段。

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 of struct _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")
>>>

由于tablepointerstruct_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
>>>

注意,temp0temp1是仍在使用上述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
>>>

为什么要打印Falsectypes实例是包含内存块和一些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.1. Finding shared libraries

当以编译语言编程时,在编译/链接程序时以及程序运行时访问共享库。

find_library()函数的目的是以类似于编译器所做的方式定位库(在具有共享库的多个版本的平台上,应该加载最近的库),而ctypes库加载器就像运行程序时一样,直接调用运行时加载器。

ctypes.util模块提供了一个可以帮助确定要加载的库的函数。

ctypes.util. find_library name

尝试找到一个库并返回一个路径名。name是没有任何前缀的库名称,例如lib,后缀如.so.dylib这是用于posix链接器选项-l如果没有找到库,则返回None

确切的功能是系统相关的。

在Linux上,find_library()尝试运行外部程序(/sbin/ldconfiggccobjdump找到库文件。它返回库文件的文件名。这里有些例子:

>>> from ctypes.util import find_library
>>> find_library("m")
'libm.so.6'
>>> find_library("c")
'libc.so.6'
>>> find_library("bz2")
'libbz2.so.1.0'
>>>

在OS X上,find_library()尝试几种预定义的命名方案和路径以查找库,如果成功则返回完整路径名:

>>> from ctypes.util import find_library
>>> find_library("c")
'/usr/lib/libc.dylib'
>>> find_library("m")
'/usr/lib/libm.dylib'
>>> find_library("bz2")
'/usr/lib/libbz2.dylib'
>>> find_library("AGL")
'/System/Library/Frameworks/AGL.framework/AGL'
>>>

在Windows上,find_library()沿系统搜索路径搜索,并返回完整路径名,但由于没有预定义的命名方案,find_library("c")将失败并返回None

如果用ctypes包装共享库,它可以更好地在开发时确定共享库名称,并硬编码进入包装模块,而不是使用find_library()在运行时查找库。

16.16.2.2. Loading shared libraries

有几种方法可以将共享库加载到Python进程中。一种方法是实例化以下类之一:

class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

此类的实例表示加载的共享库。这些库中的函数使用标准C调用约定,并假定返回int

class ctypes.OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

仅Windows:此类的实例表示加载的共享库,这些库中的函数使用stdcall调用约定,并假定返回特定于HRESULT的窗口代码。HRESULT值包含指定函数调用失败或成功的信息,以及附加的错误代码。如果返回值指示失败,则会自动提高OSError

在版本3.3中更改: WindowsError曾被提及。

class ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)

仅Windows:此类的实例表示加载的共享库,这些库中的函数使用stdcall调用约定,并且默认情况下返回int

在Windows CE上只使用标准调用约定,为方便起见,WinDLLOleDLL在此平台上使用标准调用约定。

Python global interpreter lock在调用这些库导出的任何函数之前释放,然后重新获取。

class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None)

该类的实例表现得像CDLL实例,除了在函数调用期间发布的Python GIL不是,并且在函数执行后检查Python错误标志。如果设置了错误标志,则会引发Python异常。

因此,这只是直接调用Python C api函数有用。

所有这些类都可以通过调用至少一个参数(共享库的路径名)来实例化。如果您已经有一个已经加载的共享库的句柄,它可以作为handle命名参数传递,否则底层平台dlopenLoadLibrary函数用于将库加载到进程中,并获取它的句柄。

mode参数可用于指定如何装载库。有关详细信息,请参阅dlopen(3)联机帮助页,在Windows上,将忽略模式

use_errno参数设置为True时,将启用允许以安全的方式访问系统errno的ctypes机制。ctypes维护系统的线程本地副本errno变量;如果在函数调用与ctypes私有副本交换之前调用使用use_errno=True创建的外部函数,然后调用errno值,则函数调用后会立即发生同样的情况。

函数ctypes.get_errno()返回ctypes私有副本的值,函数ctypes.set_errno()将ctypes私有副本更改为新值,并返回前值。

The use_last_error parameter, when set to True, enables the same mechanism for the Windows error code which is managed by the GetLastError() and SetLastError() Windows API functions; ctypes.get_last_error() and ctypes.set_last_error() are used to request and change the ctypes private copy of the windows error code.

ctypes。 RTLD_GLOBAL

用作模式参数的标志。在这个标志不可用的平台上,它被定义为整数零。

ctypes。 RTLD_LOCAL

用作模式参数的标志。在不可用的平台上,它与RTLD_GLOBAL相同。

ctypes。 DEFAULT_MODE

用于加载共享库的默认模式。在OSX 10.3上,这是RTLD_GLOBAL,否则与RTLD_LOCAL相同。

这些类的实例没有公共方法。共享库导出的函数可以作为属性或索引访问。请注意,通过属性访问函数会缓存结果,因此每次重复访问该函数都会返回相同的对象。另一方面,通过索引访问它每次都会返回一个新对象:

>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

以下公共属性可用,其名称以下划线开头,不与导出的函数名称冲突:

PyDLL._handle

用于访问库的系统句柄。

PyDLL._name

在构造函数中传递的库的名称。

共享库也可以通过使用LibraryLoader类的实例之一的预制对象来加载,或者通过调用LoadLibrary()方法,或者通过检索库作为装载器实例的属性。

class ctypes.LibraryLoader(dlltype)

加载共享库的类。dlltype应为CDLLPyDLLWinDLLOleDLL

__getattr__()具有特殊的行为:它允许通过作为库装载器实例的属性来访问共享库。结果被缓存,因此重复的属性访问每次返回相同的库。

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属性来使用这些函数。

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实例)。这允许定义可以将自定义对象作为函数参数的适配器。

errcheck

分配一个Python函数或另一个可调用此属性。将使用三个或更多参数调用可调用:

callable resultfuncarguments

result是外部函数返回的内容,由restype属性指定。

func是外部函数对象本身,这允许重用相同的可调用对象来检查或后处理几个函数的结果。

arguments是包含最初传递给函数调用的参数的元组,这允许专门化使用的参数的行为。

该函数返回的对象将从外部函数调用返回,但它也可以检查结果值,如果外部函数调用失败,则引发异常。

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_errnouse_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_errno()

返回调用线程中系统errno变量​​的ctypes-private副本的当前值。

ctypes.get_last_error()

仅限Windows:返回调用线程中系统LastError变量​​的ctypes-private副本的当前值。

ctypes.memmove(dst, src, count)

与标准C memmove库函数相同:将count个字节从src复制到dstdstsrc必须是可以转换为指针的整数或ctypes实例。

ctypes.memset(dst, c, count)

与标准C memset库函数相同:使用值ccount字节填充地址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_errno(value)

将调用线程中系统errno变量​​的ctypes-private副本的当前值设置为value并返回上一个值。

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_base_

有时ctypes数据实例不拥有它们包含的内存块,而是它们共享基础对象的内存块的一部分。_b_base_只读成员是拥有内存块的根ctypes对象。

_b_needsfree_

当ctypes数据实例分配了内存块本身时,此只读变量为true,否则为false。

_objects

此成员为None或包含需要保持活动的Python对象的字典,以便内存块内容保持有效。此对象仅用于调试;从不修改此字典的内容。

16.16.2.7. Fundamental data types

class ctypes._SimpleCData

这个非公共类是所有基本ctypes数据类型的基类。这里提到它是因为它包含基本ctypes数据类型的公共属性。_SimpleCData_CData的子类,因此它继承了它们的方法和属性。不包含和不包含指针的ctypes数据类型现在可以进行酸洗。

实例具有单一属性:

value

此属性包含实例的实际值。对于整数和指针类型,它是一个整数,对于字符类型,它是一个单字节字节对象或字符串,对于字符指针类型,它是一个Python字节对象或字符串。

当从ctypes实例检索value属性时,通常每次都返回一个新对象。ctypes 实现原始对象返回,始终构造一个新对象。对于所有其他ctypes对象实例也是如此。

基本数据类型作为外部函数调用结果返回时,或者例如通过检索结构体字段成员或数组项,将被透明地转换为本机Python类型。换句话说,如果外部函数有c_char_prestype,您将总是收到一个Python字节对象,而不是 一个 c_char_p实例。

基本数据类型的子类不会继承此行为。因此,如果外部函数restypec_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_int8

表示C 8位签名的 int数据类型。通常为c_byte的别名。

class ctypes.c_int16

表示C 16位签名的 int数据类型。通常是c_short的别名。

class ctypes.c_int32

表示C 32位签名 int数据类型。通常是c_int的别名。

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_uint8

表示C 8位无符号 int数据类型。通常为c_ubyte的别名。

class ctypes.c_uint16

表示C 16位无符号 int数据类型。通常为c_ushort的别名。

class ctypes.c_uint32

表示C 32位无符号 int数据类型。通常为c_uint的别名。

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)。它的值可以是TrueFalse,构造函数接受任何具有真值的对象。

class ctypes.HRESULT

仅限Windows:表示HRESULT值,其中包含函数或方法调用的成功或错误信息。

class ctypes.py_object

表示C PyObject *数据类型。不带参数调用此函数会创建NULL PyObject *指针。

ctypes.wintypes模块提供了一些其他Windows特定的数据类型,例如HWNDWPARAMDWORD还定义了一些有用的结构,例如MSGRECT

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_(如果有)。

_pack_

一个可选的小整数,允许重写实例中结构体字段的对齐。当分配_fields_时,必须已经定义_pack_,否则它将没有效果。

_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.lptdesctd.u.lptdesc是等效的,但前者更快,因为它不需要创建临时共享体实例:

td = TYPEDESC()
td.vt = VT_PTR
td.lptdesc = POINTER(some_type)
td.u.lptdesc = POINTER(some_type)

可以定义结构的子子类,它们继承基类的字段。如果子类定义具有单独的_fields_变量​​,则此类中指定的字段将附加到基类的字段。

结构体和共用体构造函数接受位置和关键字参数。位置参数用于以与在_fields_中出现的顺序相同的顺序初始化成员字段。构造函数中的关键字参数被解释为属性赋值,因此它们将以相同的名称初始化_fields_,或者为_fields_中不存在的名称创建新的属性。

16.16.2.9. Arrays and pointers

class ctypes.Array(*args)

数组的抽象基类。

创建具体数组类型的推荐方法是将任何ctypes数据类型乘以一个正整数。或者,您可以对此类型进行子类化,并定义_length__type_类变量。可以使用标准下标和片访问来读取和写入数组元素;对于片段读取,生成的对象不是本身为Array

_length_

指定数组中元素数的正整数。超出范围下标导致IndexError将由len()返回。

_type_

指定数组中每个元素的类型。

数组子类构造函数接受位置参数,用于按顺序初始化元素。

class ctypes._Pointer

指针的私有,抽象基类。

通过调用POINTER()创建具体指针类型,其类型将被指向;这由pointer()自动完成。

如果指针指向一个数组,其元素可以使用标准下标和片段访问读写。指针对象没有大小,因此len()将引发TypeError负下标将从之前的存储器读取指针(如C),超出范围的下标可能会与访问冲突(如果你幸运的话)崩溃。

_type_

指定指向的类型。

contents

返回指针指向的对象。分配给此属性会将指针更改为指向分配的对象。