
在使用 ctypes 调用 C/C++ 动态链接库(DLL)或共享库(SO)中的函数时,经常会遇到函数通过指针或引用返回数据(即输出参数)的情况。ctypes 提供了多种方式来处理这些输出参数,其中一种是使用 WINFUNCTYPE 或 CFUNCTYPE 配合 paramflags。
以 Windows API GetWindowRect 为例,其 C 语言签名如下:
BOOL GetWindowRect( [in] HWND hWnd, [out] LPRECT lpRect );
这个函数接收一个输入参数 hWnd (窗口句柄),一个输出参数 lpRect (指向 RECT 结构的指针),并返回一个 BOOL 值表示操作是否成功。
ctypes 文档中提供了一种使用 paramflags 的方式来处理输出参数:
from ctypes import POINTER, WINFUNCTYPE, windll
from ctypes.wintypes import BOOL, HWND, RECT
prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
paramflags = (1, "hwnd"), (2, "lprect") # 1 for in, 2 for out
GetWindowRect = prototype(("GetWindowRect", windll.user32), paramflags)这种方法会自动将输出参数作为函数的返回值。根据 ctypes 文档的描述,如果只有一个输出参数,它将作为函数的唯一返回值;如果有多个,则返回一个包含所有输出参数的元组。对于 GetWindowRect,这意味着调用 GetWindowRect(hwnd) 将直接返回一个 RECT 实例。
然而,这种便捷性带来了一个问题:函数的原始返回值(在本例中是 BOOL 类型,指示操作成功与否)会被“吞噬”或替换掉,无法直接获取。这对于需要根据原始返回值判断函数执行状态(例如,是否成功,或是否有错误码)的场景来说,是一个明显的限制。
为了更好地控制 ctypes 函数的调用行为,特别是当需要同时获取输出参数和原始返回值时,推荐使用 ctypes 提供的 argtypes、restype 和 errcheck 属性。这种方法提供了更细粒度的控制,并且是 ctypes 库中更常见的实践。
首先,导入必要的 ctypes 模块和 Windows 类型:
import ctypes as ct import ctypes.wintypes as w
为了使结构体在打印时更具可读性,我们可以定义一个通用的基类,重写 __repr__ 方法:
# 可重用的结构体基类,用于打印自身
class Repr(ct.Structure):
def __repr__(self):
return (f'{self.__class__.__name__}(' +
', '.join([f'{n}={getattr(self, n)}'
for n, _ in self._fields_]) + ')')
# 自定义的 RECT 结构体,继承自 wintypes.RECT 并具备打印能力
class RECT(w.RECT, Repr):
pass许多 Win32 API 函数返回 BOOL 类型来指示成功或失败,并在失败时设置一个错误码,可以通过 GetLastError() 获取。为了将这种 C 风格的错误处理转换为 Python 异常,我们可以定义一个 errcheck 函数:
# 针对返回 BOOL 类型且支持 GetLastError() 的 Win32 函数的错误检查
def boolcheck(result, func, args):
if not result: # 如果结果为假(即0),表示函数调用失败
# 抛出 WinError 异常,其中包含通过 GetLastError() 获取的错误信息
raise ct.WinError(ct.get_last_error())
return None # 如果成功,errcheck 返回 None 或原始结果,这里选择 None,让包装函数处理返回值这个 boolcheck 函数将在 C 函数返回后被调用。如果 result 为 False (通常是 C 语言中的 0),它将通过 ct.get_last_error() 获取 Windows 系统的最后错误码,并抛出一个 ct.WinError 异常,使得错误处理更加 Pythonic。
为了确保 ct.get_last_error() 能够正确获取错误码,在加载 DLL 时,需要设置 use_last_error=True:
# 确保在函数调用后直接捕获最后错误码
user32 = ct.WinDLL('user32', use_last_error=True)现在,我们可以定义 GetForegroundWindow 和 GetWindowRect 的原型。
GetForegroundWindow (辅助函数,获取当前活动窗口句柄):
GetForegroundWindow = user32.GetForegroundWindow GetForegroundWindow.argtypes = () # 没有输入参数 GetForegroundWindow.restype = w.HWND # 返回 HWND 类型
_GetWindowRect (核心函数):
对于 GetWindowRect,我们将其命名为 _GetWindowRect 以区分后续的 Python 包装函数。
_GetWindowRect = user32.GetWindowRect # 定义输入参数类型:HWND 和指向 RECT 结构的指针 _GetWindowRect.argtypes = w.HWND, ct.POINTER(RECT) # 定义函数的原始返回值类型:BOOL _GetWindowRect.restype = w.BOOL # 设置错误检查函数 _GetWindowRect.errcheck = boolcheck
通过明确设置 restype = w.BOOL,我们确保 ctypes 知道函数的原始返回值是一个布尔值。当 _GetWindowRect 被调用时,errcheck 函数会首先接收到这个 BOOL 值进行判断。
为了使 _GetWindowRect 的调用更符合 Python 习惯,我们可以编写一个包装函数:
# 包装函数,提供更友好的接口
def GetWindowRect(hwnd):
r = RECT() # 创建一个 RECT 实例用于接收输出参数
# 调用底层的 _GetWindowRect,传入句柄和 RECT 实例的引用
# ct.byref(r) 将 RECT 实例的地址传递给 C 函数
_GetWindowRect(hwnd, ct.byref(r)) # 如果失败,此处将抛出异常
return r # 成功则返回填充好的 RECT 实例在这个包装函数中:
这种方法实现了:
import ctypes as ct
import ctypes.wintypes as w
# 可重用的结构体基类,用于打印自身
class Repr(ct.Structure):
def __repr__(self):
return (f'{self.__class__.__name__}(' +
', '.join([f'{n}={getattr(self, n)}'
for n, _ in self._fields_]) + ')')
# 自定义的 RECT 结构体,继承自 wintypes.RECT 并具备打印能力
class RECT(w.RECT, Repr):
pass
# 针对返回 BOOL 类型且支持 GetLastError() 的 Win32 函数的错误检查
def boolcheck(result, func, args):
if not result:
raise ct.WinError(ct.get_last_error())
return None # 返回 None,让包装函数处理实际的输出参数
# 确保在函数调用后直接捕获最后错误码
user32 = ct.WinDLL('user32', use_last_error=True)
# 定义 GetForegroundWindow 函数
GetForegroundWindow = user32.GetForegroundWindow
GetForegroundWindow.argtypes = ()
GetForegroundWindow.restype = w.HWND
# 定义 _GetWindowRect 函数的 ctypes 接口
_GetWindowRect = user32.GetWindowRect
_GetWindowRect.argtypes = w.HWND, ct.POINTER(RECT)
_GetWindowRect.restype = w.BOOL # 指定原始返回类型为 BOOL
_GetWindowRect.errcheck = boolcheck # 设置错误检查函数
# 包装函数,提供更友好的接口
def GetWindowRect(hwnd):
r = RECT()
# 调用底层的 _GetWindowRect,如果失败,boolcheck 将抛出异常
_GetWindowRect(hwnd, ct.byref(r))
return r # 成功则返回填充好的 RECT 实例
# 示例用法
if __name__ == "__main__":
# 获取当前活动窗口的矩形
try:
current_window_rect = GetWindowRect(GetForegroundWindow())
print(f"当前活动窗口的矩形: {current_window_rect}")
except ct.WinError as e:
print(f"获取当前活动窗口矩形失败: {e}")
# 尝试使用无效句柄,预期会抛出异常
try:
GetWindowRect(None) # None 通常表示无效句柄
except ct.WinError as e:
print(f"使用无效句柄获取矩形失败(预期错误): {e}")
运行上述代码,预期会得到类似以下输出:
当前活动窗口的矩形: RECT(left=2561, top=400, right=3461, bottom=1437) # 实际坐标会根据你的屏幕和当前窗口而异 使用无效句柄获取矩形失败(预期错误): [WinError 1400] 无效的窗口句柄。
分析:
通过使用 argtypes、restype 和 errcheck,我们获得了对 ctypes 调用的更高级别控制:
虽然 paramflags 在某些简单场景下可能提供便捷,但当涉及到复杂的输出参数、原始返回值或需要详细错误处理时,argtypes、restype 和 errcheck 的组合无疑是更强大、更推荐的选择。
以上就是使用 ctypes 调C API:处理输出参数与原始返回值的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号