使用ctypes调用系统C API函数需要注意的问题,函数参数中有指针或结构体的情况下最好不要修改argtypes
有人向我反应,在代码里同时用我的python模块uiautomation和其它另一个模块后,脚本运行时会报错,但单独使用任意一个模块时都是正常的,没有错误。issue链接
我用一个例子来演示下这个问题是如何出现的。
假设我需要写一个module,这个module需要提供获取当前鼠标光标下窗口句柄的功能,这需要调用系统C API来实现。
实现如下:
module1.py
#!python3 # -*- coding: utf-8 -*- import ctypes import ctypes.wintypes class POINT(ctypes.Structure): _fields_ = [("x", ctypes.wintypes.LONG), ("y", ctypes.wintypes.LONG)] ctypes.windll.user32.WindowFromPoint.argtypes = (POINT, ) ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p ctypes.windll.user32.GetCursorPos.argtypes = (ctypes.POINTER(POINT), ) def WindowFromPoint(x, y): return ctypes.windll.user32.WindowFromPoint(POINT(x, y)) def GetCursorPos(): point = POINT(0, 0) ctypes.windll.user32.GetCursorPos(ctypes.byref(point)) return point.x, point.y def WindowFromCursor(): x, y = GetCursorPos() return WindowFromPoint(x, y)
WindowFromPoint和GetCursorPos是系统user32.dll里的函数。
WindowFromPoint的C/C++函数原型是 HWND WindowFromPoint( POINT Point ); POINT 是C/C++里的struct类型,HWND是void*类型。
WindowFromPoint.argtypes = (POINT, ) 设置函数的参数类型,如果在Python里调用WindowFromPoint传入的参数不是POINT类型就会提示参数类型不匹配。如果不设置argtypes也是可以调用的,但要保证传入的参数是合法的类型。
WindowFromPoint.restype = ctypes.c_void_p 设置函数的返回类型,这个是必要的,如果用的是64位版本,C函数返回是64位指针值,如果不设置,返回值会被截断成32位。
调用的代码如下
test.py
#!python3 # -*- coding:utf-8 -*- import module1 def main(): print('the handle under cursor is', module1.WindowFromCursor()) if __name__ == '__main__': main()
运行结果如下
the handle under cursor is 1839250
这时复制一份module1.py,重命名为module2.py,他们的代码是完全一样的
在test.py同时调用这两个module,代码如下
#!python3 # -*- coding:utf-8 -*- import module1 import module2 def main(): print('the handle under cursor is', module1.WindowFromCursor()) print('the handle under cursor is', module2.WindowFromCursor()) if __name__ == '__main__': main()
运行就会报错了
ctypes.ArgumentError: argument 1: <class 'TypeError'>: expected LP_POINT instance instead of pointer to POINT
但分开单独调用任一个模块就是正常的,不会出错。
这是因为,module1,module2调用的同一个C函数,在设置argtypes的时候,后面的修改会覆盖前面的设置。
执行
import module1 import module2
后,C函数中的POINT参数必须是module2.POINT才是合法的。
在用module1调用时,传入的参数类型是module1.POINT,运行时就会报错了。
这种错误应该只有在参数中有结构体或结构体指针时才会出现。
假设module1, module2分别是两个人写,而这两个module都会调用同一个C函数,C函数参数里有你自定义的ctypes.Structure类型,你又要同时用这两个module,只要有一个module设置了argtypes,运行时可能就会出错。
解决方法是,在module1, module2中注释两行代码
#ctypes.windll.user32.WindowFromPoint.argtypes = (POINT, ) ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p #ctypes.windll.user32.GetCursorPos.argtypes = (ctypes.POINTER(POINT), )
不要修改argtypes,也是可以调用的,再运行test.py就不会报错了。