ctypes
— 用于 Python 的外来函数库
¶
ctypes
是用于 Python 的外部函数库。它提供 C 兼容数据类型,且允许调用 DLL 或共享库中的函数。可以使用它以将这些库包裹在纯 Python 中。
注意:本教程中的代码样本使用
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 对象的属性被访问:
>>> 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
|
构造函数接受具有真值的任何对象。
可以创建这些所有类型,通过采用正确类型的可选初始化程序和值调用它们:
>>> 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
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 字段的对齐方式如同 C 编译器所做的。覆盖此行为是可能的,通过指定
_pack_
类属性在子类定义中。必须将这设为正整数,并指定字段的最大对齐方式。这是为什么
#pragma pack(n)
在 MSVC 中也如此。
ctypes
为 Structure 和 Union 使用本机字节序。构建具有非本机字节序的 Structure,可以使用某一
BigEndianStructure
,
LittleEndianStructure
,
BigEndianUnion
,和
LittleEndianUnion
基类。这些类无法包含指针字段。
创建包含位字段的 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 线程做出时。
某些共享库不仅导出函数,它们还导出变量。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 的动态性质,并在已知要求大小后 (重新) 定义数据类型,根据具体情况而定。
以编译型语言编程时,当编译/链接程序及程序在运行时会访问共享库。
目地对于
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 进程。一种方式是实例化下列类之一:
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 依赖的工具。
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
用于被引发。
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 全局解释器锁 会被释放在调用由这些库导出的任何函数之前,和之后重新获取。
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()
方法,或通过作为加载器实例的属性检索库。
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 数据实例作为自变量,并返回由库加载器指定的默认结果类型。它们是私有类的实例:
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 是包含传递给函数调用的最初参数的元组,这允许专攻所用参数的行为。
此函数返回的对象是外来函数调用所返回的,但它还可以校验结果值,并引发异常若外来函数调用失败。
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 项是包含参数方向标志组合的整数:
指定函数的输入参数。
输出参数。外来函数的值填充。
默认为整数 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_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
.
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_needsfree_
¶
此只读变量为 True 当 ctypes 数据实例本身拥有分配内存块时,否则 False。
_objects
¶
此成员为
None
或字典 (包含需要保持存活的 Python 对象,以便内存块内容保持有效)。此对象只暴露于调试;从不修改此字典的内容。
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 数据类型:
ctypes.
c_byte
¶
表示 C
signed char
数据类型,并将值解释成小整数。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_char
¶
表示 C
char
数据类型,并将值解释成单个字符。构造函数接受可选字符串初始化程序,字符串的长度必须准确是一字符。
ctypes.
c_char_p
¶
表示 C
char *
数据类型当它指向以 0 结尾的字符串时。对于还可以指向二进制数据的一般字符指针,
POINTER(c_char)
必须使用。构造函数接受整数地址,或字节对象。
ctypes.
c_double
¶
表示 C
double
数据类型。构造函数接受可选浮点初始化器。
ctypes.
c_longdouble
¶
表示 C
long double
数据类型。构造函数接受可选浮点初始化程序。当平台
sizeof(long double) ==
sizeof(double)
,它是别名化的
c_double
.
ctypes.
c_float
¶
表示 C
float
数据类型。构造函数接受可选浮点初始化器。
ctypes.
c_int
¶
表示 C
signed int
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。当平台
sizeof(int) == sizeof(long)
,它是别名化的
c_long
.
ctypes.
c_int64
¶
表示 C 64 位
signed int
数据类型。通常别名为
c_longlong
.
ctypes.
c_long
¶
表示 C
signed long
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_longlong
¶
表示 C
signed long long
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_short
¶
表示 C
signed short
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_size_t
¶
表示 C
size_t
数据类型。
ctypes.
c_ssize_t
¶
表示 C
ssize_t
数据类型。
3.2 版新增。
ctypes.
c_ubyte
¶
表示 C
unsigned char
数据类型,它将值解释成小整数。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_uint
¶
表示 C
unsigned int
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。当平台
sizeof(int) == sizeof(long)
它是别名化的
c_ulong
.
ctypes.
c_uint64
¶
表示 C 64 位
unsigned int
数据类型。通常别名为
c_ulonglong
.
ctypes.
c_ulong
¶
表示 C
unsigned long
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_ulonglong
¶
表示 C
unsigned long long
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_ushort
¶
表示 C
unsigned short
数据类型。构造函数接受可选整数初始化程序;不做溢出校验。
ctypes.
c_void_p
¶
表示 C
void *
类型。值被表示成整数。构造函数接受可选整数初始化程序。
ctypes.
c_wchar
¶
表示 C
wchar_t
数据类型,并将值解释成单个字符的 Unicode 字符串。构造函数接受可选字符串初始化程序,字符串的长度必须准确是一字符。
ctypes.
c_wchar_p
¶
表示 C
wchar_t *
数据类型,必须是指向以 0 结尾的宽字符串的指针。构造函数接受整数地址,或字符串。
ctypes.
c_bool
¶
表示 C
bool
数据类型 (更精确,
_Bool
来自 C99)。其值可以为
True
or
False
,且构造函数接受拥有真值的任何对象。
ctypes.
HRESULT
¶
仅 Windows:表示
HRESULT
值,包含函数 (或方法调用) 的成功或错误信息。
ctypes.
py_object
¶
表示 C
PyObject *
数据类型。调用这不采用自变量创建
NULL
PyObject *
指针。
The
ctypes.wintypes
模块提供了相当多的其它 Windows 特定数据类型,例如
HWND
,
WPARAM
,或
DWORD
。某些有用结构像
MSG
or
RECT
也有定义。
ctypes.
Union
(
*args
,
**kw
)
¶
用于 Union 的抽象基类,按本机字节序。
ctypes.
BigEndianStructure
(
*args
,
**kw
)
¶
用于 Structure 的抽象基类,按 big endian 字节序。
ctypes.
LittleEndianStructure
(
*args
,
**kw
)
¶
用于 Structure 的抽象基类,按 little endian 字节序。
具有非本机字节序的 Structure,不可以包含指针类型字段 (或包含指针类型字段的任何其它数据类型)。
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_
定义在子子类中,若有的话。
_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_
.
ctypes.
数组
(
*args
)
¶
用于数组的抽象基类。
创建具体数组类型的推荐方式是通过倍增任何
ctypes
数据类型采用正整数。另外,可以子类化此类型并定义
_length_
and
_type_
类变量。 可以使用标准下标和切片访问,读取/写入数组元素;对于切片读取,结果对象是
not
自身
Array
.
_length_
¶
指定数组中元素数量的正整数。下标超出范围将导致
IndexError
。将返回通过
len()
.
_type_
¶
指定数组中各元素的类型。
数组子类构造函数接受位置自变量,用于按次序初始化元素。
ctypes.
_Pointer
¶
用于指针的私有抽象基类。
具体指针类型的创建是通过调用
POINTER()
采用将指向类型;自动做到这通过
pointer()
.
若指针指向数组,使用标准下标和切片访问可以读取/写入其元素。指针对象没有大小,所以
len()
会引发
TypeError
。负值下标将读取从内存
before
指针 (如在 C 中),且下标超出范围可能崩溃因访问违规 (若幸运的话)。
_type_
¶
指定指向的类型。
contents
¶
返回指针指向的对象。赋值此属性改变指针指向的赋值对象。