说明
什么是钩子?
Windows系统程序的运行是建立在消息传递机制的基础之上的,几乎所有的程序活动都由消息来驱动。钩子机制可以看作是一个消息的中转站,控制系统发出消息的处理和传递。利用钩子,我们可以截获系统发给应用程序的消息,并且在经过处理后决定是否将消息再发给下一个应用程序。
在qt程序中,一般使用事件系统进行鼠标、按键输入的检测,但是这是有个前提条件的,就是需要聚焦在主界面或里面的对象中。
如果聚焦不在软件上面,其他地方点击鼠标或按下键盘则无法使用事件检测,这时候就需要用到Windows下的钩子技术,使用SetWindowsHookEx
API接口设置钩子(回调函数),如下代码:
keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,keyProc,nullptr,0);
keyProc
为自定义的回调函数,用于按键检测和处理。使用UnhookWindowsHookEx
接口解除钩子的绑定。如下:
UnhookWindowsHookEx(keyHook);
实现思路
上面说了,主体是使用SetWindowsHookEx
API接口注册钩子函数。
除此之外,为了我们的软件更加方便使用,我们可以对其进行一层封装,将按键、键盘的检测封装在MyHook类中,使用单例模式实例化,并检测到全局的按键或按钮后,以信号的方式对外发出。
其中有的可能只要实现鼠标钩子就行,不需要按键检测,这时候我们就可以添加一个模式设置,模式类型如下:
enum Model
{
eNull = 0,
eKey = 1,
eMouse = 2,
eMouseKey = 3
}mModel;
按模式注册钩子,不需要的就不注册,可以减少没必要的开销。
类代码
h文件
#ifndef MYHOOK_H
#define MYHOOK_H
#include <windows.h>
#include <winuser.h>
#include <QObject>
class MyHook:public QObject
{
Q_OBJECT
public:
enum Model
{
eNull = 0,
eKey = 1,
eMouse = 2,
eMouseKey = 3
}mModel;
static MyHook* instance();
void installHook(Model model); //安装钩子函数,指针参数为了发送信号用
void unInstallHook(Model model); //删除钩子
private:
MyHook();
};
#endif // MYHOOK_H
cpp文件
#include "myhook.h"
#include <QDebug>
static HHOOK keyHook = nullptr,mouseHook = nullptr; //钩子对象
static MyHook *hookClass = nullptr; //与钩子绑定的对象的指针
//钩子处理函数,包含键盘和鼠标一体
LRESULT CALLBACK keyProc(int nCode,WPARAM wParam,LPARAM lParam) //钩子消息函数,系统消息队列信息会返回到该函数中
{
if(!hookClass)
return CallNextHookEx(keyHook,nCode,wParam,lParam);
if(wParam == WM_KEYDOWN) //wParam用于判断事件类型,当前为按键按下事件
{
KBDLLHOOKSTRUCT* pkbhs = (KBDLLHOOKSTRUCT*)lParam; //lParam用于判断按键类型
if(pkbhs->vkCode == 0x31 && GetAsyncKeyState(VK_CONTROL)) //组合按键检测 按下Ctrl+1,哪个后按下的,使用GetAsyncKeyState函数判断
{
//处理程序
}
if(pkbhs->vkCode == 0x31) //检测按键1按下
{
//处理程序
}
qDebug()<<"key enter:"<<QString::number(pkbhs->vkCode,16);
//可添加一些自定义信号发出去,如:emit hookClass->keyPress
}
return CallNextHookEx(keyHook,nCode,wParam,lParam); //继续原有的事件队列
}
//钩子处理函数,
LRESULT CALLBACK mouseProc(int nCode,WPARAM wParam,LPARAM lParam) //钩子消息函数,系统消息队列信息会返回到该函数中
{
if(!hookClass)
return CallNextHookEx(mouseHook,nCode,wParam,lParam);
//鼠标点击右键
if (WM_RBUTTONDOWN == wParam || WM_LBUTTONDOWN == wParam)
{
MOUSEHOOKSTRUCT *mhookstruct = (MOUSEHOOKSTRUCT*)lParam; //鼠标HOOK结构体
POINT pt = mhookstruct->pt; //将当前鼠标坐标点的x,y坐标作为参数向主程序窗口发送消息
qDebug()<<QString("mouse enter:(%1,%2)").arg(pt.x).arg(pt.y);
//可添加一些自定义信号发出去,如:emit hookClass->mousePress
}
return CallNextHookEx(mouseHook,nCode,wParam,lParam); //继续原有的事件队列
}
MyHook::MyHook()
:mModel(eNull)
{
}
MyHook *MyHook::instance()
{
if(!hookClass)
hookClass = new MyHook();
return hookClass;
}
void MyHook::installHook(Model model) //安装钩子函数
{
mModel = model;
if(mModel | eKey)
keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,keyProc,nullptr,0);
if(mModel | eMouse)
mouseHook = SetWindowsHookEx(WH_MOUSE_LL,mouseProc,nullptr,0);
}
void MyHook::unInstallHook(Model model) //删除钩子函数
{
if(model | eKey)
UnhookWindowsHookEx(keyHook);
if(model | eMouse)
UnhookWindowsHookEx(mouseHook);
keyHook = nullptr;
mouseHook = nullptr;
mModel = eNull;
}
使用
封装为单例模式,使用就相当简单,直接使用instance静态函数,调用installHook函数设置模式即可。如果有自定义信号,可绑定你自己定义的信号槽。
如果只是运行看一下效果,可以直接在main函数或者mainwindow构造函数中加入以下代码:
MyHook::instance()->installHook(MyHook::eMouseKey);
//可使用MyHook::instance()指针,与其他的类绑定信号槽
调试运行输出:
最后
以上为鼠标、键盘钩子应用,其实在类中还可以添加其他的钩子实现其他目的,windows中钩子的类型有0–14 共15种,具体可可以参考下面文章:
https://blog.csdn.net/mid_Faker/article/details/112569461
代码定义中如下:
#define WH_JOURNALRECORD 0
#define WH_JOURNALPLAYBACK 1
#define WH_KEYBOARD 2
#define WH_GETMESSAGE 3
#define WH_CALLWNDPROC 4
#define WH_CBT 5
#define WH_SYSMSGFILTER 6
#define WH_MOUSE 7
#define WH_HARDWARE 8
#define WH_DEBUG 9
#define WH_SHELL 10
#define WH_FOREGROUNDIDLE 11
#define WH_CALLWNDPROCRET 12
#define WH_KEYBOARD_LL 13
#define WH_MOUSE_LL 14