ctypes ——针对python的外部函数库


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

ctypes教程

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

注意:一些代码示例引用了CTypes c_int 类型。在平台上 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系统dll类似 kernel32user32 通常导出函数的ANSI和Unicode版本。Unicode版本是用 W 附加到名称后,当使用 A 附加到名称后。Win32 GetModuleHandle 函数,它返回 module handle 对于给定的模块名,具有以下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 <module>
  File "ctypes.py", line 310, in __getitem__
    func = _StdcallFuncPtr(name, self)
AttributeError: function ordinal 0 not found
>>>

调用函数

您可以像调用其他任何Python一样调用这些函数。此示例使用 time() 函数,以秒为单位返回自Unix epoch以来的系统时间,以及 GetModuleHandleA() 函数,返回win32模块句柄。

此示例使用 NULL 指针 (None 应该用作 NULL 指针)::

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

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结构化异常处理来防止常规保护错误导致崩溃::

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

但是,有足够的方法可以将python与 ctypes 所以你还是要小心。这个 faulthandler 模块有助于调试崩溃(例如,由于错误的C库调用导致的分段错误)。

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

在继续使用其他参数类型调用函数之前,我们必须进一步了解 ctypes 数据类型。

基本数据类型

ctypes 定义许多与C原语兼容的数据类型:

ctypes类型

C类型

Python类型

c_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

__int64 or long long

int

c_ulonglong

unsigned __int64 or unsigned long long

int

c_size_t

size_t

int

c_ssize_t

ssize_t or Py_ssize_t

int

c_float

float

浮动

c_double

double

浮动

c_longdouble

long double

浮动

c_char_p

char* (NUL终止)

字节对象或 None

c_wchar_p

wchar_t* (NUL终止)

字符串或 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_pc_wchar_pc_void_p 改变了 内存位置 他们指出, 不是内容 内存块(当然不是,因为python bytes对象是不可变的)::

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

但是,您应该小心,不要将它们传递给期望指向可变内存的指针的函数。如果您需要可变内存块,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类型的Unicode字符的可变内存块 wchar_t 使用 create_unicode_buffer() 功能。

调用函数,续

注意,printf打印到真正的标准输出通道, notsys.stdout ,因此这些示例只在控制台提示下工作,而不是从内部 IDLEPythonWin ::

>>> 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_ 属性,并将其用作函数参数。当然,它必须是整数、字符串或字节中的一个:

>>> 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 属性。

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() 类方法接收传递给函数调用的python对象,它应该执行类型检查或需要执行的任何操作,以确保该对象是可接受的,然后返回对象本身、其 _as_parameter_ 属性,或者在本例中作为C函数参数传递的任何内容。同样,结果应该是整数、字符串、字节和 ctypes 实例或具有 _as_parameter_ 属性。

返回类型

默认情况下,假定函数返回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 bytes对象转换为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 属性,如果外部函数返回整数。将使用 整数 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 <module>
  File "<stdin>", line 3, in ValidHandle
OSError: [Errno 126] The specified module could not be found.
>>>

WinError 是一个调用Windows的函数 FormatMessage() 获取错误代码的字符串表示形式的API,以及 返回 一个例外。 WinError 接受可选的错误代码参数,如果不使用任何参数,则调用 GetLastError() 检索它。

请注意,通过 errcheck 属性;有关详细信息,请参阅参考手册。

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

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

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 含A 字段名 和A 字段类型 .

字段类型必须是 ctypesc_int 或任何其他派生的 ctypes 类型:结构、联合、数组、指针。

下面是一个简单的点结构示例,其中包含两个名为 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>
TypeError: too many initializers
>>>

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

这是一个rect结构,包含两个名为 左上角的低亮度 ::

>>> 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 S可以从 classes ,它们对于调试很有用,因为它们可以提供有用的信息::

>>> 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上工作,但库不能保证在一般情况下工作。带位字段的联合和结构应始终通过指针传递给函数。

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

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

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

结构和联合中的位字段

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

