ctypes– Python的外部函数库


ctypes是Python的外部函数库。它提供C compatibledata类型,并允许在DLL或共享库中调用函数。它可以用于将这些库包装在纯Python中.

 

ctypes教程

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

注意:某些代码示例引用了ctypes c_int type。在平台上sizeof(long) == sizeof(int)它是c_long的别名。所以,如果你想要c_long打印c_int– 它们实际上是同一种类型.

 

从加载的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 <module>
  File "ctypes.py", line 239, in __getattr__
    func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>

请注意,win32系统dllkernel32user32经常导出ANSI和函数的UNICODE版本。UNICODE版本以W附加到名称后导出,而ANSI版本导出时A附加到名称。win32 GetModuleHandle函数,对于给定的模块名称返回module handle,具有以下C原型,并且amacro用于将其中一个公开为GetModuleHandle,具体取决于是否为UNICODE定义与否:

/* ANSI version */
HMODULE GetModuleHandleA(LPCSTR lpModuleName);
/* UNICODE version */
HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll不会试图通过魔法选择其中一个,你必须通过明确指定GetModuleHandleAGetModuleHandleW来访问你需要的版本,然后调用它字节或字符串对象.

有时,dll导出的函数名称不是有效的Pythonidentifiers,比如"??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 <module>
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>

 

调用函数

你可以调用这些函数,就像任何其他Python可调用函数一样。这个例子使用time()函数,它返回自Unixepoch以来的系统时间,以及GetModuleHandleA()function,返回一个win32 modulehandle.

这个例子用NULL指针调用两个函数(None应该用作NULL指针):

>>> print(libc.time(None))  
1150640792
>>> print(hex(windll.kernel32.GetModuleHandleA(None)))  
0x1d000000
>>>

注意

ctypes可能会举起ValueError调用该函数后,ifit检测到传递了无效数量的参数。这种行为不应该依赖。它在3.6.2中已弃用,将在3.7.

ValueError使用stdcall调用约定调用cdecl函数时会引发,反之亦然:

>>> cdll.kernel32.GetModuleHandleA(None)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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 <module>
ValueError: Procedure probably called with too many arguments (4 bytes in excess)
>>>

要找出正确的调用约定,你必须查看C头文件或你要调用的函数的文档.

Windows上,ctypes使用win32结构化异常处理来防止崩溃使用invalidargument值调用函数时的保护错误:

>>> windll.kernel32.GetModuleHandleA(32)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: exception: access violation reading 0x00000020
>>>

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

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

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

 

基本数据类型

ctypes定义了一些原始的C兼容数据类型

ctypes类型 C类型 Python类型
c_bool _Bool bool(1)
c_char char 1字符字节对象
c_wchar wchar_t 1个字符的字符串
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64long long int
c_ulonglong unsigned __int64unsigned long long int
c_size_t size_t int
c_ssize_t ssize_tPy_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char *(NULL终止) 字节对象或None
c_wchar_p wchar_t *(NULL终止) 字符串或None
c_void_p void * int或None
  1. 构造函数接受任何对象具有真值.

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

>>> c_int()
c_long(0)
>>> c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>> c_ushort(-3)
c_ushort(65533)
>>>

因为这些类型是可变的,所以它们的值也可以随后更改:

>>> i = c_int(42)
>>> print(i)
c_long(42)
>>> print(i.value)
42
>>> i.value = -99
>>> print(i.value)
-99
>>>

为指针类型c_char_p,c_wchar_pc_void_p的实例分配一个新值会改变memory location他们指向内存块的not the contents(当然不是,因为Pythonbytes对象是不可变的):

>>> s = "Hello, World"
>>> c_s = c_wchar_p(s)
>>> print(c_s)
c_wchar_p(139966785747344)
>>> print(c_s.value)
Hello World
>>> c_s.value = "Hi, there"
>>> print(c_s)              # the memory location has changed
c_wchar_p(139966783348904)
>>> print(c_s.value)
Hi, there
>>> print(s)                # first object is unchanged
Hello, World
>>>

但是,您应该小心,不要将它们传递给期望指向可变内存的函数。如果你需要可变的内存块,ctypescreate_string_buffer()以各种方式创造这些的功能。可以使用raw属性访问(或更改)当前内存块内容;如果你想以NUL终止字符串的方式访问它,请使用valueproperty:

>>> 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()function from早期的ctypes发布。要创建一个包含C类型的字符的可变内存块wchar_t使用create_unicode_buffer()函数

 

调用函数,继续

请注意printf打印到真正的标准输出通道,notsys.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 <module>
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
>>>

 

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

