16.16. ctypes — 用于 Python 的外来函数库


ctypes 是用于 Python 的外部函数库。它提供 C 兼容数据类型,且允许调用 DLL 或共享库中的函数。可以使用它以将这些库包裹在纯 Python 中。

16.16.1. ctypes 教程

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

注意:某些代码样本引用 ctypes c_int 类。当平台 sizeof(long) == sizeof(int) ,它是别名化的 c_long 。因此,不应该感到困惑若 c_long 被打印若期望 c_int — 它们实际上是同一类型。

ctypes 导出 cdll ,Windows windll and oledll 对象,为加载 DLL (动态链接库)。

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

3.3 版改变: 使用 Windows 错误引发 WindowsError ,现在是别名化的 OSError .

这里是一些 Windows 范例。注意: msvcrt 是包含大多数标准 C 函数的 MS 标准 C 库,且使用 cdecl 调用约定:

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

Windows 追加通常 .dll 文件后缀,自动地。

注意

访问标准 C 库透过 cdll.msvcrt 将使用可能不兼容 Python 所用的过时版本库。若可能,使用本机 Python 功能,或导入并使用 msvcrt 模块。

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

16.16.1.2. 访问加载 DLL 的函数

函数作为 DLL 对象的属性被访问:

>>> from ctypes import *
>>> libc.printf
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.GetModuleHandleA)
<_FuncPtr object at 0x...>
>>> print(windll.kernel32.MyOwnFunction)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ctypes.py", line 239, in __getattr__
    func = _StdcallFuncPtr(name, self)
AttributeError: function 'MyOwnFunction' not found
>>>
					

注意:win32 系统 DLL 像 kernel32 and user32 经常导出函数的 ANSI 和 UNICODE 版本。导出的 UNICODE 版本将 W 追加到名称,而导出的 ANSI 版本将 A 追加到名称。win32 GetModuleHandle 函数,其返回 module handle 为给定模块名,拥有以下 C 原型,且宏用于曝光它们之一因为 GetModuleHandle 取决于 UNICODE 是否被定义:

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

windll 不会试着通过 Magic (魔法) 选择它们之一,访问所需版本必须通过指定 GetModuleHandleA or GetModuleHandleW 明确,然后分别采用字节或字符串对象调用它。

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

16.16.1.3. 调用函数

可以像调用任何其它 Python 回调一样,调用这些函数。此范例使用 time() 函数,返回从 Unix 纪元起的系统时间 (以秒为单位),而 GetModuleHandleA() 函数,其返回 win32 模块句柄。

此范例调用 2 函数采用 NULL 指针 ( None 应该被用作 NULL 指针):

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

ctypes tries to protect you from calling functions with the wrong number of arguments or the wrong calling convention. Unfortunately this only works on Windows. It does this by examining the stack after the function returns, so although an error is raised the function has been called:

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

The same exception is raised when you call an 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 * or wchar_t * )。Python 整数被传递作为平台默认 C int 类型。它们的值被屏蔽以拟合成 C 类型。

在继续采用其它参数类型调用函数之前,必须了解更多有关 ctypes 数据类型。

16.16.1.4. 基础数据类型

ctypes 定义了许多首要 C 兼容数据类型:

ctypes 类型 C 类型 Python 类型
c_bool _Bool bool (1)
c_char char 1 字符 bytes 对象
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 float
c_double double float
c_longdouble long double float
c_char_p char * (Null 结尾) bytes 对象或 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_p ,和 c_void_p 改变 内存定位 指向, 不是内容 的内存块 (当然不是,因为 Python 字节对象是不可变的):

>>> 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 特性;若希望以 NULL 终止字符串方式访问它,使用 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'
>>>
					

The create_string_buffer() 函数替换 c_buffer() 函数 (仍然可用作别名),及 c_string() 函数来自早期发行的 ctypes。要创建可变内存块包含 Unicode 字符 C 类型 wchar_t 使用 create_unicode_buffer() 函数。

16.16.1.5. 调用函数,继续

注意:printf 打印到真正的标准输出通道, not to sys.stdout ,因此,这些范例将仅工作于控制台提示,而不是 IDLE or 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
>>>
					

