ctypes

2.5版本中的新功能。

ctypes是一个用于Python的外部函数库。它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。它可以用来将这些库封装在纯Python中。

1. ctypes教程

注意:本教程中的代码示例doctest用于确保它们实际工作。由于一些代码示例在Linux,Windows或Mac OS X下表现不同,因此它们在注释中包含doctest指令。

注意:一些代码示例引用了ctypes c_int类型。这种类型是c_long32位系统上的类型的别名。所以,c_long如果您希望印刷的话,您不应该感到困惑c_int- 它们实际上是同一种类型。

1.1. 加载动态链接库

ctypes导出cdll以及Windows windlloledll对象,以加载动态链接库。

通过将它们作为这些对象的属性进行访问来加载库。cdll加载使用标准cdecl调用约定导出函数的库,而windll库使用stdcall调用约定调用函数。oledll也使用stdcall调用约定,并假定函数返回一个Windows HRESULT错误代码。错误代码用于WindowsError在函数调用失败时自动引发异常。

以下是Windows的一些示例。请注意,msvcrtMS标准C库包含大多数标准C函数,并使用cdecl调用约定:

>>> from ctypes import *
>>> print windll.kernel32  
<WinDLL 'kernel32', handle ... at ...>
>>> print cdll.msvcrt      
<CDLL 'msvcrt', handle ... at ...>
>>> libc = cdll.msvcrt     
>>>

Windows会.dll自动附加通常的文件后缀。

在Linux上,需要指定包含加载库的扩展名的文件名,因此不能使用属性访问来加载库。要么使用LoadLibrary()dll加载器的方法,要么通过调用构造函数创建CDLL实例来加载库:

>>> cdll.LoadLibrary("libc.so.6")  
<CDLL 'libc.so.6', handle ... at ...>
>>> libc = CDLL("libc.so.6")       
>>> libc                           
<CDLL 'libc.so.6', handle ... at ...>
>>>

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喜欢kernel32user32经常导出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明确地访问你需要的版本,然后分别用字符串或unicode字符串来调用它。

有时,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
>>>

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

stdcall使用cdecl调用约定调用函数时会引发同样的异常,反之亦然:

>>> 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("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 ?
WindowsError: exception: access violation reading 0x00000020
>>>

然而,有足够的方法来使Python崩溃ctypes,所以你应该小心。

None,整数,长整数,字节字符串和unicode字符串是唯一可以在这些函数调用中直接用作参数的本机Python对象。None作为C NULL指针传递,字节字符串和unicode字符串作为指针传递给包含其数据(char *wchar_t *)的内存块。Python整数和Python longs作为平台的默认C int类型传递,它们的值被掩盖以适应C类型。

在继续使用其他参数类型调用函数之前,我们必须了解更多关于ctypes数据类型的信息。

1.4. 基本数据类型

ctypes 定义了许多基本的C兼容数据类型:

ctypes类型

C类型

Python类型

c_bool

_Bool

bool (1)

c_char

char

1-character string

c_wchar

wchar_t

1-character unicode string

c_byte

char

int/long

c_ubyte

unsigned char

int/long

c_short

short

int/long

c_ushort

unsigned short

int/long

c_int

int

int/long

c_uint

unsigned int

int/long

c_long

long

int/long

c_ulong

unsigned long

int/long

c_longlong

__int64 or long long

int/long

c_ulonglong

unsigned __int64 or unsigned long long

int/long

c_float

float

float

c_double

double

float

c_longdouble

long double

float

c_char_p

char * (NUL terminated)

string or None

c_wchar_p

wchar_t * (NUL terminated)

unicode or None

c_void_p

void *

int/long or None

  1. 构造函数接受任何具有真值的对象。

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

>>> c_int()
c_long(0)
>>> c_char_p("Hello, World")
c_char_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_p以及c_void_p改变所述存储器位置它们指向,而不是内容的内存块(当然不是,因为Python字符串是不可变的):

>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print c_s
c_char_p('Hi, there')
>>> print s                 # first string 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 '\x00\x00\x00'
>>> p = create_string_buffer("Hello")      # create a buffer containing a NUL terminated string
>>> print sizeof(p), repr(p.raw)
6 'Hello\x00'
>>> print repr(p.value)
'Hello'
>>> p = create_string_buffer("Hello", 10)  # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello\x00\x00\x00\x00\x00'
>>> p.value = "Hi"
>>> print sizeof(p), repr(p.raw)
10 'Hi\x00lo\x00\x00\x00\x00\x00'
>>>

create_string_buffer()函数将取代该c_buffer()函数(仍可作为别名使用),以及c_string()早期版本的ctypes函数。要创建包含C类型的Unicode字符的可变内存块,请wchar_t使用该create_unicode_buffer()函数。

1.5. 调用函数,继续

需要注意的是的printf打印到实际的标准输出通道,sys.stdout,所以这些例子只是在控制台提示符下运行,而不是从内IDLEPythonWin的

>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("Hello, %S\n", u"World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19
>>> printf("%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
>>>

如前所述,除整数,字符串和unicode字符串之外的所有Python类型都必须用相应的ctypes类型封装,以便它们可以转换为所需的C数据类型:

>>> printf("An int %d, a double %f\n", 1234, c_double(3.14))
An int 1234, a double 3.140000
31
>>>

1.6. 使用您自己的自定义数据类型调用函数

您还可以自定义ctypes参数转换以允许您自己的类的实例用作函数参数。ctypes查找一个_as_parameter_属性并将其用作函数参数。当然,它必须是整数,字符串或unicode之一:

>>> class Bottles(object):
...     def __init__(self, number):
...         self._as_parameter_ = number
...
>>> bottles = Bottles(42)
>>> printf("%d bottles of beer\n", bottles)
42 bottles of beer
19
>>>

如果您不想将实例的数据存储在_as_parameter_实例变量中,则可以定义一个property()使数据可用的数据。

1.7. 指定所需的参数类型(函数原型)

通过设置argtypes属性,可以指定从DLL导出的函数所需的参数类型。

argtypes必须是C数据类型的序列(该printf函数在这里可能不是一个好例子,因为它取决于格式字符串需要可变数量和不同类型的参数,另一方面,实验此功能非常方便) :

>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf("String '%s', Int %d, Double %f\n", "Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>>

指定格式可以防止不兼容的参数类型(就像C函数的原型一样),并尝试将参数转换为有效的类型:

>>> printf("%d %d %d", 1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ArgumentError: argument 2: exceptions.TypeError: wrong type
>>> printf("%s %d %f\n", "X", 2, 3)
X 2 3.000000
13
>>>

如果你已经定义了你自己传递给函数调用的from_param()类,你必须实现一个类方法,以便它们能够按argtypes顺序使用它们。本from_param()类方法接收传给函数的Python对象,它做一个类型检测,或者是需要确保这个对象是可接受的,然后返回对象本身,它_as_parameter_不管你想传递的C函数属性,或在这种情况下的论点。同样,结果应该是整数,字符串,unicode,ctypes实例或具有_as_parameter_属性的对象。

1.8. 返回类型

默认情况下,函数被假定为返回C int类型。其他返回类型可以通过设置restype函数对象的属性来指定。

这是一个更高级的例子,它使用了strchr函数,它需要一个字符串指针和一个字符,并返回一个指向字符串的指针:

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

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

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr("abcdef", "d")
'def'
>>> strchr("abcdef", "def")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print strchr("abcdef", "x")
None
>>> strchr("abcdef", "d")
'def'
>>>

restype如果外部函数返回一个整数,您也可以使用可调用的Python对象(例如函数或类)作为属性。可调用函数将用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
WindowsError: [Errno 126] The specified module could not be found.
>>>

WinError是一个函数,它将调用Windows FormatMessage()API获取错误代码的字符串表示形式,并返回一个异常。WinError需要一个可选的错误代码参数,如果没有人使用,它会调用GetLastError()来检索。

请注意,通过errcheck属性可以使用更强大的错误检查机制;详情请参阅参考手册。

1.9. 传递指针(或者:通过引用传递参数)

有时C API函数需要一个指向数据类型的指针作为参数,可能写入相应的位置,或者数据太大而无法通过值传递。这也被称为通过引用传递参数

ctypesbyref()通过引用导出用于传递参数的函数。这个pointer()函数可以达到同样的效果,但是pointer()由于它构造了一个真正的指针对象,因此做了很多工作,所以byref()如果你不需要Python本身的指针对象,使用它会更快。

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer('\000' * 32)
>>> print i.value, f.value, repr(s.value)
0 0.0 ''
>>> libc.sscanf("1 3.14 Hello", "%d %f %s",
...             byref(i), byref(f), s)
3
>>> print i.value, f.value, repr(s.value)
1 3.1400001049 'Hello'
>>>

1.10. 结构和联合

结构和联合必须从导出StructureUnion其中所定义的基类ctypes模块。每个子类都必须定义一个_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))

段描述符可以从类中检索,它们对于调试很有用,因为它们可以提供有用的信息:

>>> 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,但图书馆无法保证在一般情况下工作。具有位域的联合和结构应始终通过指针传递给函数。

1.11. 结构/联合对齐和字节顺序

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

ctypes使用结构和联合的本地字节顺序。要建立与非本地字节顺序结构,你可以使用一个BigEndianStructureLittleEndianStructureBigEndianUnion,和LittleEndianUnion基类。这些类不能包含指针字段。

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

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,
...
1 2 3 4 5 6 7 8 9 10
>>>

1.14. 指针

通过调用pointer()一个ctypes类型的函数来创建指针实例:

>>> 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中一样:您可以访问或更改任意内存位置。通常,如果您从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
>>>

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

1.15. 类型转换

通常,ctypes会进行严格的类型检查。这意味着,如果您POINTER(c_int)argtypes函数列表中或者在结构定义中作为成员字段的类型,则只接受完全相同类型的实例。这个规则有一些例外,其中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
>>>

另外,如果一个函数参数被显式声明为一个指针类型(如POINTER(c_int))in argtypes,则可以将该指针类型的对象(c_int在本例中)传递给该函数。byref()在这种情况下,ctypes会自动应用所需的转换。

要将POINTER类型字段设置为NULL,您可以分配None

>>> bar.values = None
>>>

有时你有不兼容类型的实例。在C中,您可以将一种类型转换为另一种类型。ctypes提供cast()可以以相同方式使用的功能。Bar上面定义的结构接受其字段的POINTER(c_int)指针或c_int数组values,但不包含其他类型的实例:

>>> 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()可以用来分配结构的values字段Bar

>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print bar.values[0]
0
>>>

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,
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

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

对于回调函数的第一个实现,我们只需打印我们得到的参数,并返回0(增量开发;-):

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a, b
...     return 0
...
>>>

创建C可调用回调:

>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

我们准备好了:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) 
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
>>>

我们知道如何访问指针的内容,所以让我们重新定义我们的回调:

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a[0], b[0]
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

这是我们在Windows上得到的:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) 
py_cmp_func 7 1
py_cmp_func 33 1
py_cmp_func 99 1
py_cmp_func 5 1
py_cmp_func 7 5
py_cmp_func 33 5
py_cmp_func 99 5
py_cmp_func 7 99
py_cmp_func 33 99
py_cmp_func 7 33
>>>

看到在Linux上sort函数看起来效率更高,它比较少做比较:

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

在Windows上最终运行:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) 
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 5 99
py_cmp_func 1 99
py_cmp_func 33 7
py_cmp_func 1 33
py_cmp_func 5 33
py_cmp_func 5 7
py_cmp_func 1 7
py_cmp_func 5 1
>>>