您也可以自定义ctypes参数转换以允许您自己的类的实例用作函数参数。ctypes寻找_as_parameter_attribute并将其用作函数参数。当然,它必须是整数,字符串或字节之一:

>>> 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,它可以根据要求提供属性.

 

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

通过设置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 <module>
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()class方法接收传递给函数调用的Python对象,它应该做一个类型检查或者确保这个对象可以接受的任何东西,然后返回对象本身,它的_as_parameter_在这种情况下,属性或任何你想要作为C函数参数传递的属性。同样,结果应该是整数,字符串,字节,ctypes实例,或具有_as_parameter_属性的对象.

 

返回类型

默认情况下,假定函数返回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 <module>
ArgumentError: argument 2: exceptions.TypeError: one character string expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
>>>

您还可以使用可调用的Python对象(例如函数或类)作为restype属性,如果外部函数返回一个整数。将使用调用thecallableintegerC函数返回,此调用的结果将用作函数调用的结果。这对于检查错误返回值并自动引发异常非常有用:

>>> 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 <module>
  File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>

WinError是一个会调用Windows的功能FormatMessage()api获取错误代码的字符串表示,并且returns例外.WinError采用可选的错误代码参数,如果没有使用,则调用GetLastError()来检索它.

请注意,errcheck可以使用更强大的错误检查机制属性;有关详细信息,请参阅参考手册.

 

使用指针(或:通过引用传递参数)

有时候,一个C api函数需要pointer数据类型作为参数,可能写入相应的位置,或者如果数据太大而无法通过值传递。这也称为passing parameters by reference.

ctypes导出byref()函数,用于通过引用传递参数。使用pointer()函数可以实现相同的效果,虽然pointer()因为它构造了一个真正的指针对象而做了很多工作,所以如果你不使用byref()会更快在Python本身需要指针对象:

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

 

结构和联合

结构和联合必须来自StructureUnion基类,它们是在ctypes模块。每个子类必须定义一个_fields_属性。_fields_必须是2-tuples,包含field namefield type.

字段类型必须是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 <module>
ValueError: too many initializers
>>>

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

这是一个RECT结构,它包含两个名为 upperleft 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
>>>

的POINT,也可以初始化嵌套结构在构造函数中有几种方式:

>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))

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

>>> print(POINT.x)
<Field type=c_long, ofs=0, size=4>
>>> print(POINT.y)
<Field type=c_long, ofs=4, size=4>
>>>

警告

ctypes不支持按值传递具有bit-fieldsto函数的联合或结构。虽然这可能适用于32位x86,但图书馆无法保证在一般情况下工作。具有位字段的联合和结构应始终通过指针传递给函数.

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

默认情况下,Structure和Union字段的对齐方式与Ccompiler的方式相同。可以通过指定_pack_子类定义中的class属性。必须将其设置为正整数,并指定字段的最大对齐方式。这个#pragma pack(n)在MSVC中也是如此.

ctypes使用结构和联合的本机字节顺序。要使用非本机字节顺序构建结构,可以使用BigEndianStructure, LittleEndianStructure,BigEndianUnionLittleEndianUnion基类。这些类不能包含指针字段.

 

结构和工会中的字段

可以创建包含位字段的结构和联合。位字段只能用于整数字段,位宽指定为_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>
>>>

 

数组

数组是序列,包含固定数量的相同类型的实例.

创建数组类型的推荐方法是将数据类型与正整数相乘:

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

 

指针

通过在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实例分配给指针的contents属性会导致指针指向存储它的内存位置:

>>> i = c_int(99)
>>> pi.contents = i
>>> pi.contents
c_long(99)
>>>

指针实例也可以用整数索引:

>>> pi[0]
99
>>>

分配整数索引会改变指向的值:

>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)
>>>

也可以使用不同于0的索引,但你必须知道你在做什么,就像在C中一样:你可以访问或更改任意内存位置。一般来说,如果你收到一个来自C函数的指针你只使用这个功能,你know指针实际指向一个数组而不是一个单元.

在幕后,pointer()函数不仅仅是创建指针实例,还必须创建指针types首先这是通过POINTER()函数完成的,该函数接受任何ctypes类型,并返回一个新的类型:

>>> PI = POINTER(c_int)
>>> PI
<class 'ctypes.LP_c_long'>
>>> PI(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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
>>>

 

输入转换次数

通常,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)中显式声明为指针类型(如argtypes),则为尖头型(c_int在这种情况下)可以传递给函数。ctypes将在这种情况下自动应用所需的byref()转换

将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 <module>
TypeError: incompatible types, c_byte_Array_4 instance instead of LP_c_long instance
>>>

在这些情况下,cast()函数很方便.