如前所述,除整数、字符串及 bytes 对象外,所有 Python 类型都必须包裹在其相应 ctypes 类型,以便可以将它们转换成要求的 C 数据类型:

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

16.16.1.6. 调用函数采用您自己的自定义数据类型

也可以定制 ctypes 自变量转换,以允许将自己的类实例用作函数自变量。 ctypes 寻找 _as_parameter_ :属性并将其用作函数自变量。当然,它必须是整数、字符串或字节之一:

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

若不想把实例数据存储在 _as_parameter_ 实例变量,可以定义 property 使属性在请求时可用。

16.16.1.7. 指定要求的自变量类型 (函数原型)

指定从 DLL 导出函数所需的自变量类型是可能的,通过设置 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_ 属性。

16.16.1.8. 返回类型

默认情况下,假定函数返回 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 属性,并将第 2 自变量从单个字符 Python 字节对象转换成 C 字符:

>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
  File "<stdin>", line 1, in <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 属性,若外来函数返回整数。可调用将被调用采用 integer 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 属性;见参考手册了解细节。

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

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

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

16.16.1.10. Structure 和 Union

Structure 和 Union 必须派生自 Structure and Union 基类的定义在 ctypes 模块。每个子类必须定义 _fields_ 属性。 _fields_ 必须是列表 2-tuples ,包含 field name field type .

字段类型必须是 ctypes 类型像 c_int ,或任何其它派生 ctypes 类:Structure、Union、Array、Pointer。

这里是简单 POINT 结构范例,其包含 2 个整数,名为 x and y ,并展示如何在构造函数中初始化结构:

>>> 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 结构,包含 2 POINT 名为 upperleft and lowerright :

>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print(rc.upperleft.x, rc.upperleft.y)
0 5
>>> print(rc.lowerright.x, rc.lowerright.y)
0 0
>>>
					

还可以按几种方式在构造函数中,初始化嵌套结构:

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

字段 descriptor 可以被检索从 class ,它们对调试很有用,因为它们可以提供有用信息:

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

警告

ctypes 不支持通过值将将具有位字段的 Union 或 Structure 传递给函数。虽然这可以工作于 32 位 x86,但库不保证它能工作于一般情况。应始终通过指针,将具有位字段的 Union 和 Structure 传递给函数。

16.16.1.11. Structure/Union 对齐和字节序

By default, Structure and Union fields are aligned in the same way the C compiler does it. It is possible to override this behavior be specifying a _pack_ 类属性在子类定义中。必须将这设为正整数,并指定字段的最大对齐方式。这是为什么 #pragma pack(n) 在 MSVC 中也如此。

ctypes 为 Structure 和 Union 使用本机字节序。构建具有非本机字节序的 Structure,可以使用某一 BigEndianStructure , LittleEndianStructure , BigEndianUnion ,和 LittleEndianUnion 基类。这些类无法包含指针字段。

16.16.1.12. 在 Structure 和 Union 中的位字段

创建包含位字段的 Structure 和 Union 是可能的。位字段只能是整数字段,位宽度的指定如第 3 项在 _fields_ 元组:

>>> class Int(Structure):
...     _fields_ = [("first_16", c_int, 16),
...                 ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
<Field type=c_long, ofs=0:0, bits=16>
>>> print(Int.second_16)
<Field type=c_long, ofs=0:16, bits=16>
>>>
					

16.16.1.13. 数组

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

创建数组类型的推荐方式,是数据类型乘以正整数:

TenPointsArrayType = POINT * 10
					

这里是稍微人工的数据类型范例,是除其它东西外包含 4 POINT 的结构。

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class MyStruct(Structure):
...     _fields_ = [("a", c_int),
...                 ("b", c_float),
...                 ("point_array", POINT * 4)]
>>>
>>> print(len(MyStruct().point_array))
4
>>>
					

实例的创建是按通常方式,通过调用类:

arr = TenPointsArrayType()
for pt in arr:
    print(pt.x, pt.y)
					

以上代码打印一系列 0 0 行,因为数组内容被初始化为 0。

还可以指定正确类型的初始化器:

>>> from ctypes import *
>>> TenIntegers = c_int * 10
>>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> print(ii)
<c_long_Array_10 object at 0x...>
>>> for i in ii: print(i, end=" ")
...
1 2 3 4 5 6 7 8 9 10
>>>
					

16.16.1.14. 指针

指针实例的创建是通过调用 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() 函数相比只需创建指针实例会做更多,它必须创建指针 类型 首先。做到这是采用 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
>>>
					

16.16.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) ) 在 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() 函数很得心应手。