在Linux上:

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

Windows qsort()功能比Linux版本需要的更多,这很有趣。

正如我们可以轻松检查,我们的数组现在排序:

>>> for i in ia: print i,
...
1 5 7 33 99
>>>

注意

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

另外,请注意,如果在Python控制之外创建的线程(例如通过调用回调的外部代码)中调用回调函数,则ctypes会在每次调用时创建一个新的虚拟Python线程。这种行为在大多数情况下都是正确的,但这意味着即使这些调用是由同一个C线程创建的,存储在其中的值threading.local不会在不同的回调中生存。

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),或者c_long(2)如果-OO已经指定。

一个扩展的例子也演示了如何使用指针来访问PyImport_FrozenModules由Python导出的指针。

引用Python文档:该指针初始化为指向“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是记录pointer数组struct_frozen,我们可以遍历它,但是我们必须确保我们的循环终止,因为指针没有大小。迟早它可能会因访问冲突或其他原因而崩溃,所以最好在我们敲入NULL条目时跳出循环:

>>> for item in table:
...     print item.name, item.size
...     if item.name is None:
...         break
...
__hello__ 104
__phello__ -104
__phello__.spam 104
None 0
>>>

标准Python有一个冻结模块和一个冻结包(由负大小成员表示)的事实并不为人所熟知,它仅用于测试。import __hello__例如试用。

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将缓冲区内容复制temp0rc缓冲区中。这又反过来改变了内容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
>>>

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