cast()函数可以用来将一个ctypes实例转换成一个不同的ctypes数据类型cast()接受两个参数,一个是或者可以转换为某种指针的ctypesobject,以及一个ctypespointer类型。它返回第二个参数的一个实例,它引用与第一个参数相同的内存块:

>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
>>>

所以,cast()可以用来分配给values字段Bar thestructure:

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

 

不完整的类型

不完整的类型是结构,联合或数组,其成员不是yetspecified。在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 <module>
  File "<stdin>", line 2, in cell
NameError: name 'cell' is not defined
>>>

因为新的class cell在类声明本身中不可用。在ctypes中,我们可以定义cell类并在类声明之后设置_fields_属性:

>>> from ctypes import *
>>> class cell(Structure):
...     pass
...
>>> cell._fields_ = [("name", c_char_p),
...                  ("next", POINTER(cell))]
>>>

让我们尝试一下。我们创建了cell的两个实例,并让它们指向彼此,最后跟随指针链几次:

>>> c1 = cell()
>>> c1.name = "foo"
>>> c2 = cell()
>>> c2.name = "bar"
>>> c1.next = pointer(c2)
>>> c2.next = pointer(c1)
>>> p = c1
>>> for i in range(8):
...     print(p.name, end=" ")
...     p = p.next[0]
...
foo bar foo bar foo bar foo bar
>>>

 

回调函数

ctypes允许创建C可调用函数指针Python callables。这些有时被称为callback functions.

首先,你必须为回调函数创建一个类。该类知道调用约定,返回类型,以及函数将接收的参数的数量和类型.

CFUNCTYPE()工厂函数使用cdecl调用约定为回调函数创建类型。在Windows上,WINFUNCTYPE()工厂函数使用stdcall调用约定为回调函数创建类型.

这两个工厂函数都以结果类型作为firstargument调用,而回调函数将期望参数类型作为剩余参数调用

我将在这里展示一个使用标准C库的qsort()函数的示例,该函数用于在回调函数的帮助下对项目进行排序。qsort()将用于排序整数数组:

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

qsort()必须使用指向要排序的数据的指针,数据数组中的项目数,一个项的大小以及指向比较函数(回调)的指针来调用。然后将使用两个指针toitems调用回调,如果第一个项目小于第二个项目则返回负整数,如果它们相等则返回零,否则返回正整数

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

注意

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

还要注意,如果在一个线程中调用了回调函数在Python控件的外部创建(例如通过调用回调的外部代码),ctypes在每次调用时都会创建一个新的虚拟Python线程。这个行为在大多数情况下是正确的,但这意味着threading.local存储的值将not在不同的回调中存活,即使是来自同一个C线程的调用也是如此.

 

访问从dlls

导出的值一些共享库不仅导出函数,还导出变量。Python库中的一个例子就是Py_OptimizeFlag,一个0,1或2的整数,取决于onstartup上给出的-O-OO标志.

ctypes可以用in_dll()访问这样的值类型的类方法。pythonapi是一个预定义的符号,可以访问Python Capi:

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

引用该值的文档:

此指针初始化为指向struct _frozen记录的数组,由一个成员全部为NULL或零的记录终止。导入冻结模块时,将在此表中搜索。第三方代码可以通过它来提供动态创建的冻结模块集合.

操纵这个指针甚至可以证明是有用的。为了限制examplesize,我们只展示如何用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记录的数组,我们可以迭代它,但我们必须确保我们的循环终止,因为指针没有大小。迟早它可能会因anaccess违规或其他原因而崩溃,所以最好在我们输入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__试试吧。例如

 

企业

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

Hm。我们当然希望打印最后一句话3 4 1 2。发生了什么?以上是rc.a, rc.b = rc.b, rc.a行的步骤:

>>> temp0, temp1 = rc.b, rc.a
>>> rc.a = temp0
>>> rc.b = temp1
>>>

注意temp0temp1是仍然使用rc的内部缓冲区的对象上面的对象。所以执行rc.a = temp0复制temp0进入rc的缓冲区。反过来,这改变了temp1的内容。所以,最后的任务rc.b = temp1没有预期的效果.

请记住,从Structure,Unions和Arrays中检索子对象不要copy子-object,而是检索一个访问根对象的底层缓冲区的包装器对象.

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

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

为什么要打印Falsectypes实例是包含内存块的对象加上一些描述符 s访问内存的内容。内存块中的Python对象不存储对象本身,而是contents存储了对象。再次访问内容会构造一个新的Python对象!

 

可变大小的数据类型

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

resize()function可用于调整现有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
>>>