The cast() 函数可以用于将 ctypes 实例铸造成指向不同 ctypes 数据类型的指针。 cast() 接受 2 参数,是 (或) 可以被转换成某种类型指针的 ctypes 对象,和 ctypes 指针类型。它返回第 2 自变量实例,引用如第 1 自变量的相同内存块:

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

16.16.1.16. 不完整类型

不完整类型 是 Structure、Union 或数组,尚未指定其成员。在 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))]
>>>
					

Lets try it. We create two instances of cell ,并让它们指向彼此,最后沿指针链来几次:

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

16.16.1.17. 回调函数

ctypes 允许从 Python 可调用创建 C 可调用函数指针。有时称这些为 回调函数 .

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

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

采用结果类型作第 1 自变量调用这 2 工厂函数,而回调函数期望自变量类型如剩余自变量。

这里将呈现的范例使用标准 C 库 qsort() 函数,用于在回调函数的帮助下排序项。 qsort() 将用于对整数数组排序:

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

qsort() 必须被调用采用指向要排序的数据指针、数据数组中的项数、每项的大小及比较函数指针 (回调)。然后采用 2 个指向项的指针调用回调函数,且它必须返回负整数若第 1 项小于第 2 项,0 若它们相等,否则为正整数。

因此,回调函数接收指向整数的指针,且必须返回整数。首先,我们创建 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
>>>
					

现在,我们可以实际比较 2 项并返回有用结果:

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

由于可以轻松校验,我们现在排序数组:

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

注意

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

另请注意,若回调函数是在 Python 控制之外创建的线程中调用的 (如,通过调用回调的外部代码),ctypes 会创建新的虚设 Python 线程当每次援引时。此行为对于大多数用途是正确的,但意味着值存储采用 threading.local will not 跨不同回调幸存,甚至在这些调用由同一 C 线程做出时。

16.16.1.18. 访问 DLL 导出值

某些共享库不仅导出函数,它们还导出变量。Python 库本身的范例是 Py_OptimizeFlag ,整数设为 0、1、或 2,从属 -O or -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) if -OO 有指定。

扩展范例还演示使用指针访问 PyImport_FrozenModules 指针导出通过 Python。

该值的引用文档:

此指针被初始化成指向数组为 struct _frozen 记录,被终止当其每一成员都是 NULL or zero. When a frozen module is imported, it is searched in this table. Third-party code could play tricks with this to provide a dynamically created collection of frozen modules.

因此,操纵此指针甚至可以证明是有用的。为限制范例大小,只展示如何读取此表采用 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 records, we can iterate over it, but we just have to make sure that our loop terminates, because pointers have no size. Sooner or later it would probably crash with an access violation or whatever, so it’s better to break out of the loop when we hit the NULL entry:

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

The fact that standard Python has a frozen module and a frozen package (indicated by the negative size member) is not well known, it is only used for testing. Try it out with import __hello__ 例如。

16.16.1.19. Surprises

有一些边缘在 ctypes 您可能期待一些事情,而不是实际发生什么。

考虑以下范例:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = ("x", c_int), ("y", c_int)
...
>>> class RECT(Structure):
...     _fields_ = ("a", POINT), ("b", POINT)
...
>>> p1 = POINT(1, 2)
>>> p2 = POINT(3, 4)
>>> rc = RECT(p1, p2)
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
1 2 3 4
>>> # now swap the two points
>>> rc.a, rc.b = rc.b, rc.a
>>> print(rc.a.x, rc.a.y, rc.b.x, rc.b.y)
3 4 3 4
>>>
					

嗯,我们肯定期望最后语句打印 3 4 1 2 。发生了什么? 这里的步骤用于 rc.a, rc.b = rc.b, rc.a 行见上文:

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

注意, temp0 and temp1 对象仍使用内部缓冲 rc 对象见上文。因此执行 rc.a = temp0 拷贝缓冲内容 temp0 into rc 的缓冲。这依次改变内容为 temp1 。因此,最后赋值 rc.b = temp1 ,没有期望效果。

