• 使用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就不会报错了。

      

  • 相关阅读:
    Live2d Test Env
    Live2d Test Env
    Spring Boot GraphQL 实战 02_增删改查和自定义标量
    Spring Boot GraphQL 实战 01_快速入门
    【MacBook】 SSH使用
    一个神奇的没有springboot注释的api文档生成器---JApiDocs
    Spring Cloud Alibaba 06:Gateway服务网关
    leetcode-300. 最长递增子序列
    leetcode-309. 最佳买卖股票时机含冷冻期
    leetcode-121. 买卖股票的最佳时机
  • 原文地址:https://www.cnblogs.com/Yinkaisheng/p/10955468.html
Copyright © 2020-2023  润新知