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 — 它们实际上是同一类型。

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

访问加载 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
>>>
			
					

调用函数

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

此范例调用 2 函数采用 ( 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 * or wchar_t * )。Python 整数被传递作为平台默认 C int 类型。它们的值被屏蔽以拟合成 C 类型。

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

基础数据类型

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() 函数。

调用函数,继续

注意: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
>>>
			
					

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

也可以定制 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 使属性在请求时可用。

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

指定从 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_ 属性。

返回类型

默认情况下,假定函数返回 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 属性;见参考手册了解细节。

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

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

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>
TypeError: 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 传递给函数。

Structure/Union 对齐和字节序

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

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

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

数组

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

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

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

指针

指针实例的创建是通过调用 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
>>>
			
				

类型转换

通常,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
>>>
			
				

不完整类型

不完整类型 是 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))]
>>>
				

让我们试一试。创建 2 个实例化的 cell ,并让它们指向彼此,最后沿指针链来几次:

>>> c1 = cell()
>>> c1.name = b"foo"
>>> c2 = cell()
>>> c2.name = b"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 可调用函数指针。有时称这些为 回调函数 .

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

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_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 will not 跨不同回调幸存,甚至在这些调用由同一 C 线程做出时。

访问 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 或 0。当冻结模块被导入时,在此表中搜索它。第 3 方代码可以按此玩点技巧,以提供动态创建的冻结模块的集合。

因此,操纵此指针甚至可以证明是有用的。为限制范例大小,只展示如何读取此表采用 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
>>>
				

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

请记住,从 Structure、Union 及数组检索子对象不 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 用于访问内存中的内容。在内存块中存储 Python 对象并不存储对象本身,而是 contents 在对象被存储。每次再次访问内容,都会构造新的 Python 对象!

可变大小的数据类型

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 的动态性质,并在已知要求大小后 (重新) 定义数据类型,根据具体情况而定。

ctypes 参考

查找共享库

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

目地对于 find_library() 函数是以类似于编译器 (或运行时加载器) 所做的方式定位库 (在共享库拥有多个版本的平台,应加载最近版本),而 ctypes 库加载器的行动像当程序在运行,并直接调用运行时加载器。

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

ctypes.util. find_library ( name )

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

确切功能从属系统。

在 Linux, find_library() 试着运行外部程序 ( /sbin/ldconfig , gcc , objdump and ld ) 来查找库文件。它返回库文件的文件名。

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() 会尝试几种预定义命名方案和按路径定位库,并返回完整路径名若成功:

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

加载共享库

有几种方式将共享库加载到 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] The specified module could not be found”. 此错误消息不包含缺失 DLL 的名称,因为 Windows API 不返回此信息 (使此错误难以诊断)。要解析此错误并确定哪个 DLL 未找到,需要查找依赖 DLL 列表,并使用 Windows 调试和跟踪工具确定未找到哪个 DLL。

另请参阅

微软 DUMPBIN 工具 – 查找 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 仅使用标准调用约定,为方便起见 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 私有副本。

The winmode 参数在 Windows 用于指定如何加载库 (由于 mode 被忽略)。它接受任何有效值为 Win32 API LoadLibraryEx 标志参数。省略时,默认使用导致最安全 DLL 加载标志,以避免譬如 DLL 劫持问题。将完整路径传递给 DLL 是确保正确加载库及依赖项的最安全方式。

3.8 版改变: 添加 winmode 参数。

ctypes. RTLD_GLOBAL

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

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 )

加载共享库的类。 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 属性以使用这些函数。

透过任何这些对象加载库会引发 审计事件 ctypes.dlopen 采用字符串自变量 name ,用于加载库的名称。

访问加载库中的函数,会引发审计事件 ctypes.dlsym 采用自变量 library (库对象) 和 name (符号名称为字符串或整数)。

若仅库句柄可用而不是对象,访问函数会引发审计事件 ctypes.dlsym/handle 采用自变量 handle (原生库句柄) 和 name .

外来函数

如之前章节解释,外部函数可以作为加载共享库的属性访问。默认情况下,以这种方式创建的函数对象接受任意数量的自变量,接受任何 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

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

在 Windows,当外来函数调用引发系统异常 (例如:由于访问违规) 时,它会被捕获并替换为合适的 Python 异常。进一步,审计事件 ctypes.seh_exception 采用自变量 code 会被引发,允许审计挂钩以自己拥有的替换异常。

援引外来函数调用的某些方式,可能引发审计事件 ctypes.call_function 采用自变量 function pointer and arguments .

函数原型

也可以通过实例化函数原型,创建外部函数。函数原型类似于 C 语言中的函数原型;它们描述函数 (返回类型、自变量类型、调用约定) 不定义实现。必须采用期望的结果类型和函数自变量类型调用工厂函数,且可以用作装饰器工厂,并因此应用于函数透过 @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_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
>>>
			
							

实用函数

ctypes. addressof ( obj )

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

引发 审计事件 ctypes.addressof 采用自变量 obj .

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_string_buffer 采用自变量 init , size .

ctypes. create_unicode_buffer ( init_or_size , size=None )

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

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

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

引发 审计事件 ctypes.create_unicode_buffer 采用自变量 init , size .

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_errno 不带自变量。

ctypes. get_last_error ( )

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

引发 审计事件 ctypes.get_last_error 不带自变量。

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_errno 采用自变量 errno .

ctypes. set_last_error ( value )

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

引发 审计事件 ctypes.set_last_error 采用自变量 error .

ctypes. sizeof ( obj_or_type )

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

ctypes. string_at ( address , size=-1 )

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

引发 审计事件 ctypes.string_at 采用自变量 address , size .

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 结尾。

引发 审计事件 ctypes.wstring_at 采用自变量 address , size .

数据类型

class ctypes. _CData

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

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

from_buffer ( source [ , offset ] )

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

引发 审计事件 ctypes.cdata/buffer 采用自变量 pointer , size , offset .

from_buffer_copy ( source [ , offset ] )

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

引发 审计事件 ctypes.cdata/buffer 采用自变量 pointer , size , offset .

from_address ( address )

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

此方法和间接调用此方法的其它方法,会引发 审计事件 ctypes.cdata 采用自变量 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 对象,以便内存块内容保持有效)。此对象只暴露于调试;从不修改此字典的内容。

基础数据类型

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 也有定义。

结构化数据类型

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。

定义 Structure 类型子子类是可能的,它们继承基类字段加 _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)
						

定义子子类化 Structure 是可能的,它们继承基类字段。若子类定义拥有单独 _fields_ 变量,其中的指定字段会被追加到基类字段。

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

数组和指针

class ctypes. 数组 ( *args )

用于数组的抽象基类。

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

_length_

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

_type_

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

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

class ctypes. _Pointer

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

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

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

_type_

指定指向的类型。

contents

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