请记住,从 Structure、Union 及数组检索子对象不 copy 子对象,相反,它检索访问根对象底层缓冲的包装器对象。

Another example that may behave different from what one would expect is this:

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

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

16.16.1.20. 可变大小的数据类型

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

The resize() 函数可以用于重置现有 ctypes 对象的内存缓冲大小。函数接受对象作为第 1 自变量,和请求以字节为单位的大小作为第 2 自变量。内存块不可以比由对象类型指定的自然内存块更小, 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 的动态性质,并在已知要求大小后 (重新) 定义数据类型,根据具体情况而定。

16.16.2. ctypes 引用

16.16.2.1. 查找共享库

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

目地对于 find_library() function is to locate a library in a way similar to what the compiler does (on platforms with several versions of a shared library the most recent should be loaded), while the ctypes library loaders act like when a program is run, and call the runtime loader directly.

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

ctypes.util. find_library ( name )

试着查找库并返回路径名。 name 是库名称没有任何前缀像 lib ,后缀像 .so , .dylib 或版本号 (使用这种形式为 POSIX 链接器选项 -l )。若找不到库,返回 None .

确切功能从属系统。

在 Linux, find_library() 试着运行外部程序 ( /sbin/ldconfig , gcc ,和 objdump ) to find the library file. It returns the filename of the library file. Here are some examples:

>>> 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 ,它 may 更好在开发时确定共享库名称,并将这硬编码到包裹器模块,而不是使用 find_library() 以在运行时定位库。

16.16.2.2. 加载共享库

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

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

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

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

仅 Windows:此类的实例表示加载的共享库,这些库中的函数使用 stdcall 调用约定,并假定返回 Windows 特定 HRESULT 代码。 HRESULT 值包含函数调用失败或成功的指定信息,连同额外错误代码一起。若返回值信号故障, OSError 被自动引发。

3.3 版改变: WindowsError 用于被引发。

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

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

Windows CE 仅使用标准调用约定,为方便起见 WinDLL and OleDLL 在此平台使用标准调用约定。

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

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

此类的实例行为像 CDLL 实例,除了 Python GIL not 被释放在函数调用期间,并在函数执行后校验 Python 错误标志。若有设置错误标志,引发 Python 异常。

因此,这只对直接调用 Python C API 函数有用。

可以通过采用至少一自变量 (共享库的路径名) 调用它们以实例化所有这些类。若拥有已加载共享库的现有句柄,可以将它它传递作为 handle 命名参数,否则底层平台 dlopen or LoadLibrary 函数被用以将库加载到进程,并获取其句柄。

The mode 参数可以用于指定如何加载库。有关细节,翻阅 dlopen(3) 手册页。在 Windows, mode 被忽略。在 POSIX 系统,始终添加 RTLD_NOW,且不可配置。

The use_errno 参数,当设为 True 时启用 ctypes 机制允许访问系统 errno 错误编号以安全方式。 ctypes 维护线程本地副本对于系统 errno 变量;若调用外部创建函数采用 use_errno=True 那么 errno 值在函数调用之前会与 ctypes 私有副本交换,同样立即发生在函数调用之后。

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

The use_last_error 参数,当设为 True 时启用相同机制为 Windows 错误代码管理通过 GetLastError() and SetLastError() Windows API 函数; ctypes.get_last_error() and ctypes.set_last_error() 用于请求和更改 Windows 错误代码的 ctypes 私有副本。

ctypes. RTLD_GLOBAL

标志能用作 mode 参数。在此标志不可用的平台,它被定义为整数 0。

ctypes. RTLD_LOCAL

标志能用作 mode 参数。在此不可用的平台,它如同 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 应为某一 CDLL , PyDLL , WinDLL ,或 OleDLL 类型。

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

LoadLibrary ( name )

将共享库加载到进程中并返回它。此方法始终返回库的新实例。

这些预制库加载程序是可用的:

ctypes. cdll

创建 CDLL 实例。

ctypes. windll

仅 Windows:创建 WinDLL 实例。

ctypes. oledll

仅 Windows:创建 OleDLL 实例。

