疫情当下,群羊乱舞,请做好个人防护谨慎外出
接下来进入正题。
1、建议先把PyHook3.HookManager的一个小bug修好
这是类对象的析构bug,被析构(del)时触发:AttributeError: 'HookManager' object has no attribute 'keyboard_hook'
from PyHook3 import HookManager
def Func():
hm = HookManager()
Func()
因为这个bug我已经修过了,我就不发执行结果。 解决方法如图(点击图片跳转github链接):
2、HookManager的挂钩行为要在单个线程内完成,不允许跨线程
提到钩子,就不得不提到伟大的win32编程中的一个坑爹伟大的API,没错就是SetWindowsHookEx 函数 (winuser.h) 虽然它提到了调用SetWindowsHookEx 之后需要调用UnhookWindowsHookEx 卸载钩子,但是它只字不提跨线程的事,说的简单点就是,钩子的挂上和取下,都要在同一个函数内完成,不能跨线程,要不然你就能切身体会到什么叫“摸不着的痛苦”。关于win32的钩子使用方法我有空再写。
回到这里,同样的,PyHook3的全局钩子,怎么可能绕得开底层API,既然底层API都要在同个线程里完成钩子的挂上和取下,那么在Python更不用提了。如果不在同个生命周期内完成钩子的安装和卸载,那么这个钩子就直接废掉无法正常使用。
测试代码如下:
from threading import Thread
from PyHook3 import HookManager
from time import sleep
import pythoncom
def onMouseEvent(event):
print(event.Position)
return True
hm=None
def ThreadFunc():
global hm
hm=HookManager()
hm.MouseAll = onMouseEvent
hm.HookMouse()
if __name__=='__main__':
Thread(target=ThreadFunc).start()
sleep(0.1)
hm.UnhookMouse()
hm.HookMouse()
pythoncom.PumpMessages()
然后就是,顺便说一下“全局钩子”。以下为简短的科普时间,不感兴趣的可以跳过。
在win32编程中,全局钩子指的是鼠标和键盘,全局钩子是个挂上后到哪都能成功生效的玩意儿(而不只是局限于某个进程)。其中,钩子的枚举值为WH_KEYBOARD_LL 和WH_MOUSE_LL ,(点此查看所有钩子枚举值),其中“LL”的意思是“Low-Level”,也就是“低级”的意思,但这并不是通俗意义上的“差”的意思,而是“底层”的那种,说白了就是,它优先级高,而且生效范围广。一般情况下Low-Level函数会更加难用、更易受限一点(例如适用系统受限),例如Low-Level控制台输出函数
3、钩子挂上后必须进入消息循环
钩子必须进入消息循环才能正常生效,想通过time.sleep() 、threading.Event.wait() 等方法替代消息循环让钩子只运行一段时间的白日梦实际上只会徒增痛苦。这里和“一般线程不允许修改UI元素,想修改UI元素必须使用UI线程”有点像,也就是“用特定的API完成特定的行为”,在这里的话就是挂上钩子后必须进入消息循环,(我可没说进入消息循环只能用pythoncom.PumpMessages 这个函数)
from threading import Event
from threading import Thread
from PyHook3 import HookManager
from time import sleep
import pythoncom
def onMouseEvent(event):
print(event.Position)
return True
event=Event()
def ThreadFunc():
hm=HookManager()
hm.MouseAll = onMouseEvent
hm.HookMouse()
pythoncom.PumpMessages()
hm.UnhookMouse()
if __name__=='__main__':
event.clear()
Thread(target=ThreadFunc).start()
sleep(2)
event.set()
4、建议使用win32gui.PumpMessages进入消息循环
这里先说一下,win32gui和pythoncom应该是一家人,为什么说是“应该”呢,因为我也没去细查。 pythoncom的API: http://www.markjour.com/docs/pywin32-docs/pythoncom.html win32gui的API: http://www.markjour.com/docs/pywin32-docs/win32gui.html 这俩模块均在同一个网站中,只不过这网站我也不敢说它是官网,虽然文档是英文的,但是文档不全(有些API只有签名没有说明功能和用法),而且网页的标签图标居然是中文的“码”字…
然后就是,pythoncom.PumpMessages 和win32gui.PumpMessages 可以说是一模一样了,API一样,功能也一样的说,而且更主要的是,使用win32gui的API能让代码更加可控,让你随时退出消息循环取下钩子。运行下面测试代码,带你开辟新世界。(编程老手请绕道,容我在此装杯)
import win32gui as WG
import win32api as WA
import win32con as WC
from threading import Thread
from PyHook3 import HookManager
from time import sleep
def onMouseEvent(event):
print(event.Position)
return True
tid=0
def ThreadFunc():
global tid
hm = HookManager()
hm.MouseAll = onMouseEvent
hm.HookMouse()
tid=WA.GetCurrentThreadId()
WG.PumpMessages()
if __name__=='__main__':
print("Start Tracking")
Thread(target=ThreadFunc).start()
sleep(1)
print("Stop Tracking")
WG.PostThreadMessage(tid,WC.WM_QUIT,0,0)
sleep(1)
print("Exit")
exit()
以上就是本人踩过的关于PyHook3.HookManager的几个坑。码字不易,白嫖随意,但请勿私自转载。 原博客发布于CSDN:https://blog.csdn.net/weixin_44733774/article/details/128379683
|