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_int的地方输出了c_long,它们实际上是相同的类型,您不应该感到困惑。

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

请注意,像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不会尝试通过magic选择其中的一个,您必须明确指定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
>>>

然而,有足够的方法使Python与ctypes崩溃,所以你应该小心。faulthandler模块可以帮助您调试崩溃(例如,来自错误的C库调用产生的分割错误)。

None, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls. None is passed as a C NULL pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (char * or 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_ubyteunsigned charINT
c_shortshortINT
c_ushortunsigned shortINT
c_intintINT
c_uintunsigned intINT
c_longlongINT
c_ulongunsigned longINT
c_longlong__int64 INT
c_ulonglong无符号 __ int64无符号 长 t6 > T3>INT
c_size_tsize_tINT
c_ssize_tssize_tPy_ssize_tINT
c_floatfloat浮动
c_doubledouble浮动
c_longdoublelong double浮动
c_char_pchar *(NUL已终止)字节对象或None
c_wchar_pwchar_t *(NUL已终止)字符串或None
c_void_p *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的实例指定一个新值会改变它们指向的内存位置 to,不是内存块的内容(当然不是,因为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. 调用函数,继续

请注意,printf会打印到真正的标准输出通道而不是sys.stdout,因此这些示例只能在控制台提示符下运行,而不能在IDLE < / t4>或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函数,它需要一个字符串指针和一个字符,并返回一个指向字符串的指针:

>>> strchr = libc.strchr
>>> strchr(b"abcdef", ord("d"))  
8059983
>>> strchr.restype = c_char_p    # c_char_p is a pointer to a string
>>> strchr(b"abcdef", ord("d"))
b'def'
>>> print(strchr(b"abcdef", ord("x")))
None
>>>

如果你想避免上面的ord("x")调用,你可以设置argtypes属性,第二个参数将从单个字符Python字节对象成C字符:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
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. 结构体和联合

结构和联合必须继承自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
>>>

但是,您可以构建更复杂的结构。通过使用结构作为字段类型,结构本身可以包含其他结构。

Here is a RECT structure which contains two POINTs named upperleft and lowerright:

>>> 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. 结构体/共用体的对齐方式和字节序

默认情况下,Structure和Union字段的对齐方式与C编译器所做的相同。可以在子类定义中指定_pack_ class属性来覆盖此行为。这必须设置为正整数并指定字段的最大对齐方式。这与在MSVC中#pragma pack(n)功能一样

ctypes使用结构和联合的本地字节顺序。To build structures with non-native byte order, you can use one of the BigEndianStructure, LittleEndianStructure, BigEndianUnion, and LittleEndianUnion base classes. 这些类不能包含指针字段。

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实例会导致指针指向存储它的内存位置:

>>> 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中一样:您可以访问或更改任意内存位置。Generally you only use this feature if you receive a pointer from a C function, and you know that the pointer actually points to an array instead of a single item.

在幕后,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 checks for NULL when dereferencing pointers (but dereferencing invalid non-NULL pointers would crash 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()指定Barvalues字段的结构:

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

因为新的 cell在类语句本身中不可用。ctypes中,我们可以定义cell类,并在稍后的class声明之后设置_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() will be used to sort an array of integers:

>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

qsort() must be called with a pointer to the data to sort, the number of items in the data array, the size of one item, and a pointer to the comparison function, the callback. 这个回调函数将被两个指向项目的指针调用,如果第一个项目小于第二个项目,它必须返回一个负整数,如果它们相等则返回一个零,否则返回一个正整数。

所以我们的回调函数接收指向整数的指针,并且必须返回一个整数。首先我们为回调函数创建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
>>>

注意

只要从C代码中使用它们,确保保持对CFUNCTYPE()对象的引用。ctypes不会,如果你不这样做,它们可能会被垃圾收集,在回调时崩溃你的程序。

另外,请注意,如果在Python控制之外创建的线程中调用回调函数(例如,通过调用回调的外部代码),ctypes在每次调用时创建一个新的虚拟Python线程。This behavior is correct for most purposes, but it means that values stored with threading.local will not survive across different callbacks, even when those calls are made from the same C thread.

16.16.1.18. 对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)
>>>

如果解释器启动带有 -O, 这个例子会打印c_long(1), 或者打印 c_long(2) 如果指定/(带有) -OO

一个扩展的例子也演示了如何使用指针来访问由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. 意外¶ T0>

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

嗯。We certainly expected the last statement to print 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
>>>

Note that temp0 and temp1 are objects still using the internal buffer of the rc object above. So executing rc.a = temp0 copies the buffer contents of temp0 into rc ‘s buffer. 这又反过来改变了temp1的内容。所以,最后一个分配rc.b = temp1,没有预期的效果。

请记住,从Structure,Unions和Arrays中检索子对象不会复制子对象,而是检索访问根对象的基础缓冲区的包装对象。

另一个可能与预期不同的例子是:

>>> s = c_char_p()
>>> s.value = "abc def ghi"
>>> s.value
'abc def ghi'
>>> s.value is s.value
False
>>>

为什么要打印Falsectypes实例是包含一个内存块和一些访问内存内容的descriptor的对象。在存储器块中存储Python对象不会存储对象本身,而是存储对象的contents每次访问内容都会构造一个新的Python对象!

16.16.1.20. 可变大小的数据类型

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. 查找共享库

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

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

On OS X, find_library() tries several predefined naming schemes and paths to locate the library, and returns a full pathname if successful:

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

On Windows, find_library() searches along the system search path, and returns the full pathname, but since there is no predefined naming scheme a call like find_library("c") will fail and return None.

If wrapping a shared library with ctypes, it may be better to determine the shared library name at development time, and hardcode that into the wrapper module instead of using find_library() to locate the library at runtime.

16.16.2.2. 加载共享库

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

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

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

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

仅限Windows:此类的实例表示已加载的共享库,这些库中的函数使用stdcall调用约定,并假定返回特定于窗口的HRESULT代码。HRESULT values contain information specifying whether the function call failed or succeeded, together with additional error code. 如果返回值表示失败,则会自动产生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)

Instances of this class behave like CDLL instances, except that the Python GIL is not released during the function call, and after the function execution the Python error flag is checked. 如果设置了错误标志,则会引发Python异常。

因此,这仅用于直接调用Python C api函数。

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

mode参数可用于指定库的加载方式。有关详细信息,请参阅Windows上的dlopen(3)联机帮助页,mode将被忽略。

use_errno参数设置为True时,将启用一个ctypes机制,以允许以安全的方式访问系统errno错误编号。ctypes maintains a thread-local copy of the systems errno variable; if you call foreign functions created with use_errno=True then the errno value before the function call is swapped with the ctypes private copy, the same happens immediately after the function call.

函数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. 外部函数

如前一节所述,外部函数可以作为加载共享库的属性来访问。默认以这种方式创建的函数对象接受任意数量的参数,接受任何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中,但每个项目必须有一个返回可用作参数(整数,字符串,ctypes实例)的值的from_param()方法。这允许定义可以将自定义对象作为函数参数的适配器。

errcheck

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

callable(result, func, arguments)

结果是外部函数返回的结果,由restype属性指定。

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

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

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

exception ctypes.ArgumentError

当外部函数调用不能转换传递的参数之一时引发此异常。

16.16.2.4. 函数原型

外部函数也可以通过实例化函数原型来创建。函数原型与C中的函数原型相似;他们描述了一个函数(返回类型,参数类型,调用约定),而没有定义实现。必须使用所需的结果类型和函数的参数类型来调用工厂函数。

ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)