这很好很好,但如何访问附加内容这个数组包含哪些元素?由于类型仍然只知道4个元素,因此我们在访问其他元素时遇到错误:

>>> short_array[:]
[0, 0, 0, 0]
>>> short_array[7]
Traceback (most recent call last):
    ...
IndexError: invalid index
>>>

使用ctypes使用可变大小数据类型的另一种方法是使用Python的动态特性,并且)根据具体情况,在已知所需大小之后定义数据类型.

 

ctypes reference

 

查找共享库

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

find_library()函数是以类似于编译器或运行时加载器的方式定位库(在具有最近应加载的共享库的多个平台的平台上),而ctypeslibrary加载器的行为就像程序运行时一样,并调用运行时loaderdirectly.

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

ctypes.util.find_library(name)
试试找到一个库并返回一个路径名。name是库名,没有像lib这样的前缀,后缀如.so, .dylib或版本号(这是用于posix链接器选项-l的形式)。如果找不到库,则返回None.

确切的功能取决于系统.

在Linux上,find_library()尝试运行外部程序(/sbin/ldconfig, gcc, objdumpld)找到库文件。它返回库文件的文件名.

更改版本3.6:在Linux上,环境变量的值LD_LIBRARY_PATH用于搜索库时,如果无法通过任何其他方式找到库.

这里有一些例子:

>>> 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()尝试几个预定义的命名方案和pathsto定位库,如果成功则返回一个完整的路径名:

>>> 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()沿系统搜索路径搜索,并返回完整路径名,但由于没有预定义的命名方案,calllike find_library("c")将失败并返回None.

如果用ctypes,它may最好在开发时确定共享库名称,并将其硬编码到包装器模块中,而不是使用find_library()在运行时定位库.

 

加载共享库

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

class ctypes.CDLLname, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False
此类的实例表示已加载的共享库。这些库中的函数使用标准的C调用约定,并假定返回int.
class ctypes.OleDLLname, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False
仅限Windows:此类的实例表示已加载的共享库,这些库中的函数使用stdcall调用约定,而areassumed返回特定于WindowsHRESULT代码。HRESULTvalues包含指定函数调用是否失败的信息,以及其他错误代码。如果返回值信号失败,OSError会自动升起

在版本3.3中更改:WindowsError曾经被提升.