ctypes. pydll

创建 PyDLL 实例。

为直接访问 C Python API,随时可以使用 Python 共享库对象:

ctypes. pythonapi

实例化的 PyDLL 以属性形式暴露 Python C API 函数。注意,假定所有这些函数返回 C int ,当然始终不是事实,因此必须赋值正确 restype 属性以使用这些函数。

16.16.2.3. 外来函数

如之前章节解释,外部函数可以作为加载共享库的属性访问。默认情况下,以这种方式创建的函数对象接受任意数量的自变量,接受任何 ctypes 数据实例作为自变量,并返回由库加载器指定的默认结果类型。它们是私有类的实例:

class ctypes. _FuncPtr

用于 C 可调用外来函数的基类。

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

通过赋值外部函数对象的特殊属性,可以定制此行为。

restype

将 ctypes 类型赋值给外来函数的指定结果类型。使用 None for void ,什么都不返回的函数。

赋值非 ctypes 类型的可调用 Python 对象是可能的,在这种情况下,假定函数返回 C int ,且可调用对象将以此整数被调用,允许进一步处理 (或错误校验)。使用这被弃用,对于更灵活的后期处理 (或错误校验),使用 ctypes 数据类型作为 restype 并把可调用赋值给 errcheck 属性。

argtypes

将 ctypes 类型的元组赋值给函数接受的指定自变量类型。函数使用 stdcall 调用约定才可被调用采用如此元组长度相同自变量数;使用 C 调用约定的函数还接受额外、未指定自变量。

当调用外来函数时,每个实际自变量被传递给 from_param() 类方法对于项在 argtypes 元组,此方法允许将实际自变量适配成外部函数接受的对象。例如, c_char_p 项在 argtypes 元组将使用 ctypes 转换规则,将传递作为自变量的字符串转换成 bytes 对象。

新增:现在将项放入非 ctypes 类型 argtypes 是可能的,但每项必须拥有 from_param() 方法返回的值可用作自变量 (整数。字符串、 ctypes 实例)。这允许定义适配器,可以将自定义对象适配成函数参数。

errcheck

将 Python 函数或另一可调用赋值给此属性。可调用会被调用采用 3 个或更多自变量:

callable ( result , func , arguments )

result 是外函数所返回的,如指定通过 restype 属性。

func 是外来函数对象本身,这允许重用相同可调用对象以校验 (或后期处理) 几个函数的结果。

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

此函数返回的对象是外来函数调用所返回的,但它还可以校验结果值,并引发异常若外来函数调用失败。

exception ctypes. ArgumentError

此异常被引发当外来函数调用无法转换传递的自变量之一时。

16.16.2.4. 函数原型

Foreign functions can also be created by instantiating function prototypes. Function prototypes are similar to function prototypes in C; they describe a function (return type, argument types, calling convention) without defining an implementation. The factory functions must be called with the desired result type and the argument types of the function.

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_errno and use_last_error 的含义同上。

ctypes. PYFUNCTYPE ( restype , *argtypes )

返回的函数原型,创建使用Python 调用约定的函数。函数会 not 释放 GIL 在调用期间。

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

prototype ( address )

返回在指定地址的外来函数,地址必须为整数。

prototype ( callable )

创建 C 可调用函数 (回调函数) 从 Python callable .

prototype ( func_spec [ , paramflags ] )

返回由共享库导出的外来函数。 func_spec 必须是 2 元素元组 (name_or_ordinal, library) 。第 1 项是以字符串形式导出的函数名称,或是以小整数形式导出的函数序数。第 2 项是共享库实例。

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

返回将调用 COM 方法的外来函数。 vtbl_index 是虚函数表中的索引,非负小整数。 name 是 COM (组件对象模型) 方法的名称。 iid 是指向扩展错误报告中所用接口标识符的可选指针。

COM 方法使用特殊的调用约定:它们要求指向 COM (组件对象模型) 接口的指针作为第 1 自变量,除了那些指定参数在 argtypes 元组。

可选 paramflags 参数创建的外来函数包装器,具有比上文描述特征更多的功能。

paramflags 必须是元组,长度如同 argtypes .

此元组中的各项包含关于参数的进一步信息,它必须是包含 1 项、2 项或 3 项的元组。

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