返回的函数原型创建使用标准C调用约定的函数。该功能将在通话过程中释放GIL。If use_errno is set to True, the ctypes private copy of the system errno variable is exchanged with the real errno value before and after the call; use_last_error does the same for the Windows error code.

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)

在指定的地址(address)返回外部函数,返回类型为整形

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将继续其在输出参数上执行的正常处理。If you want to return a tuple of window coordinates instead of a RECT instance, you can retrieve the fields in the function and return them instead, the normal processing will no longer take place:

>>> 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类型的实例。偏移量默认为零,并且必须是一个将被添加到内部指针值的整数。

byref(obj, offset)对应于这个C代码:

(((char *)&obj) + offset)

返回的对象只能用作外部函数调用参数。它的行为与pointer(obj)类似,但构造速度更快。

ctypes.cast(obj, type)

该函数与C中的cast操作符类似。它返回一个指向与obj相同的内存块的type新实例。type必须是指针类型,obj必须是可以解释为指针的对象。

ctypes.create_string_buffer(init_or_size, size=None)

这个函数创建一个可变的字符缓冲区。返回的对象是c_char的ctypes数组。

init_or_size must be an integer which specifies the size of the array, or a bytes object which will be used to initialize the array items.

如果一个字节对象被指定为第一个参数,那么缓冲区会比其长度大一个项目,以便数组中的最后一个元素是一个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:返回错误代码代码的文本描述。如果未指定错误代码,则通过调用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)

将调用线程中system errno变量​​的ctypes-private副本的当前值设置为并返回以前的值。

ctypes.set_last_error(value)

仅Windows:将调用线程中系统LastError变量​​的ctypes-private副本的当前值设置为并返回以前的值。

ctypes.sizeof(obj_or_type)

返回ctypes类型或实例内存缓冲区的大小(以字节为单位)。与C sizeof运算符相同。

ctypes.string_at(address, size=-1)

此函数返回从内存地址address开始的C字符串作为字节对象。如果指定了大小,则将其用作大小,否则将假定该字符串为零终止。

ctypes.WinError(code=None, descr=None)

仅Windows:这个函数可能是ctypes中命名最差的东西。它创建了一个OSError实例。如果未指定code,则调用GetLastError来确定错误代码。如果descr没有被指定, FormatError()被调用来获取错误(error)的文本描述。