class ctypes.WinDLL (name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
仅限Windows:此类的实例表示已加载的共享库,函数在这些图书馆使用stdcall调用约定,并且areasumed返回int默认情况下

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

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

class ctypes.PyDLLname, mode=DEFAULT_MODE, handle=None
这个类的实例表现得像CDLL实例,除了thePython GIL是not在函数调用期间释放,并在函数执行后检查Python错误标志。如果设置了错误标志,则会引发Pythonexception .

因此,这仅对直接调用Python C api函数有用.

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

mode参数可用于指定库的加载方式。详情请参阅dlopen(3)联机帮助页。在Windows上,modeisignored。在posix系统上,RTLD_NOW总是被添加,并且是不可配置的.

use_errno参数,当设置为true时,启用一个ctypes机制,允许以安全的方式访问系统errno错误号.ctypes维护系统的线程局部副本errno变量;如果调用用use_errno=True创建的外部函数,那么函数调用之前的errno值与ctypes privatecopy交换,在函数调用后立即发生同样的事情.

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

use_last_error参数,当设置为true时,为Windows错误代码启用相同的机制,该错误代码由GetLastError()SetLastError() Windows API函数管理;ctypes.get_last_error()ctypes.set_last_error()用于请求和更改windows错误代码的ctypes私有化.

ctypes.RTLD_GLOBAL
Flag用作mode参数。在没有此标志的平台上,它被定义为整数零.
ctypes.RTLD_LOCAL
Flag用作mode参数。在没有这个的平台上,它与RTLD_GLOBAL.
ctypes.DEFAULT_MODE
用于加载共享库的默认模式。在OSX 10.3上,这是RTLD_GLOBAL,否则它与RTLD_LOCAL.

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

>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6")  # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False

可以使用以下公共属性,它们的名称以anunderscore开头,不与导出的函数名冲突:

PyDLL._handle
用于访问库的系统句柄.
PyDLL._name
在构造函数中传递的库的名称.

也可以使用其中一个预制对象加载共享库,这些对象是LibraryLoader上课,或者拨打LoadLibrary()方法,或者通过检索库作为加载器实例的属性.

class ctypes.LibraryLoader (dlltype)
加载共享库的类。dlltype应该是CDLL, PyDLL, WinDLLOleDLL类型之一

__getattr__()具有特殊行为:它允许通过将其作为库加载器实例的属性来加载共享库。结果是缓存的,所以重复的属性访问每次返回相同的库.

LoadLibraryname
将一个共享库加载到进程中并返回它。这个方法总是返回一个新的库实例.

这些预制的库加载器可用:

ctypes.cdll
创建CDLL实例
ctypes.windll
只有窗口:创建WinDLL实例
ctypes.oledll
只有窗口:创建OleDLL实例
ctypes.pydll
创建PyDLL instances.

为了直接访问C Python api,可以使用一个随时可用的Python共享库对象:

ctypes.pythonapi
PyDLL将Python C API函数公开为属性。请注意,假设所有这些函数都返回C int,这当然不总是事实,所以你必须给出正确的restype属性来使用这些功能.

 

外国函数

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

class ctypes._FuncPtr
基类为C可调用的外来函数

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

这个行为可以通过赋予foreign函数对象的特殊属性来定制.

restype
分配一个ctypes类型来指定外部函数的结果类型。使用Nonevoid,一个没有返回任何东西的功能.

可以分配一个不是ctypestype的可调用Python对象,在这种情况下假定函数返回一个C int,并且将使用此整数调用callable,从而允许进一步处理或错误检查。不推荐使用它,为了更灵活的邮件处理或错误检查,使用一个ctypes数据类型restype并为errcheck属性分配一个callable

argtypes
对一个ctypes类型的元组进行分配指定函数接受的参数类型。使用stdcall调用约定的函数只能使用与thistuple的长度相同的参数数来调用;使用C调用约定的函数也接受附加的,未指定的参数.

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

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

errcheck
为此属性分配Python函数或其他可调用函数。将使用三个或更多参数调用thecallable:

callableresult, func, arguments
result是外部函数返回的内容,由restypeattribute.

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

arguments是包含最初传递给函数调用的参数的元组,这允许对所使用的参数进行特殊处理.

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

exception ctypes.ArgumentError
当外部函数调用无法转换时引发该异常ofpasssed arguments。

 

函数原型

也可以通过实例化函数prototype来创建外部函数。函数原型类似于C中的函数原型;它们描述了函数(返回类型,参数类型,调用约定)而没有定义实现。必须使用所需的resulttype和函数的参数类型调用工厂函数,并且可以将其用作decoratorfactories,因此,可以通过@wrapper语法应用于函数。查看回调函数示例.

ctypes.CFUNCTYPE (restype, *argtypes, use_errno=False, use_last_error=False)
返回的函数原型创建使用标准Ccalling约定的函数。该功能将在通话期间释放GIL。如果use_errno设置为true,系统的ctypes私有副本errno变量在调用之前和之后与真实的errno值交换;use_last_errorWindows错误代码也一样.
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调用方法的函数。该功能将not在通话期间释放GIL .

由这些工厂函数创建的函数原型可以以不同的方式实例化,具体取决于调用中参数的类型和数量:

prototypeaddress
返回指定地址的外部函数,该地址必须是整数.
prototypecallable)
从Python创建一个C可调用函数(回调函数)callable.
prototypefunc_spec [, paramflags]
返回共享库导出的外部函数。func_spec必须是2元组(name_or_ordinal, library)。第一项是导出函数的名称为字符串,或导出函数的序号为小整数。第二项是共享库实例.
prototype (vtbl_index, name [, paramflags [, iid]]
返回一个将调用COM方法的外部函数。vtbl_index是虚函数表的索引,是一个小的非负整数。name是COM方法的名称。iid是扩展错误报告中使用的接口标识符的可选指针.

方法使用特殊的调用约定:它们需要一个指针作为第一个参数的COM接口,除了那些在argtypes tuple.

中指定的参数可选的paramflags参数创建的外部函数包装器具有比上述功能更多的功能.

paramflags必须是一个与argtypes.

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

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

1
指定函数的输入参数.
2
输出参数。外来函数填写值
/
/输入参数默认为整数零.

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

可选的第三项是此参数的默认值.

这个例子演示了如何包装Windows MessageBoxW函数,它支持默认参数和命名参数。来自windows头文件的C声明是这样的:

WINUSERAPI int WINAPI
MessageBoxW(
    HWND hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT uType);

这是ctypes

>>> from ctypes import c_int, WINFUNCTYPE, windll
>>> from ctypes.wintypes import HWND, LPCWSTR, UINT
>>> prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
>>> paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", "Hello from ctypes"), (1, "flags", 0)
>>> MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)

的包装现在可以用这些方式调用MessageBox外来函数:

>>> MessageBox()
>>> MessageBox(text="Spam, spam, spam")
>>> MessageBox(flags=2, text="foo bar")