1
指定函数的输入参数。
2
输出参数。外来函数的值填充。
4
默认为整数 0 的输入参数。

可选第 2 项是字符串形式的参数名称。若这有指定,可以采用命名参数调用外来函数。

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

此范例演示如何包裹 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)
						

The MessageBox 外来函数现在可以按这些方式调用:

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

第 2 个范例演示输出参数。win32 GetWindowRect 函数检索指定窗口的维度通过把它们拷贝到 RECT 结构调用者必须提供。这里是 C 声明:

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

在此包裹采用 ctypes :

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

带输出参数的函数将自动返回输出参数值,若只有一个输出参数值 (或元组包含多个输出参数值),因此 GetWindowRect 函数现在返回 RECT 实例当调用时。

可以组合输出参数与 errcheck 协议以进一步处理输出和校验错误。win32 GetWindowRect API 函数返回 BOOL 以发出成功 (或失败) 信号,所以此函数可以做错误校验,并引发异常当 API 调用失败时:

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

errcheck 函数不变返回它收到的自变量元组, ctypes 会继续正常对输出参数做处理。若想要返回窗口坐标元组而不是 RECT 实例,可以在函数中检索字段而不是返回它们,正常处理将不再发生:

>>> def errcheck(result, func, args):
...     if not result:
...         raise WinError()
...     rc = args[1]
...     return rc.left, rc.top, rc.bottom, rc.right
...
>>> GetWindowRect.errcheck = errcheck
>>>
						

16.16.2.5. 实用函数

ctypes. addressof ( obj )

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

ctypes. alignment ( obj_or_type )

返回 ctypes 类型的对齐要求。 obj_or_type 必须是 ctypes 类型或实例。

ctypes. byref ( obj [ , offset ] )

返回的轻量指针指向 obj ,必须是 ctypes 类型的实例。 offset 默认为 0,且必须是将被添加到内部指针值的整数。

byref(obj, offset) 相当于此 C 代码:

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

返回对象只可用作外来函数的调用参数。其行为类似于 pointer(obj) ,但构造要快得多。

ctypes. cast ( obj , type )

此函数类似于 C cast 运算符。它返回新实例化的 type ,指向同一内存块如 obj . type 必须是指针类型,且 obj 必须是可以被解释为指针的对象。

ctypes. create_string_buffer ( init_or_size , size=None )

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

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

若将 bytes 对象指定作为第 1 自变量,缓冲会比其长度大 1 项,以便数组中的最后元素是 NUL 终止字符。可以将整数传递作为允许指定数组大小的第 2 自变量,若不应使用 bytes 长度。

ctypes. create_unicode_buffer ( init_or_size , size=None )

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

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

若将字符串指定作为第 1 自变量,缓冲会比字符串长度大 1 项,以便数组中的最后元素是 NUL 终止字符。可以将整数传递作为允许指定数组大小的第 2 自变量,若不应使用字符串长度。

ctypes. DllCanUnloadNow ( )

仅 Windows :此函数是允许采用 ctype 实现进程内 COM (组件对象模型) 服务器的挂钩。它的调用是来自 DllCanUnloadNow 函数 _ctypes 扩展 DLL (动态链接库) 导出。

ctypes. DllGetClassObject ( )

仅 Windows :此函数是允许采用 ctype 实现进程内 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 变量在调用线程中。

ctypes. get_last_error ( )

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

ctypes. memmove ( dst , src , count )

如同标准 C memmove 库函数:拷贝 count 字节来自 src to dst . dst and src 必须是整数或 ctypes 实例,可以被转换成指针。

ctypes. memset ( dst , c , count )

如同标准 C memset 库函数:填充内存块在地址 dst with count 字节的值 c . dst 必须是指定地址的整数,或 ctypes 实例。

ctypes. POINTER ( type )

此工厂函数创建并返回新的 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 并返回先前值。

ctypes. set_last_error ( value )

仅 Windows:设置 ctypes 私有副本的当前值为系统 LastError 变量在调用线程中到 value 并返回先前值。

ctypes. sizeof ( obj_or_type )

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

ctypes. string_at ( address , size=-1 )