在版本3.3中更改:曾经创建WindowsError的实例。

ctypes.wstring_at(address, size=-1)

该函数以字符串的形式返回从内存地址地址开始的宽字符串。如果指定了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数据类型都有这个classmethod的默认实现,通常返回obj,如果这是一个类型的实例。有些类型也接受其他对象。

in_dll(library, name)

此方法返回由共享库导出的ctypes类型实例。name是导出数据的符号的名称,library是加载的共享库。

ctypes数据类型的通用实例变量:

_b_base_

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

_b_needsfree_

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

_objects

该成员要么是None,要么是包含需要保持活动状态的Python对象的字典,以便内存块内容保持有效。该对象仅用于调试;永远不要修改这本词典的内容。

16.16.2.7. 基本数据类型

class ctypes._SimpleCData

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

实例具有单个属性:

value

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

当从ctypes实例中检索value属性时,通常每次都会返回一个新对象。ctypes does not implement original object return, always a new object is constructed. 所有其他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 signed long数据类型。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。

class ctypes.c_longlong

表示C 有符号 数据类型。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。

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 无符号 char数据类型,它将该值解释为小整数。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。

class ctypes.c_uint

表示C 无符号 int数据类型。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。sizeof(int) == sizeof(long)的平台上,它是c_ulong

class ctypes.c_uint8

Represents the C 8-bit unsigned int datatype. 通常是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 无符号 long数据类型。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。

class ctypes.c_ulonglong

表示C 无符号 long long数据类型。构造函数接受一个可选的整数初始值设定项;没有溢出检查完成。

class ctypes.c_ushort

表示C 无符号 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)。它的值可以是TrueFalse,并且构造函数接受任何具有真值的对象。

class ctypes.HRESULT

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

class ctypes.py_object

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

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

16.16.2.8. 结构化数据类型

class ctypes.Union(*args, **kw)

本地字节顺序的联合的抽象基类。

class ctypes.BigEndianStructure(*args, **kw)

用于big endian字节顺序的结构的抽象基类。

class ctypes.LittleEndianStructure(*args, **kw)

小端字节顺序的结构的抽象基类。

具有非本地字节顺序的结构不能包含指针类型字段或包含指针类型字段的任何其他数据类型。

class ctypes.Structure(*args, **kw)

native字节顺序的结构的抽象基类。

具体结构和联合类型必须通过继承这些类型之一来创建,并至少定义一个_fields_类变量。ctypes将创建descriptor,允许通过直接属性访问来读写字段。这些是

_fields_

定义结构字段的序列。这些项目必须是2元组或3元组。第一个项目是字段的名称,第二个项目指定字段的类型;它可以是任何ctypes数据类型。

对于像c_int这样的整数类型字段,可以给出第三个可选项。它必须是一个定义字段位宽的小正整数。

一个结构或联合中的字段名称必须是唯一的。这不检查,名称重复时只能访问一个字段。

可以在定义Structure子类的类语句之后定义_fields_类变量,这允许直接或间接引用它们自己的数据类型:

class List(Structure):
    pass
List._fields_ = [("pnext", POINTER(List)),
                 ...
                ]

The _fields_ class variable must, however, be defined before the type is first used (an instance is created, sizeof() is called on it, and so on). 稍后分配给_fields_类变量将引发一个AttributeError。

可以定义结构类型的子子类,它们继承基类的字段加上在子子类中定义的_fields_(如果有的话)。

_pack_

一个可选的小整数,它允许覆盖实例中结构字段的对齐方式。_fields_分配时必须已经定义_pack_,否则它将不起作用。

_anonymous_

列出未命名(匿名)字段名称的可选序列。_anonymous_ must be already defined when _fields_ is assigned, otherwise it will have no effect.

此变量中列出的字段必须是结构或联合类型字段。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 and td.u.lptdesc are equivalent, but the former is faster since it does not need to create a temporary union instance:

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. 数组和指针

class ctypes.Array(*args)

数组的抽象基类。

推荐的创建具体数组类型的方法是将任何ctypes数据类型与正整数相乘。或者,您可以继承此类型并定义_length__type_类变量。数组元素可以使用标准的下标和切片访问来读写;对于片段读取,结果对象本身不是本身就是Array

_length_

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

_type_

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

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

class ctypes._Pointer

指针的私有抽象基类。

具体的指针类型通过调用带有指向类型的POINTER()来创建;这是通过pointer()自动完成的。

如果指针指向数组,则可以使用标准下标和切片访问来读取和写入其元素。指针对象没有大小,所以len()会引发TypeError负下标将从内存读取之前 T0>指针(如C)中,和外的范围的下标将可能与访问冲突崩溃(如果你幸运的话)。

_type_

指定指向的类型。

contents

返回要指向点的对象。分配给该属性会将指针更改为指向指定的对象。