ctypes.ArgumentError 将 kivy 与 pywinauto 一起使用时
ctypes.ArgumentError when using kivy with pywinauto
我有一个可以使用 pywinauto 模块与其他 windows 交互的 kivy 应用程序。该应用程序在 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
- 基维 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()
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q055928463]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py
[INFO ] [Logger ] Record log in C:\Users\cfati\.kivy\logs\kivy_19-05-01_83.txt
[INFO ] [Kivy ] v1.10.1
[INFO ] [Python ] v3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
[INFO ] [Factory ] 194 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] GLEW initialization succeeded
[INFO ] [GL ] Backend used <glew>
[INFO ] [GL ] OpenGL version <b'4.5.0 - Build 23.20.16.4973'>
[INFO ] [GL ] OpenGL vendor <b'Intel'>
[INFO ] [GL ] OpenGL renderer <b'Intel(R) HD Graphics 530'>
[INFO ] [GL ] OpenGL parsed version: 4, 5
[INFO ] [GL ] Shading version <b'4.50 - Build 23.20.16.4973'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pywinauto\__init__.py:80: UserWarning: Revert to STA COM threading mode
warnings.warn("Revert to STA COM threading mode", UserWarning)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Base ] Start application main loop
Traceback (most recent call last):
File "code.py", line 36, in <module>
Demo().run()
File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\app.py", line 826, in run
runTouchApp()
File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 477, in runTouchApp
EventLoop.start()
File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 164, in start
provider.start()
File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\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
在继续之前,我想指出:
- [Python 3.Docs]: ctypes - A foreign function library for Python
- [MS.Docs]: SetWindowLongPtrW function
从后者可以看出,SetWindowLongPtrW的3rd参数可以是一个DWORD,一个HANDLE,一个函数指针,取决于2nd参数值:基本上它是一个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
基维 ([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进程中只加载一次
- 上面的代码初始化函数,通常只在模块导入时执行一次(它可以根据需要执行多次,但会降低性能)
- 两个模块都指定了函数(windll.user32.SetWindowLongPtrW,因为我们在64bit)原型上,但他们这样做了不同
- 根据上面的 3,最后导入的模块决定函数原型的外观
- 当导入的模块 1st 尝试使用原型时,它与传递的参数不匹配,因此错误
这就是为什么我必须在 Kivy 之后移动 Pywinauto 导入,以便 Kivy 尝试使用 Pywinauto 的原型调用该函数,否则它会起作用。
是
可能反过来,但我没有费心去寻找 Pywinauto 会调用该函数的场景,因为它不相关。
查看 2 个 ctypes 原型和 C 一个(来自 MS URL),结果是:
- Pywinauto 做事正确(不过我很好奇它在 32 位 上如何工作)
- Kivy 只使用 SetWindowLongPtrW 用例(带有函数指针的那个),为了让事情更简单,他们改编了他们场景的原型。但是,它与 C 原型 不太匹配,而这个来自我的 PoV,看起来像一个蹩脚的解决方法 (gainarie)
我修改了 Kivy 安装,tadaa! (这是复活节兔子!:)):
注意:此处遇到的(更简单的)变体:
更新#0
我已经提交了[GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug, which was merged. Not sure when it will be available on the market (PyPI,所以你可以简单地pip install
)。
作为替代方案,您可以下载补丁,并在本地应用更改。检查 (修补 utrunner 部分)了解如何在 Win 上应用补丁(基本上,以 一个“+” 符号开头的每一行都会进入,并且以 一个“-” 符号开头的每一行都会消失)。我正在使用 Cygwin,btw。
或者您可以下载 3 个修改后的文件并覆盖您现有的文件。
无论如何,先备份!另外,我不知道这些变化如何适应旧的 Kivy 版本。
我有一个可以使用 pywinauto 模块与其他 windows 交互的 kivy 应用程序。该应用程序在 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
- 基维 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()
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q055928463]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py [INFO ] [Logger ] Record log in C:\Users\cfati\.kivy\logs\kivy_19-05-01_83.txt [INFO ] [Kivy ] v1.10.1 [INFO ] [Python ] v3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] [INFO ] [Factory ] 194 symbols loaded [INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored) [INFO ] [Window ] Provider: sdl2 [INFO ] [GL ] Using the "OpenGL" graphics system [INFO ] [GL ] GLEW initialization succeeded [INFO ] [GL ] Backend used <glew> [INFO ] [GL ] OpenGL version <b'4.5.0 - Build 23.20.16.4973'> [INFO ] [GL ] OpenGL vendor <b'Intel'> [INFO ] [GL ] OpenGL renderer <b'Intel(R) HD Graphics 530'> [INFO ] [GL ] OpenGL parsed version: 4, 5 [INFO ] [GL ] Shading version <b'4.50 - Build 23.20.16.4973'> [INFO ] [GL ] Texture max size <16384> [INFO ] [GL ] Texture max units <32> [INFO ] [Window ] auto add sdl2 input provider [INFO ] [Window ] virtual keyboard not allowed, single mode, not docked e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pywinauto\__init__.py:80: UserWarning: Revert to STA COM threading mode warnings.warn("Revert to STA COM threading mode", UserWarning) [INFO ] [Text ] Provider: sdl2 [INFO ] [Base ] Start application main loop Traceback (most recent call last): File "code.py", line 36, in <module> Demo().run() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\app.py", line 826, in run runTouchApp() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 477, in runTouchApp EventLoop.start() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 164, in start provider.start() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\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
在继续之前,我想指出:
- [Python 3.Docs]: ctypes - A foreign function library for Python
- [MS.Docs]: SetWindowLongPtrW function
从后者可以看出,SetWindowLongPtrW的3rd参数可以是一个DWORD,一个HANDLE,一个函数指针,取决于2nd参数值:基本上它是一个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
基维 ([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进程中只加载一次
- 上面的代码初始化函数,通常只在模块导入时执行一次(它可以根据需要执行多次,但会降低性能)
- 两个模块都指定了函数(windll.user32.SetWindowLongPtrW,因为我们在64bit)原型上,但他们这样做了不同
- 根据上面的 3,最后导入的模块决定函数原型的外观
- 当导入的模块 1st 尝试使用原型时,它与传递的参数不匹配,因此错误
这就是为什么我必须在 Kivy 之后移动 Pywinauto 导入,以便 Kivy 尝试使用 Pywinauto 的原型调用该函数,否则它会起作用。
是
可能反过来,但我没有费心去寻找 Pywinauto 会调用该函数的场景,因为它不相关。
查看 2 个 ctypes 原型和 C 一个(来自 MS URL),结果是:
- Pywinauto 做事正确(不过我很好奇它在 32 位 上如何工作)
- Kivy 只使用 SetWindowLongPtrW 用例(带有函数指针的那个),为了让事情更简单,他们改编了他们场景的原型。但是,它与 C 原型 不太匹配,而这个来自我的 PoV,看起来像一个蹩脚的解决方法 (gainarie)
我修改了 Kivy 安装,tadaa! (这是复活节兔子!:)):
注意:此处遇到的(更简单的)变体:
更新#0
我已经提交了[GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug, which was merged. Not sure when it will be available on the market (PyPI,所以你可以简单地pip install
)。
作为替代方案,您可以下载补丁,并在本地应用更改。检查
或者您可以下载 3 个修改后的文件并覆盖您现有的文件。
无论如何,先备份!另外,我不知道这些变化如何适应旧的 Kivy 版本。