此函数返回的 C 字符串始于内存地址 address 如 bytes 对象。若 size 有指定,用作大小,否则假定字符串以 0 结尾。

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

仅 Windows:此函数可能是 ctypes 中的最差命名。它创建 OSError 实例。若 code 未指定, GetLastError 被调用以确定错误代码。若 descr 未指定, FormatError() 被调用以获取错误的正文描述。

3.3 版改变: 实例化的 WindowsError 用于创建。

ctypes. wstring_at ( address , size=-1 )

此函数返回的宽字符字符串始于内存地址 address 如字符串。若 size 有指定,用作字符串的字符数,否则假定字符串以 0 结尾。

16.16.2.6. 数据类型

class ctypes. _CData

此非公共类是所有 ctypes 数据类型的公用基类。除其它事情外,所有 ctypes 类型实例均包含保持 C 兼容数据的内存块;内存块地址的返回是通过 addressof() 帮手函数。另一实例变量被暴露成 _objects ;这包含需要在内存块包含指针的情况下,保持存活的其它 Python 对象。

常见 ctypes 数据类型方法都是类方法 (确切地说,这些方法源于 metaclass ):

from_buffer ( source [ , offset ] )

此方法返回共享缓冲的 ctypes 实例为 source 对象。 source 对象必须支持可写缓冲接口。可选 offset 参数指定到源缓冲的偏移 (以字节为单位);默认为 0。若源缓冲不够大 ValueError 被引发。

from_buffer_copy ( source [ , offset ] )

此方法创建 ctypes 实例,拷贝缓冲从 source 对象缓冲 (必须可读)。可选 offset 参数指定到源缓冲的偏移 (以字节为单位);默认为 0。若源缓冲不够大 ValueError 被引发。

from_address ( address )

此方法返回使用内存的 ctypes 类型实例指定通过 address 其必须为整数。

from_param ( obj )

此方法适配 obj 到 ctypes 类型。它与外来函数调用中所用的实际对象一起被调用,当类型呈现在外来函数的 argtypes 元组;它必须返回可以用作函数调用参数的对象。

所有 ctypes 数据类型拥有此类方法的默认实现,通常返回 obj 若那是类型的实例。某些类型还接受其它对象。

in_dll ( library , name )

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

ctypes 数据类型的常见实例变量:

_b_base_

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

_b_needsfree_

此只读变量为 True 当 ctypes 数据实例本身拥有分配内存块时,否则 False。

_objects

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

16.16.2.7. 基础数据类型

class ctypes. _SimpleCData

此非公共类是所有基础 ctypes 数据类型的基类。这里提及是因为它包含基础 ctypes 数据类型的公用属性。 _SimpleCData 是子类化的 _CData ,所以它继承了它们的方法和属性。现在可以腌制不包含指针 (或做不包含指针) 的 Ctypes 数据类型。

实例拥有单一属性:

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

value 属性从 ctypes 实例检索时,通常每次返回新对象。 ctypes does not 实现原始对象返回,始终构造新对象。同样如此对于所有其它 ctypes 对象实例。

当作为外来函数调用结果返回 (或者,例如:通过检索 Structure 字段成员或数组项) 时,基础数据类型会被透明地转换成本机 Python 类型。换句话说,若外来函数拥有 restype of c_char_p ,将始终收到 Python 字节对象, not a c_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 * 数据类型当它指向以 0 结尾的字符串时。对于还可以指向二进制数据的一般字符指针, 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 * 数据类型,必须是指向以 0 结尾的宽字符串的指针。构造函数接受整数地址,或字符串。

class ctypes. c_bool

表示 C bool 数据类型 (更精确, _Bool 来自 C99)。其值可以为 True or False ,且构造函数接受拥有真值的任何对象。

class ctypes. HRESULT

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

class ctypes. py_object

表示 C PyObject * 数据类型。调用这不采用自变量创建 NULL PyObject * 指针。

The ctypes.wintypes 模块提供了相当多的其它 Windows 特定数据类型,例如 HWND , WPARAM ,或 DWORD 。某些有用结构像 MSG or RECT 也有定义。

16.16.2.8. 结构化数据类型

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

用于 Union 的抽象基类,按本机字节序。

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

用于 Structure 的抽象基类,按 big endian 字节序。

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

