我有一个kivy应用程序,可以使用pywinauto模块与其他窗口进行交互。该应用程序在Linux(未使用pywinauto)上运行良好,但是在Windows中,我收到以下错误消息,该应用程序甚至无法启动:

C:\Program Files (x86)\Python36_64\lib\site-packages\pywinauto\__init__.py:80: UserWarning: Revert to STA COM threading mode
    warnings.warn("Revert to STA COM threading mode", UserWarning)
[INFO   ] [GL          ] NPOT texture support is available
[INFO   ] [Base        ] Start application main loop
Traceback (most recent call last):
File ".\application.py", line 368, in <module>
    Application().run()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\app.py", line 826, in run
    runTouchApp()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\base.py", line 477, in runTouchApp
    EventLoop.start()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\base.py", line 164, in start
    provider.start()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\input\providers\wm_touch.py", line 68, in start
    self.hwnd, GWL_WNDPROC, self.new_windProc)
ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type

我认为这是pywinauto的问题,原因是我有以下几行内容,并且在Linux上可以正常使用:
if SYSTEM == "Windows":
    import win32gui
    import win32process
    import wmi

    from pywinauto import application
    import pywinauto

我也注释掉了pywinauto导入行,它开始了。它可能链接到this issue.我真的不知道要包含什么代码,因为它可以在其他操作系统上工作。...我假设pywinauto正在更改使kivy无法正常工作的功能。

我的问题是:在同一个应用程序中如何同时拥有kivy和pywinauto的功能?

最佳答案

我能够使用以下方式重现该行为:

  • Python 3.7.3 x64
  • Kivy 1.10.1
  • Pywinauto 0.6.6

  • 附带说明一下,我之前没有使用过这两个软件包中的任何一个,我通过pip install专门针对此任务对其进行了编码。

    由于我不知道如何重现该行为,因此我只是从[GitHub]: pywinauto/pywinauto - ctypes.ArgumentError @ click_input(您在问题中也分享了)中复制了MCVE,并对其进行了少许修改(仅是遇到了错误,没有样式,没有改进等)。 )。

    code.py:

    import random
    from kivy.app           import App
    from kivy.lang          import Builder
    from kivy.core.window   import Window
    from kivy.uix.boxlayout import BoxLayout
    
    import pywinauto  # @TODO - cfati: moved after Kivy import(s), as it works otherwise (https://github.com/pywinauto/pywinauto/issues/419#issuecomment-488258224)
    
    
    class DemoLayout(BoxLayout): pass
    Builder.load_string("""
    #: import datetime  datetime.datetime
    <DemoLayout>:
      padding: 75
    
      Button:
        on_press: print(f"PRESSED @ {datetime.now()}")
    """)
    
    
    class Demo(App):
    
      def build(self):
        self.root = DemoLayout()
    
      def on_start(self):
        title = f"__KIVY_APP__{random.getrandbits(128)}"
        Window.set_title(title)
        hwnd = pywinauto.findwindows.find_window(title=title)
        app = pywinauto.Application()
        app.connect(handle=hwnd)
        window = app.window(handle=hwnd).wrapper_object()
        window.click_input(button="left", pressed="", coords=(100, 100), double=False, absolute=False)
    
    
    Demo().run()
    

    输出:



    在继续之前,我想指出:
  • [Python 3.Docs]: ctypes - A foreign function library for Python
  • [MS.Docs]: SetWindowLongPtrW function

  • 从后者可以看出,取决于第二个参数值,SetWindowLongPtrW的第三个参数可以是DWORD,HANDLE,函数指针:基本上是一个void *,可以将其映射到任何东西。这两个模块都通过ctypes调用此函数:

  • Pywinauto([GitHub]: pywinauto/pywinauto - (0.6.6) pywinauto/pywinauto/win32functions.py):

    try:
        SetWindowLongPtr    =   ctypes.windll.user32.SetWindowLongPtrW
        SetWindowLongPtr.argtypes = [win32structures.HWND, ctypes.c_int, win32structures.LONG_PTR]
        SetWindowLongPtr.restype = win32structures.LONG_PTR
    except AttributeError:
        SetWindowLongPtr = SetWindowLong
    
  • Kivy([GitHub]: kivy/kivy - (1.10.1) kivy/kivy/input/providers/wm_common.py):

    try:
        windll.user32.SetWindowLongPtrW.restype = WNDPROC
        windll.user32.SetWindowLongPtrW.argtypes = [HANDLE, c_int, WNDPROC]
        SetWindowLong_wrapper = windll.user32.SetWindowLongPtrW
    except AttributeError:
        windll.user32.SetWindowLongW.restype = WNDPROC
        windll.user32.SetWindowLongW.argtypes = [HANDLE, c_int, WNDPROC]
        SetWindowLong_wrapper = windll.user32.SetWindowLongW
    

  • 解释:
  • user32.dll在当前的Python进程
  • 中仅加载一次
  • 上面的代码初始化函数,通常仅在模块导入时执行一次(可以根据需要执行多次,但会降低性能)
  • 这两个模块都指定了函数原型(prototype)(就像我们在64bit上一样是windll.user32.SetWindowLongPtrW)原型(prototype),但是它们用不同的来做功能
  • 从上面的3中,最后导入的模块的结果决定了函数原型(prototype)的外观如何像
  • 当第一个导入的模块尝试使用原型(prototype)时,它与传递给的参数不匹配,因此出现了
  • 错误

    这就是为什么我必须在Kivy之后移​​动Pywinauto导入,以便Kivy尝试使用Pywinauto的原型(prototype)来调用该函数,否则它将起作用。它是
    可能会采取其他方法,但是我没有费心找到Pywinauto会调用该函数的情况,因为它不相关。

    查看2个ctypes原型(prototype)和C个原型(prototype)(从MS URL),结果表明:
  • Pywinauto做的正确(尽管我很好奇它如何在32bit上工作)
  • Kivy仅使用SetWindowLongPtrW用例(带有函数指针的用例),为了使事情变得更容易,他们针对其场景调整了原型(prototype)。但是,与C原型(prototype)不太匹配,这从我的PoV看,似乎是一个me脚的解决方法(gainarie)

  • 我修改了Kivy装置,然后修改了tadaa! (这是复活节兔子!:)):

    python - 与pywinauto一起使用kivy时ctypes.ArgumentError-LMLPHP

    注意:此处遇到的一个(更简单的)变体:[SO]: How to keep pynput and ctypes from clashing?



    更新#0

    我已经提交了 [GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug ,该已合并为。但是,不确定何时将在市场上出售它(PyPI,因此您可以简单地对其进行pip install编码)。

    或者,您可以下载补丁,然后在本地应用更改。检查[SO]: Run/Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (@CristiFati's answer)(修补utrunner 部分),了解如何在Win上应用补丁程序(基本上,每行以开头的一个“+” 符号都会进入,而每行以开头的每个行都会有一个“-” 符号消失) 。我正在使用Cygwin,顺便说一句。或者,您可以下载3个修改过的文件并覆盖现有文件。无论如何,首先将它们备份为!另外,我不知道这些更改如何适应较旧的Kivy版本。

    关于python - 与pywinauto一起使用kivy时ctypes.ArgumentError,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55928463/

    10-14 19:01