1.20. 可变大小的数据类型

ctypes 为可变大小的数组和结构提供了一些支持。

resize()函数可用于调整现有ctypes对象的内存缓冲区大小。该函数将对象作为第一个参数,并将请求的大小以字节为第二个参数。内存块不能小于由对象类型指定的自然内存块,ValueError如果尝试这样,则会引发a :

>>> 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的动态特性,并且在所需大小已知的情况下(根据具体情况)重新定义数据类型。

2. ctypes参考

2.1. 查找共享库

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

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

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

ctypes.util.find_library(name)

尝试找到一个库并返回一个路径名。名字是不一样的任何前缀库名的lib,标的相同.so.dylib或版本号(这是用于POSIX链接器选项的形式-l)。如果没有找到库,则返回None

确切的功能依赖于系统。

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

>>> 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()定位在运行时库。

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调用约定,并假定返回特定于Windows的HRESULT代码。HRESULT值包含指定函数调用失败还是成功的信息,以及其他错误代码。如果返回值表示失败,WindowsError则自动提升。

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

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

在Windows CE只标准调用约定时,为了方便,WinDLLOleDLL使用标准调用约定在这个平台上。

在调用由这些库导出的任何函数之前释放Python 全局解释器锁,然后重新获取它。

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

这个类的实例的行为与CDLL实例类似,只是在函数调用期间没有释放Python GIL ,并且在函数执行后检查了Python错误标志。如果设置了错误标志,则会引发Python异常。

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

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

模式参数可用于指定库的加载方式。有关详细信息,请参阅dlopen(3)联机帮助页。在Windows上,模式被忽略。在POSIX系统上,RTLD_NOW总是被添加,并且是不可配置的。

use_errno参数,当设置为true,使一个ctypes机制,允许访问该系统errno以安全的方式错误号。ctypes维护系统errno变量的线程局部副本; 如果您在函数调用与ctypes私有副本交换之前调用使用use_errno=Trueerrno值创建的外部函数,则在函数调用后立即发生相同的情况。

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

use_last_error参数,设置为true时,使能由所述管理Windows错误代码相同的机制GetLastError()SetLastError()Windows API函数; ctypes.get_last_error()ctypes.set_last_error()用于请求和更改Windows错误代码的ctypes专用副本。

2.6版新增功能:添加了use_last_erroruse_errno可选参数。

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

2.3. 外部职能

如前一节所述,外部函数可以作为加载共享库的属性来访问。默认以这种方式创建的函数对象接受任意数量的参数,接受任何ctypes数据实例作为参数,并返回由库加载器指定的默认结果类型。他们是一个私人课堂的例子:

class ctypes._FuncPtr

C可调用外部函数的基类。

外部函数的实例也是C兼容的数据类型; 它们代表C函数指针。

这种行为可以通过分配给外部函数对象的特殊属性来定制。

restype

指定一个ctypes类型来指定外部函数的结果类型。使用Nonevoid,函数不返回任何东西。

可以分配一个不是ctypes类型的可调用Python对象,在这种情况下,假定该函数返回一个C int,并且可调用对象将用该整数调用,从而允许进一步处理或错误检查。不推荐使用此方法,以便更灵活地进行后处理或错误检查,并使用ctypes数据类型作为参数restype并将可调用参数指定给参数errcheck

argtypes