第二个例子演示了输出参数。win32 GetWindowRect函数通过将指定窗口的尺寸复制到调用者必须提供的RECT结构来检索它们的尺寸。这是C声明:

WINUSERAPI BOOL WINAPI
GetWindowRect(
     HWND hWnd,
     LPRECT lpRect);

这是包装ctypes

>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError
>>> from ctypes.wintypes import BOOL, HWND, RECT
>>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
>>> paramflags = (1, "hwnd"), (2, "lprect")
>>> GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)
>>>

带有输出参数的函数会自动返回输出参数值(如果只有一个或一个元组)当存在多个时,包含输出参数值,因此GetWindowRect函数现在返回aRECT实例,当被调用时

//输出参数可以与errcheck协议组合以进一步输出处理和错误检查。win32 GetWindowRect apifunction返回BOOL表示成功或失败,所以这个函数可以进行错误检查,并在api调用失败时引发异常:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     return args
...
>>> GetWindowRect.errcheck = errcheck
>>>

如果errcheck函数返回它收到的变量的参数元组,ctypes继续它对outputparameters的正常处理。如果你想返回一个窗口坐标元组而不是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
>>>

 

实用函数

ctypes.addressofobj
以整数形式返回内存缓冲区的地址。obj必须是ctypes类型的实例.
ctypes.alignment (obj_or_type)
恢复ctypes类型的对齐要求。obj_or_type必须是actypes type或instance.
ctypes.byref (obj [, offset])
Retur a a light-q指向obj的指针,它必须是actypes类型的实例。offset默认为零,并且必须是一个加到内部指针值的整数.

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

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

返回的对象只能用作一个外来函数调用参数。它的行为类似于pointer(obj),但结构快得多.

ctypes.cast (obj, type)
这个函数类似于C中的强制转换操作符。它返回一个新的type实例,它指向obj. type必须是apointer类型的同一个内存块,并且obj必须是一个可以解释为指针的对象.
ctypes.create_string_bufferinit_or_size, size=None
此函数创建一个可变字符缓冲区。返回的对象是c_char.

init_or_size必须是一个指定数组大小的整数,或者是用于初始化数组项的abytes对象.

如果将bytes对象指定为第一个参数,则将缓冲区设置为比其长度更大的一个项目,以便数组中的最后一个元素是NULtermination字符。可以将整数作为第二个参数传递,如果不应该使用字节的长度,则允许指定数组的大小.

ctypes.create_unicode_bufferinit_or_size, size=None
此函数创建一个可变的unicode字符缓冲区。返回的对象是的ctypes数组c_wchar.

init_or_size必须是一个整数,指定数组的大小,或astring将用于初始化数组项.

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

ctypes.DllCanUnloadNow
仅限Windows:此函数是一个钩子,允许使用ctypes实现进程内服务器。从DllCanUnloadNow函数调用_ctypes扩展名dll exports.
ctypes.DllGetClassObject
仅限Windows:此函数是一个钩子,允许使用ctypes实现进程内服务器。从DllGetClassObject函数中调用_ctypes extension dll exports.
ctypes.util.find_libraryname
尝试查找库并返回路径名。name是库名,没有任何前缀,如lib,后缀如.so, .dylib或versionnumber(这是用于posix链接器选项-l的形式)。如果找不到库,则在调用线程中返回None.

确切的功能取决于系统.

ctypes.util.find_msvcrt()
仅限窗口:返回Python使用的VC运行时库的文件名,以及扩展模块。如果无法确定库的名称,则返回None

如果需要释放内存,例如,通过调用free(void *)分配扩展模块,重要的是你在分配内存的同一个库中使用该功能.