>>> 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() A函数 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函数收到指针时,才使用此功能,并且 know 指针实际指向数组而不是单个项。

在幕后, 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 <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 在这种情况下)可以传递给函数。C类型将应用所需的 byref() 在这种情况下自动转换。

将指针类型字段设置为 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() 接受两个参数,一个转换为或可以转换为某种指针的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
>>>

类型不完整

类型不完整 是尚未指定其成员的结构、联合或数组。在C语言中,它们是由forward声明指定的,后者在后面定义:

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_ 属性,在class语句之后::

>>> 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 允许从Python调用创建C可调用函数指针。这些有时被称为 回调函数 .

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

这个 CFUNCTYPE() factory函数使用 cdecl 调用约定。在Windows上 WINFUNCTYPE() factory函数使用 stdcall 调用约定。

调用这两个factory函数时,结果类型都是第一个参数,回调函数期望参数类型是剩余参数。

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

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

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

所以回调函数接收指向整数的指针,并且必须返回一个整数。首先我们创建 type 对于回调函数:

>>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>>

为了开始,这里有一个简单的回调,它显示了它传递的值:

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

结果:

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

现在我们可以实际比较这两个项目并返回一个有用的结果:

>>> def py_cmp_func(a, b):
...     print("py_cmp_func", a[0], b[0])
...     return a[0] - b[0]
...
>>>
>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) 
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

由于我们可以很容易地检查,我们的数组现在被排序:

>>> for i in ia: print(i, end=" ")
...
1 5 7 33 99
>>>

功能工厂可以用作装饰工厂,所以我们也可以写:

>>> @CFUNCTYPE(c_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.localnot 即使这些调用是从同一个C线程发出的,也能在不同的回调中存活。

访问从DLL导出的值

有些共享库不仅导出函数,还导出变量。python库本身的一个例子是 Py_OptimizeFlag ,一个整数设置为0、1或2,具体取决于 -O-OO 启动时给出的标志。

ctypes 可以使用 in_dll() 类型的类方法。 巨嘴鸟 是允许访问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导出的指针。

为该值引用文档:

此指针初始化为指向 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:
...     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有一个冻结的模块和一个冻结的包(由 size 成员)不是很有名,它只用于测试。试试看 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
>>>

嗯,我们当然希望最后一份声明能打印出来 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 ,没有预期的效果。

请记住,从结构、联合和数组中检索子对象不会 copy 子对象,而不是检索访问根对象的底层缓冲区的封装对象。

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

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

注解

从中实例化的对象 c_char_p 只能将其值设置为字节或整数。

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

可变大小的数据类型

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

这个 resize() 函数可用于调整现有CTypes对象的内存缓冲区大小。函数将对象作为第一个参数,请求的字节大小作为第二个参数。内存块不能小于对象类型a指定的自然内存块。 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引用

查找共享库

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

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

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

ctypes.util.find_library(name)

尝试查找库并返回路径名。 name 没有任何前缀的库名是否类似 lib 后缀 .so.dylib 或版本号(这是用于POSIX链接器选项的表单 -l )如果找不到库,则返回 None .

确切的功能取决于系统。

在Linux上, find_library() 尝试运行外部程序 (/sbin/ldconfiggccobjdumpld )查找库文件。它返回库文件的文件名。

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

在操作系统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 .

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

正在加载共享库

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

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

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

在Windows上创建 CDLL 即使DLL名称存在,实例也可能失败。当未找到加载的DLL的依赖DLL时, OSError 消息引发错误 [“[WinError 126]找不到指定的模块”。] 此错误消息不包含丢失的DLL的名称,因为Windows API不返回此信息,因此很难诊断此错误。若要解决此错误并确定找不到哪个DLL,需要使用Windows调试和跟踪工具查找依赖DLL列表并确定找不到哪个DLL。

参见

Microsoft DUMPBIN tool --查找DLL依赖项的工具。

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

仅限Windows:此类的实例表示加载的共享库,这些库中的函数使用 stdcall 调用约定,并假定返回特定于Windows的 HRESULT 代码。 HRESULT 值包含指定函数调用是失败还是成功的信息,以及其他错误代码。如果返回值指示故障,则 OSError 自动引发。

在 3.3 版更改: WindowsError 曾经被引发过。

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

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

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

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

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

此类的实例行为如下 CDLL 实例,除了python gil是 not 在函数调用期间以及函数执行之后释放,将检查python错误标志。如果设置了错误标志,则会引发python异常。

因此,这只对直接调用python c api函数有用。

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

这个 mode 参数可用于指定库的加载方式。有关详细信息,请参阅 dlopen(3) 手册页。在Windows上, mode 被忽略。在posix系统上,rtld_现在总是被添加的,并且是不可配置的。

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

函数 ctypes.get_errno() 返回ctypes private copy的值和函数 ctypes.set_errno() 将CTypes私有副本更改为新值并返回前一个值。

这个 use_last_error 参数设置为true时,对由 GetLastError()SetLastError() Windows API函数; ctypes.get_last_error()ctypes.set_last_error() 用于请求和更改Windows错误代码的CTypes私有副本。

这个 温德模式 参数在Windows上用于指定库的加载方式(因为 mode 被忽略)。它采用对win32 api有效的任何值 LoadLibraryEx 标志参数。省略时,默认情况下使用导致最安全的dll加载的标志,以避免诸如dll劫持之类的问题。将完整路径传递到dll是确保加载正确的库和依赖项的最安全的方法。

在 3.8 版更改: 补充 温德模式 参数。

ctypes.RTLD_GLOBAL

使用的标志 mode 参数。在没有此标志的平台上,它被定义为整数零。

ctypes.RTLD_LOCAL

使用的标志 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

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

PyDLL._handle

用于访问库的系统句柄。

PyDLL._name

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

共享库也可以通过使用其中一个预制构件加载,这是 LibraryLoader 通过调用 LoadLibrary() 方法,或通过将库作为加载程序实例的属性检索。

class ctypes.LibraryLoader(dlltype)

加载共享库的类。 DLL型 应该是 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 属性来使用这些函数。

提出一个 auditing event ctypes.dlopen 带着论证 name .

提出一个 auditing event ctypes.dlsym 带着论据 libraryname .

提出一个 auditing event ctypes.dlsym/handle 带着论据 handlename .

外部功能

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

class ctypes._FuncPtr

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

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

可以通过指定外部函数对象的特殊属性来自定义此行为。

restype

指定CTypes类型以指定外部函数的结果类型。使用 None 对于 void ,一个不返回任何内容的函数。

可以分配一个不是ctypes类型的可调用python对象,在这种情况下,假定函数返回一个c int ,并且将使用此整数调用可调用文件,从而允许进一步处理或检查错误。不推荐使用此方法,为了更灵活的后期处理或错误检查,请将CTypes数据类型用作 restype 并将可调用分配给 errcheck 属性。

argtypes

指定CTypes类型的元组以指定函数接受的参数类型。函数使用 stdcall 调用约定只能使用与此元组长度相同的参数数目来调用;使用C调用约定的函数也接受其他未指定的参数。

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

新:现在可以将项目放入不是CTypes类型的argtype中,但每个项目必须具有 from_param() 方法,返回可用作参数的值(integer、string、ctypes实例)。这允许定义可以将自定义对象调整为函数参数的适配器。

errcheck

为该属性分配一个python函数或另一个可调用的函数。将使用三个或更多参数调用Callable:

callable(result, func, arguments)

结果 是外部函数返回的值,如 restype 属性。

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

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

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

exception ctypes.ArgumentError

当外部函数调用无法转换某个传递的参数时,会引发此异常。

提出一个 auditing event ctypes.seh_exception 带着论证 code .

提出一个 auditing event ctypes.call_function 带着论据 func_pointerarguments .

函数原型

也可以通过实例化函数原型来创建外部函数。函数原型与C中的函数原型相似;它们描述一个函数(返回类型、参数类型、调用约定),而不定义实现。factory函数必须使用所需的结果类型和函数的参数类型调用,并且可以用作修饰工厂,因此,可以通过 @wrapper 语法。参见 回调函数 举个例子。

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

返回的函数原型创建使用标准C调用约定的函数。该函数将在调用期间释放gil。如果 use_errno 设置为true时,系统的CTypes私有副本 errno 变量与实数交换 errno 调用前后的值; use_last_error 对Windows错误代码执行相同的操作。

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

仅限Windows:返回的函数原型创建使用 stdcall 调用约定,但在Windows CE上除外 WINFUNCTYPE() 是一样的 CFUNCTYPE() . 该函数将在调用期间释放gil。 use_errnouse_last_error 与上述含义相同。

ctypes.PYFUNCTYPE(restype, *argtypes)

返回的函数原型创建使用Python调用约定的函数。功能将 not 在通话中释放gil。

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

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 是进入虚拟函数表的索引,一个小的非负整数。 name 是COM方法的名称。 iid 是指向在扩展错误报告中使用的接口标识符的可选指针。

COM方法使用特殊的调用约定:除了在 argtypes 元组。

可选的 副旗标 参数创建的外部函数封装器的功能比上面描述的功能多得多。

副旗标 必须是与相同长度的元组 argtypes .

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

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

1

指定函数的输入参数。

2

输出参数。foreign函数填充一个值。

4

默认为整数零的输入参数。

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

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

这个例子演示了如何封装窗口 MessageBoxW 函数,以便它支持默认参数和命名参数。Windows头文件中的C声明如下:

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

这是封装纸 ctypes ::

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

这个 MessageBox 现在可以通过以下方式调用外部函数:

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

第二个示例演示输出参数。Win32 GetWindowRect 函数通过将指定窗口的维度复制到 RECT 调用方必须提供的结构。这是C声明:

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

这是封装纸 ctypes ::

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

带有输出参数的函数如果只有一个参数,将自动返回输出参数值;如果有多个参数,则返回包含输出参数值的元组,因此当调用getWindowRect函数时,它现在返回一个rect实例。

输出参数可以与 errcheck 协议做进一步的输出处理和错误检查。Win32 GetWindowRect API函数返回 BOOL 指示成功或失败,以便此函数可以进行错误检查,并在API调用失败时引发异常::

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

如果 errcheck 函数返回它接收到的参数元组不变, ctypes 继续对输出参数进行正常处理。如果要返回窗口坐标的元组而不是 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.addressof(obj)

以整数形式返回内存缓冲区的地址。 obj 必须是CTypes类型的实例。

提出一个 auditing event ctypes.addressof 带着论证 obj .

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中的强制转换运算符类似。它返回 type 指向同一个内存块 obj . type 必须是指针类型,并且 obj 必须是可以解释为指针的对象。

ctypes.create_string_buffer(init_or_size, size=None)

此函数创建可变字符缓冲区。返回的对象是 c_char .

init_or_size 必须是指定数组大小的整数,或者是用于初始化数组项的bytes对象。

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

提出一个 auditing event ctypes.create_string_buffer 带着论据 initsize .

ctypes.create_unicode_buffer(init_or_size, size=None)

此函数创建可变的Unicode字符缓冲区。返回的对象是 c_wchar .

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

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

提出一个 auditing event ctypes.create_unicode_buffer 带着论据 initsize .

ctypes.DllCanUnloadNow()

仅限Windows:此函数是一个钩子,允许使用CTypes实现进程内COM服务器。它是从dllcanUnloadNow函数调用的,该函数用于扩展dll导出。

ctypes.DllGetClassObject()

仅限Windows:此函数是一个钩子,允许使用CTypes实现进程内COM服务器。它是从dllgetClassObject函数调用的, _ctypes 扩展dll导出。

ctypes.util.find_library(name)

尝试查找库并返回路径名。 name 没有任何前缀的库名是否类似 lib 后缀 .so.dylib 或版本号(这是用于POSIX链接器选项的表单 -l )如果找不到库,则返回 None .

确切的功能取决于系统。

ctypes.util.find_msvcrt()

仅限Windows:返回python和扩展模块使用的vc运行库的文件名。如果无法确定库的名称, None 返回。

例如,如果需要释放内存,则由一个扩展模块分配,并调用 free(void *) ,在分配内存的同一个库中使用该函数很重要。

ctypes.FormatError([code])

仅限Windows:返回错误代码的文本描述 code . 如果未指定错误代码,则通过调用Windows API函数getLastError来使用最后一个错误代码。

ctypes.GetLastError()

仅限Windows:返回调用线程中Windows设置的最后一个错误代码。此函数调用Windows GetLastError() 函数,它不会返回错误代码的CTypes私有副本。

ctypes.get_errno()

返回系统的CTypes私有副本的当前值 errno 调用线程中的变量。

提出一个 auditing event ctypes.get_errno 没有参数。

ctypes.get_last_error()

仅限Windows:返回系统的CTypes私有副本的当前值 LastError 调用线程中的变量。

提出一个 auditing event ctypes.get_last_error 没有参数。

ctypes.memmove(dst, src, count)

与标准的c memmove库函数相同:copies 计数 字节从 srcdst . dstsrc 必须是可转换为指针的整数或CTypes实例。

ctypes.memset(dst, c, count)

与标准的c memset library函数相同:填充地址处的内存块 dst 具有 计数 价值字节 c . dst 必须是指定地址或CTypes实例的整数。

ctypes.POINTER(type)

此factory函数创建并返回新的CTypes指针类型。指针类型在内部进行缓存和重用,因此重复调用此函数很便宜。 type 必须是CTypes类型。

ctypes.pointer(obj)

此函数创建一个新的指针实例,指向 obj . 返回的对象类型为 POINTER(type(obj)) .

注意:如果只想将指向对象的指针传递给外部函数调用,则应使用 byref(obj) 速度快得多。

ctypes.resize(obj, size)

此函数用于调整 obj ,它必须是CTypes类型的实例。不能使缓冲区小于对象类型的本机大小,如 sizeof(type(obj)) ,但可以放大缓冲区。

ctypes.set_errno(value)

设置系统的CTypes私有副本的当前值 errno 调用线程中的变量 value 并返回上一个值。

提出一个 auditing event ctypes.set_errno 带着论证 errno .

ctypes.set_last_error(value)

仅限Windows:设置系统的CTypes私有副本的当前值 LastError 调用线程中的变量 value 并返回上一个值。

提出一个 auditing event ctypes.set_last_error 带着论证 error .

ctypes.sizeof(obj_or_type)

返回CTypes类型或实例内存缓冲区的字节大小。和C一样吗 sizeof 操作员。

ctypes.string_at(address, size=- 1)

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

提出一个 auditing event ctypes.string_at 带着论据 addresssize .

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

仅限Windows:此函数可能是CTypes中命名最差的函数。它创建了一个OSError实例。如果 code 未指定, GetLastError 调用以确定错误代码。如果 德克思 未指定, FormatError() 调用以获取错误的文本描述。

在 3.3 版更改: 的实例 WindowsError 以前是要创建的。

ctypes.wstring_at(address, size=- 1)

此函数返回从内存地址开始的宽字符串 地址 作为字符串。如果 size 如果指定,它将用作字符串的字符数,否则假定该字符串以零结尾。

提出一个 auditing event ctypes.wstring_at 带着论据 addresssize .

数据类型

class ctypes._CData

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

ctypes数据类型的常用方法,这些都是类方法(准确地说,它们是 metaclass ):

from_buffer(source[, offset])

此方法返回共享 source 对象。这个 source 对象必须支持可写缓冲区接口。可选的 抵消 参数以字节为单位指定到源缓冲区的偏移量;默认值为零。如果源缓冲区不够大, ValueError 提高了。

提出一个 auditing event ctypes.cdata/buffer 带着论据 pointersizeoffset .

from_buffer_copy(source[, offset])

此方法创建CTypes实例,从 source 必须可读的对象缓冲区。可选的 抵消 参数以字节为单位指定到源缓冲区的偏移量;默认值为零。如果源缓冲区不够大, ValueError 提高了。

提出一个 auditing event ctypes.cdata/buffer 带着论据 pointersizeoffset .

from_address(address)

此方法使用由指定的内存返回CTypes类型实例 地址 必须是整数。

提出一个 auditing event ctypes.cdata 带着论证 address .

from_param(obj)

这种方法适用于 obj 到CTypes类型。当类型存在于外部函数的 argtypes 元组;它必须返回一个可以用作函数调用参数的对象。

所有CTypes数据类型都具有此ClassMethod的默认实现,该方法通常返回 obj 如果这是该类型的实例。有些类型也接受其他对象。

in_dll(library, name)

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

ctypes数据类型的公共实例变量:

_b_base_

有时,CTypes数据实例不拥有所包含的内存块,而是共享基本对象的部分内存块。这个 _b_base_ 只读成员是拥有内存块的根CTypes对象。

_b_needsfree_

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

_objects

这个成员是 None 或者包含需要保持活动以便保持内存块内容有效的python对象的字典。此对象只公开用于调试;永远不要修改此字典的内容。

基本数据类型

class ctypes._SimpleCData

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

实例具有单个属性:

value

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

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

当基本数据类型作为外部函数调用结果返回时,或者,例如,通过检索结构字段成员或数组项,基本数据类型将透明地转换为本机Python类型。换句话说,如果一个外部函数 restype 属于 c_char_p ,您将始终收到一个python bytes对象, notc_char_p 实例。

基本数据类型的子类有 not 继承此行为。所以,如果一个外国函数 restype 是的子类 c_void_p ,您将从函数调用中接收此子类的实例。当然,您可以通过访问 value 属性。

这些是基本CTypes数据类型:

class ctypes.c_byte

表示C signed char 数据类型,并将值解释为小整数。构造函数接受可选的整数初始值设定项;未执行溢出检查。

class ctypes.c_char

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

class ctypes.c_char_p

表示C char* 当数据类型指向以零结尾的字符串时。对于也可以指向二进制数据的一般字符指针, POINTER(c_char) 必须使用。构造函数接受整数地址或字节对象。

class ctypes.c_double

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

class ctypes.c_longdouble

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

class ctypes.c_float

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

class ctypes.c_int

表示C signed int 数据类型。构造函数接受可选的整数初始值设定项;未执行溢出检查。在平台上 sizeof(int) == sizeof(long) 它是 c_long .

class ctypes.c_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 数据类型。

3.2 新版功能.

class ctypes.c_ubyte

表示C unsigned char 数据类型,它将值解释为小整数。构造函数接受可选的整数初始值设定项;未执行溢出检查。

class ctypes.c_uint

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

class ctypes.c_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 ,构造函数接受任何具有真值的对象。

class ctypes.HRESULT

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

class ctypes.py_object

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

这个 ctypes.wintypes 例如,模块提供了许多其他特定于Windows的数据类型 HWNDWPARAMDWORD . 一些有用的结构,比如 MSGRECT 也被定义。

结构化数据类型

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

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

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

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

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

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

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

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

中结构的抽象基类 本地的 字节顺序。

具体结构和联合类型必须通过子类化其中一个类型来创建,并且至少定义 _fields_ 类变量。 ctypes 将创造 descriptor 允许通过直接属性访问读取和写入字段的。这些是

_fields_

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

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

字段名在一个结构或联合中必须是唯一的。未选中此项,重复名称时只能访问一个字段。

可以定义 _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 字段定义为匿名字段,现在可以直接从typedsc实例访问成员。 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_ 类变量。可以使用标准下标和切片访问来读取和写入数组元素;对于切片读取,生成的对象是 not 本身是一个 Array .

_length_

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

_type_

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

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

class ctypes._Pointer

指针的私有抽象基类。

具体的指针类型是通过调用 POINTER() 使用将被指向的类型;这是由 pointer() .

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

_type_

指定指向的类型。

contents

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