分配一个ctypes类型的元组来指定该函数接受的参数类型。使用stdcall调用约定的函数只能使用与此元组长度相同数量的参数来调用; 使用C调用约定的函数也接受其他未指定的参数。

当调用外部函数时,每个实际参数都会传递给元组中from_param()项目的类方法argtypes,此方法允许将实际参数调整为外部函数接受的对象。例如,元组中的c_char_p项目argtypes将使用ctypes转换规则将作为参数传递的unicode字符串转换为字节字符串。

新增功能:现在可以将项目放入不是ctypes类型的argtypes中,但每个项目都必须有一个from_param()返回可用作参数(整数,字符串,ctypes实例)的值的方法。这允许定义可以将自定义对象作为函数参数的适配器。

errcheck

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

callable(result, func, arguments)

result是外部函数返回的内容,如restype属性所指定的那样。

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

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

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

exception ctypes.ArgumentError

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

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错误代码也一样。

在版本2.6中更改:添加了可选的use_errnouse_last_error参数。

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)

返回指定地址处的外部函数,该地址必须是整数。

prototype(callable)

从Python 可调用中创建一个C可调用函数(回调函数)。

prototype(func_spec[, paramflags])

返回共享库导出的外部函数。func_spec必须是2元组(name_or_ordinal, library)。第一项是作为字符串的导出函数的名称,或者是作为小整数的导出函数的序数。第二项是共享库实例。

prototype(vtbl_index, name[, paramflags[, iid]])

返回将调用COM方法的外部函数。vtbl_index是虚函数表中的索引,一个小的非负整数。名称是COM方法的名称。iid是用于扩展错误报告的接口标识符的可选指针。

COM方法使用特殊的调用约定:除了在argtypes元组中指定的那些参数外,它们还需要一个指向COM接口的指针作为第一个参数。

可选的paramflags参数创建比上述功能更多功能的外部函数包装器。

paramflags必须是一个长度与元素长度相同的元组argtypes

此元组中的每个项目都包含有关参数的更多信息,它必须是包含一个,两个或三个项目的元组。

第一个项目是一个包含参数方向标志组合的整数:

1指定函数的输入参数.2输出参数。外部函数填充一个值。输入参数默认为整数零。

可选的第二项是作为字符串的参数名称。如果指定了这个,可以用命名参数调用外部函数。

可选的第三项是该参数的默认值。

此示例演示如何包装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 GetWindowRectAPI函数返回一个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
>>>

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),但建设速度更快。

2.6版新增功能:添加了偏移量可选参数。

ctypes.cast(obj, type)

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

ctypes.create_string_buffer(init_or_size[, size])

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

init_or_size必须是指定数组大小的整数,或者是用来初始化数组项的字符串。

如果一个字符串被指定为第一个参数,那么缓冲区会比字符串的长度大一个项目,以便数组中的最后一个元素是一个NUL终止字符。一个整数可以作为第二个参数传递,它允许指定数组的大小,如果不应该使用字符串的长度。

如果第一个参数是一个unicode字符串,则根据ctypes转换规则将其转换为一个8位字符串。

ctypes.create_unicode_buffer(init_or_size[, size])

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

init_or_size必须是一个指定数组大小的整数,或者一个将用于初始化数组项的unicode字符串。

如果将一个unicode字符串指定为第一个参数,则会将缓冲区设置为比字符串长度大一个项目,以便数组中的最后一个元素是NUL终止字符。一个整数可以作为第二个参数传递,它允许指定数组的大小,如果不应该使用字符串的长度。

如果第一个参数是8位字符串,则根据ctypes转换规则将其转换为unicode字符串。

ctypes.DllCanUnloadNow()

仅Windows:这个函数是一个钩子,它允许实现具有ctypes的进程内COM服务器。它是从DllCanUnloadNow函数调用的_ctypes扩展dll导出的。

ctypes.DllGetClassObject()

仅Windows:这个函数是一个钩子,它允许实现具有ctypes的进程内COM服务器。它从_ctypes扩展dll导出的DllGetClassObject函数中调用。

ctypes.util.find_library(name)