用于 Structure 的抽象基类,按 little endian 字节序。

具有非本机字节序的 Structure,不可以包含指针类型字段 (或包含指针类型字段的任何其它数据类型)。

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

用于 Structure 的抽象基类,按 native 字节序。

具体 Structure 和 Union 类型必须通过子类化这些类型之一来创建,且至少有定义 _fields_ 类变量。 ctypes 将创建 descriptor ,允许通过直接访问属性读取和写入字段。这些是

_fields_

定义 Structure 字段的序列。项必须是 2 元素元组 (或 3 元素元组)。第 1 项是字段名,第 2 项指定字段类型;它可以是任何 ctypes 数据类型。

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

字段名在一个 Structure 或 Union 中必须唯一。这不校验,只可以访问一字段当名称重复时。

它是可能的定义 _fields_ 类变量 after 定义 Structure 子类的类语句,这允许创建直接 (或间接) 引用自身的数据类型:

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

The _fields_ 类变量必须定义,不管怎样,在第一次使用类型之前 (创建实例, sizeof() 会调用它,等等)。稍后赋值 _fields_ 类变量将引发 AttributeError。

It is possible to defined sub-subclasses of structure types, they inherit the fields of the base class plus the _fields_ 定义在子子类中,若有的话。

_pack_

允许覆盖实例中 Structure 字段对齐的可选小整数。 _pack_ 必须已定义当 _fields_ 被赋值,否则不起作用。

_anonymous_

列出未命名 (匿名) 字段名称的可选序列。 _anonymous_ 必须已定义当 _fields_ 被赋值,否则不起作用。

此变量中列出的字段,必须是 Structure 或 Union 类型字段。 ctypes 将在允许直接访问嵌套字段的 Structure 类型中创建描述符,不需要创建 Structure 或 Union 字段。

这里是范例类型 (Windows):

class _U(Union):
    _fields_ = [("lptdesc", POINTER(TYPEDESC)),
                ("lpadesc", POINTER(ARRAYDESC)),
                ("hreftype", HREFTYPE)]
class TYPEDESC(Structure):
    _anonymous_ = ("u",)
    _fields_ = [("u", _U),
                ("vt", VARTYPE)]
										

The TYPEDESC 结构描述 COM (组件对象模型) 数据类型, vt 字段指定哪个 Union 字段有效。由于 u 字段被定义成匿名字段,关闭 TYPEDESC 实例直接访问成员现在是可能的。 td.lptdesc and td.u.lptdesc 等价,但前者更快,由于它不需要创建临时 Union 实例:

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

It is possible to defined sub-subclasses of structures, they inherit the fields of the base class. If the subclass definition has a separate _fields_ 变量,其中的指定字段会被追加到基类字段。

Structure 和 Union 构造函数接受位置和关键词自变量两者。位置自变量用于按相同次序初始化成员字段,如它们出现在 _fields_ 。构造函数中的关键词自变量被解释成属性赋值,因此它们将初始化 _fields_ 采用相同名称,或创建新属性若名称未呈现在 _fields_ .

16.16.2.9. 数组和指针

class ctypes. 数组 ( *args )

用于数组的抽象基类。

创建具体数组类型的推荐方式是通过倍增任何 ctypes 数据类型采用正整数。另外,可以子类化此类型并定义 _length_ and _type_ 类变量。 可以使用标准下标和切片访问,读取/写入数组元素;对于切片读取,结果对象是 not 自身 Array .

_length_

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

_type_

指定数组中各元素的类型。

数组子类构造函数接受位置自变量,用于按次序初始化元素。

class ctypes. _Pointer

用于指针的私有抽象基类。

具体指针类型的创建是通过调用 POINTER() 采用将指向类型;自动做到这通过 pointer() .

若指针指向数组,使用标准下标和切片访问可以读取/写入其元素。指针对象没有大小,所以 len() 会引发 TypeError 。负值下标将读取从内存 before 指针 (如在 C 中),且下标超出范围可能崩溃因访问违规 (若幸运的话)。

_type_

指定指向的类型。

contents

返回指针指向的对象。赋值此属性改变指针指向的赋值对象。

版权所有  © 2014-2026 乐数软件    

工业和信息化部: 粤ICP备14079481号-1