ctypes.FormatError ( [code])
仅限Windows:返回错误代码code的文本描述。如果指定了noerror代码,则通过调用Windowsapi函数GetLastError来使用最后一个错误代码.
ctypes.GetLastError ()
仅限Windows:返回Windows设置的最后一个错误代码在调用线程中。该函数直接调用Windows GetLastError()函数,它不返回错误代码的ctypes-private副本.
ctypes.get_errno
返回系统的ctypes-private副本的当前值errno变量.
ctypes.get_last_error (
仅限Windows:在调用线程中返回系统LastError变量的ctypes-private副本的当前值.
ctypes.memmove(dst, src, count)
同为标准C memmove库函数:拷贝countsrcdst. dstsrc的字节必须是可以转换为指针的整数或ctypes实例.
ctypes.memset (dst, c, count)
与标准C memset库函数相同:使用dst填充内存块at//dress countc. dst必须是整数指定地址,或者是ctypes实例.
ctypes.POINTERtype
此工厂函数创建并返回新的ctypes指针类型。Pointertypes在内部被缓存和重用,所以重复调用这个函数。type必须是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_errorvalue
仅限Windows:设置系统的ctypes-private副本的当前值LastError调用线程中的变量value并返回前一个值.
ctypes.sizeofobj_or_type
返回ctypes类型或实例内存缓冲区的大小(以字节为单位)。与C 相同sizeofoperator.
ctypes.string_ataddress, size=-1
此函数返回从内存地址address作为bytesobject。如果指定了大小,则将其用作大小,否则假定该字符串为零终止.
ctypes.WinError (code=None, descr=None )
仅限Windows:此函数可能是ctypes中最糟糕的名称。它创建一个OSError实例。如果未指定code,则调用GetLastError来确定错误代码。如果descr没有说明,FormatError()被称为获得错误的文字描述.

改版3.3:的一个例子WindowsError曾经被创造过

ctypes.wstring_ataddress, size=-1
此函数返回从内存地址address开始的宽字符串作为字符串。如果size指定时,它用作字符串的字符数,否则假定字符串为零 –

 

数据类型

class ctypes._CData
这个非公共类是所有ctypes数据类型的公共基类。在其他方面,所有ctypes类型实例都包含一个保存C兼容数据的内存块;addressof()辅助功能。另一个实例变量暴露为_objects;这包含其他需要保持活动的Python对象,以防内存块包含指针.

ctypes数据类型的常用方法,这些都是类方法(要完成,它们是元类的方法):

from_buffersource [, offset]
此方法返回一个共享source对象缓冲区的ctypes实例。source对象必须支持可写缓冲区接口。可选offsetparameter以字节为单位指定源缓冲区的偏移量;默认值为零。如果源缓冲区不够大ValueError被养了
from_buffer_copysource[, offset]
此方法创建一个ctypes实例,从source对象缓冲区复制缓冲区,该缓冲区必须是可读的。可选offsetparameter以字节为单位指定源缓冲区的偏移量;默认值为零。如果源缓冲区不够大ValueErrorisraised.
from_addressaddress
此方法使用address必须是整数
from_paramobj
这种方法适应obj到一个ctypes类型。当外部函数的中包含类型时,使用外部函数调用中使用的实际对象调用它argtypes元组;它必须返回一个可以用作函数调用参数的对象.

所有ctypes数据类型都有这个类方法的默认实现,通常会返回obj如果那是该类型的实例。Sometypes也接受其他物品.

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

ctypes数据类型的常见变量:

_b_base_
有时ctypes数据实例不拥有它们包含的内存块,而是共享基础对象的部分内存块。该_b_base_只读成员是拥有主题块的根ctypes对象.
_b_needsfree_
当ctypes数据实例已分配内存块本身时,此只读变量为true,否则为false
_objects
这个成员要么是None,要么是包含需要保持活动的Python对象的字典,以便内存块内容保持有效。该对象仅用于调试;永远不要修改这个字典的内容.

 

基本数据类型

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

实例只有一个属性:

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

value属性时从ctypes实例中检索,通常每次都返回一个新对象。ctypes not实现原始对象返回,总是构造一个新对象。所有其他ctypes对象实例的相同的istrue。

Fundamental数据类型,当作为外部函数调用结果返回时,或者,例如,通过检索结构字段成员或数组项,被透明地转换为本机Python类型。换句话说,如果一个外来函数有restype c_char_p,你总会得到一个Python bytesobject,not一个c_char_p instance.

基本数据类型的子类not继承此行为。所以,如果上述函数restypec_void_p,您将从函数调用中接收此子类的实例。当然,你可以通过访问value属性来获取指针的值.

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

class ctypes.c_byte
代表C signed char数据类型,并解释值assmall整数。构造函数接受可选的整数初始值设定项;nooverflow检查完成.
class ctypes.c_char
表示C char数据类型,并将该值解释为单字符。构造函数接受一个可选的字符串初始化程序,字符串的长度必须正好是一个字符.
class ctypes.c_char_p
表示C char *数据类型,当它指向以零结尾的字符串时。对于也可能指向二进制数据的通用字符指针,POINTER(c_char)必须使用。构造函数接受integeraddress或bytes对象.
class ctypes.c_double
表示C double数据类型。构造函数接受anoptional float initializer.
class ctypes.c_longdouble
表示C long double数据类型。构造函数接受anoptional float初始化程序。在sizeof(long double) ==sizeof(double)的平台上,它是c_double.
class ctypes.c_float
的别名代表C float数据类型。构造函数接受anoptional float初始化程序.
class ctypes.c_int
表示C signed int数据类型。构造函数接受anoptional整数初始化器;没有进行溢出检查。在平台上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数据类型。构造函数接受anoptional整数初始化器;没有进行溢出检查.
class ctypes.c_longlong
表示C signed long long数据类型。构造函数接受可选的整数初始值设定项;没有溢出检查.
class ctypes.c_short
表示C signed short数据类型。构造函数接受anoptional整数初始化器;没有溢出检查完成.
class ctypes.c_size_t
表示C size_t datatype.
class ctypes.c_ssize_t
表示C ssize_t datatype.

新版本3.2.

class ctypes.c_ubyte
表示C unsigned char数据类型,它解释值assmall整数。构造函数接受可选的整数初始值设定项;nooverflow检查完成.
class ctypes.c_uint
表示C unsigned int数据类型。构造函数接受anoptional整数初始化器;没有进行溢出检查。在平台上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数据类型。构造函数接受anoptional整数初始化器;没有溢出检查.
class ctypes.c_ulonglong
表示C unsigned long long数据类型。constructoraccepts一个可选的整数初始化器;没有进行溢出检查.
class ctypes.c_ushort
表示C unsigned short数据类型。构造函数接受可选的整数初始值设定项;没有溢出检查.
class ctypes.c_void_p
表示C void *类型。该值表示为整数。构造函数接受可选的整数初始化程序.
class ctypes.c_wchar
表示C wchar_t数据类型,并将该值解释为单字符unicode字符串。构造函数接受一个可选的stringinitializer,字符串的长度必须正好是一个字符.
class ctypes.c_wchar_p
表示C wchar_t *数据类型,它必须是指向以零结尾的宽字符串的指针。构造函数接受一个integeraddress或一个字符串.
class ctypes.c_bool
表示C bool数据类型(更确切地说,_Bool来自C99)。它的值可以是TrueFalse,构造函数接受任何具有真值的对象.
class ctypes.HRESULT
仅限Windows:表示HRESULTvalue,包含函数或方法调用的成功orerror信息.
class ctypes.py_object
表示C PyObject *数据类型。在没有争议的情况下调用它会创建一个NULL PyObject *指针

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

 

结构化数据类型

class ctypes.Union*args, **kw
摘要本地字节顺序的联合基类.
class ctypes.BigEndianStructure (*args, **kw)
big endian字节顺序的结构的抽象基类.
class ctypes.LittleEndianStructure*args, **kw
little endian字节顺序中结构的抽象基类.

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

class ctypes.Structure (*args, **kw)
native字节顺序结构的抽象基类.

混凝土结构必须通过继承其中一个类型来创建联合类型,并至少定义一个_fields_类变量。ctypes会创建描述符允许通过directattribute访问来读取和写入字段的s。这些是

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

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

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

可以定义_fields_类变量after定义Structure子类的类语句,这个允许创建直接或间接引用自己的数据类型:

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

然而,必须在首次使用类型之前定义_fields_类变量(创建实例,sizeof()被调用在它上面,等等)。后来分配给_fields_class变量会引起一个AttributeError.

可以定义结构类型的子类,它们继承了基类的字段和_fields_在子子类中定义,如果有的话

_pack_
一个可选的小整数,允许覆盖实例中结构字段的对齐_pack_必须已经定义_fields_已分配,否则无效.
_anonymous_
列出未命名(匿名)字段名称的可选序列._anonymous_必须在_fields_isassigned,否则它将无效.

此变量中列出的字段必须是结构或联合类型字段.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数据类型vtfields指定哪个union字段有效。自ufieldis定义为匿名字段,现在可以直接从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_.

 

数组和指针中不存在的名称创建新属性

class ctypes.Array*args
数组的抽象基类.

创建具体数组类型的推荐方法是乘以任意ctypes具有正整数的数据类型。或者,你可以子类化这个类型并定义_length__type_类变量。可以使用standardsubscript和slice访问来读取和写入阵列元素;对于切片读取,生成的对象是not本身Array.

_length_
一个正整数,指定数组中元素的数量。超出范围的下标导致IndexError。将由len().
_type_
返回指定数组中每个元素的类型.

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

class ctypes._Pointer
Private,抽象基类for pointers.

通过使用将指向的类型调用POINTER()创建混合指针类型;这是由pointer().

自动完成的。如果指针指向一个数组,可以使用标准的下标和切片访问来读取和写入其元素。指针对象没有大小,所以len()会提升TypeError。Negativesubscripts将从内存中读取before指针(如在C中)和超出范围的下标可能会因访问冲突而崩溃(如果你很幸运).

_type_
指定指向的类型
contents
将对象返回到哪个指针指向。分配给this属性会将指针更改为指向指定的对象.

评论被关闭。