尝试找到一个库并返回一个路径名。名字是库名称没有任何前缀一样lib,后缀的相似.so.dylib或版本号(这是用于POSIX链接器选项的形式-l)。如果没有找到库,则返回None

确切的函数依赖于系统。

版本2.6中更改:仅Windows:find_library("m")find_library("c")将呼叫结果返回给find_msvcrt()

ctypes.util.find_msvcrt()

仅Windows:返回Python使用的VC运行时库的文件名,以及扩展模块。如果库的名称无法确定,则返回None

如果您需要释放内存(例如,通过调用扩展模块分配内存),free(void *)则必须在分配内存的同一库中使用此函数。

2.6版本中的新功能。

ctypes.FormatError([code])

仅适用于Windows:返回的错误代码的文本描述代码。如果未指定错误代码,则通过调用Windows API函数GetLastError来使用上一个错误代码。

ctypes.GetLastError()

仅Windows:返回调用线程中由Windows设置的最后一个错误代码。该函数GetLastError()直接调用Windows 函数,它不返回错误代码的ctypes-private副本。

ctypes.get_errno()

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

2.6版本中的新功能。

ctypes.get_last_error()

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

2.6版本中的新功能。

ctypes.memmove(dst, src, count)

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

ctypes.memset(dst, c, count)

与标准C memset库函数相同:填充地址为dst的存储器块,其计数字节为cdst必须是指定地址的整数或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_conversion_mode(encoding, errors)

此函数设置ctypes对象在8位字符串和unicode字符串之间转换时使用的规则。编码必须是指定编码的字符串,例如'utf-8''mbcs'错误必须是指定编码/解码错误处理错误的字符串。可能的值的实例是"strict""replace",或"ignore"

set_conversion_mode()返回包含以前转换规则的2元组。在Windows上,最初的转换规则('mbcs', 'ignore')在其他系统上('ascii', 'strict')

ctypes.set_errno(value)

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

2.6版本中的新功能。

ctypes.set_last_error(value)

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

2.6版本中的新功能。

ctypes.sizeof(obj_or_type)

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

ctypes.string_at(address[, size])

这个函数返回了从内存地址字符串地址。如果指定了大小,则将其用作大小,否则将假定该字符串为零终止。

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

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

ctypes.wstring_at(address[, size])

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

2.6. 数据类型

class ctypes._CData

这个非公共类是所有ctypes数据类型的公共基类。除此之外,所有ctypes类型实例都包含一个保存C兼容数据的内存块; 内存块的地址由addressof()辅助函数返回。另一个实例变量暴露为_objects; 这包含其他Python对象,在内存块包含指针的情况下需要保持活动状态。

ctypes数据类型的通用方法,这些都是类方法(确切地说,它们是元类的方法):

from_buffer(source[, offset])

此方法返回共享source对象的缓冲区的ctypes实例。所述对象必须支持可写缓冲器接口。可选的offset参数以字节为单位指定源缓冲区的偏移量;默认值为零。如果源缓冲区不够大,则引发ValueError

2.6版本中的新功能。

from_buffer_copy(source[, offset])

此方法创建一个ctypes实例,从必须可读的对象缓冲区复制缓冲区。可选的offset参数以字节为单位指定源缓冲区的偏移量;默认值为零。如果源缓冲区不够大,则引发ValueError

2.6版本中的新功能。

from_address(address)

此方法使用address指定的内存返回一个ctypes类型实例,该address必须是整数。

from_param(obj)

此方法将obj调整为ctypes类型。当外部函数的argtypes元组中存在该类型时,它将与在外部函数调用中使用的实际对象一起调用;它必须返回一个可以用作函数调用参数的对象。

所有的ctypes数据类型都有这个classmethod的默认实现,如果这是一个类型的实例,通常会返回obj。有些类型也接受其他对象。

in_dll(library, name)

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

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

_b_base_

有时ctypes数据实例不拥有它们包含的内存块,而是共享基础对象的部分内存块。所述_b_base_只读构件是根ctypes的对象拥有该存储器块。

_b_needsfree_

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

_objects

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

2.7. 基本数据类型

class ctypes._SimpleCData

这个非公共类是所有基本ctypes数据类型的基类。这里提到它是因为它包含基本ctypes数据类型的通用属性。_SimpleCData是它的一个子类_CData,所以它继承了它们的方法和属性。

在版本2.6中更改:现在可以对不包含指针的ctypes数据类型进行pickle。

实例具有单个属性:

value

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

value从ctypes实例中检索属性时,通常每次都会返回一个新对象。ctypes没有实现原来的目标回报率,总是一个新的对象被创建。所有其他ctypes对象实例也是如此。

基本数据类型在作为外部函数调用结果返回时,或者例如通过检索结构字段成员或数组项时,会透明地转换为本机Python类型。换句话说,如果一个外国函数有一个restypec_char_p,你总是会收到一个Python字符串,不是一个c_char_p实例。

基本数据类型的子类不会继承此行为。所以,如果一个外部函数restype是它的一个子类c_void_p,你将从函数调用中得到这个子类的一个实例。当然,你可以通过访问value属性来获得指针的值。

这些是基本的ctypes数据类型:

class ctypes.c_byte

表示C signed char数据类型,并将该值解释为小整数。构造函数接受一个可选的整数初始值设定项; 没有溢出检查完成。

class ctypes.c_char

表示C char数据类型,并将该值解释为单个字符。构造函数接受一个可选的字符串初始值设定项,字符串的长度必须恰好为一个字符。

class ctypes.c_char_p

char *当它指向零终止的字符串时表示C 数据类型。对于也可能指向二进制数据的通用字符指针,POINTER(c_char)必须使用。构造函数接受一个整数地址或一个字符串。

class ctypes.c_double

表示C double数据类型。构造函数接受一个可选的浮点初始值设定项。

class ctypes.c_longdouble

表示C long double数据类型。构造函数接受一个可选的浮点初始值设定项。在sizeof(long double) == sizeof(double)它是别名的平台上c_double

2.6版本中的新功能。

class ctypes.c_float

表示C float数据类型。构造函数接受一个可选的浮点初始值设定项。

class ctypes.c_int

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

class ctypes.c_int8

表示C 8位signed int数据类型。通常是别名c_byte

class ctypes.c_int16

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

class ctypes.c_int32

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

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数据类型。

2.7版本的新功能。

class ctypes.c_ubyte

表示C unsigned char数据类型,它将该值解释为小整数。构造函数接受一个可选的整数初始值设定项; 没有溢出检查完成。

class ctypes.c_uint

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

class ctypes.c_uint8

表示C 8位unsigned int数据类型。通常是别名c_ubyte

class ctypes.c_uint16

表示C 16位unsigned int数据类型。通常是别名c_ushort

class ctypes.c_uint32

表示C 32位unsigned int数据类型。通常是别名c_uint

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数据类型(更准确地说,_Bool来自C99)。其值可以是TrueFalse,和构造函数接受具有真值的任何对象。

2.6版本中的新功能。

class ctypes.HRESULT

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

class ctypes.py_object

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

ctypes.wintypes模块提供了相当长的一段其他Windows特定的数据类型,例如HWNDWPARAMDWORD。一些有用的结构像MSG或者RECT也被定义。

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将创建描述符,允许通过直接属性访问来读写字段。这些是

_fields_

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

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

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

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

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

_fields_类变量但是,必须被定义在第一次使用之前的类型(创建一个实例,sizeof()被称为其上,等等)。稍后分配给_fields_类变量将引发AttributeError。

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

_pack_

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

_anonymous_

列出未命名(匿名)字段名称的可选序列。_anonymous_必须在_fields_分配时已经定义,否则将不起作用。

此变量中列出的字段必须是结构或联合类型字段。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_

2.9. 数组和指针

class ctypes.Array(*args)

数组的抽象基类。

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

_length_

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

_type_

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

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

class ctypes._Pointer

指针的私有抽象基类。

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

如果指针指向数组,则可以使用标准下标和切片访问来读取和写入其元素。指针对象没有大小,所以len()会提高TypeError。负指标将在指针之前从内存中读取(如C中所示),并且超出范围的下标可能会因访问冲突(如果幸运的话)而崩溃。

_type_

指定指向的类型。

contents

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

扫码关注云+社区

领取腾讯云代金券