12.1 使用键盘鼠标监控钩子

本节将介绍如何使用Windows API中的SetWindowsHookExRegisterHotKey函数来实现键盘鼠标的监控。这些函数可以用来设置全局钩子,通过对特定热键挂钩实现监控的效果,两者的区别在于SetWindowsHookEx函数可以对所有线程进行监控,包括其他进程中的线程,而RegisterHotKey函数只能对当前线程进行监控。

首先我们来实现注册热键功能,注册热键可以使用RegisterHotKey()函数,该函数可以将一个热键与当前应用程序或线程绑定,使得当用户按下热键时,系统会自动将该热键的消息发送到该应用程序或线程中,该函数原型如下;

BOOL RegisterHotKey(HWND hWnd,int id,UINT fsModifiers,UINT vk
);

其中,参数的含义如下:

  • hWnd:热键所属的窗口句柄,通常设置为NULL,表示与当前线程绑定
  • id:热键的ID号,用于区分不同的热键
  • fsModifiers:热键的修饰键,可以使用组合键,例如CtrlAltShift
  • vk:热键的虚拟键码,例如VK_F1表示F1VK_LEFT表示左箭头键等

函数需要传入一个窗口句柄、热键ID、热键组合键等参数来设置热键。当热键被按下时,系统会自动将一个WM_HOTKEY消息发送给注册了该热键的窗口,应用程序需要重载该窗口的消息处理函数来响应该事件,从而实现相应的响应操作。该函数会返回一个BOOL类型的值,表示热键设置是否成功。

当热键被注册后则就需要接收热键消息,通常可以使用GetMessage函数,该函数用于从消息队列中获取一个消息并将其存储在一个结构体中,通常用于在一个循环中不断地获取消息,从而实现对Windows消息的处理。

该函数的原型定义如下所示;

BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax
);

其中,参数的含义如下:

  • lpMsg:指向MSG结构体的指针,用于存储获取到的消息
  • hWnd:消息接收者的窗口句柄,通常设置为NULL,表示接收所有窗口的消息
  • wMsgFilterMin:指定获取消息的最小消息值,通常设置为0
  • wMsgFilterMax:指定获取消息的最大消息值,通常设置为0

GetMessage函数需要传入一个指向MSG结构体的指针,该结构体包含了消息的各种信息,例如消息的类型、发送者、接收者、时间戳等等。读者只需要通过判断函数内的WM_HOTKEY消息,并监控是否为我们所需要的即可,如下代码是一段注册热键的实现,分别注册了Ctrl+F1 Ctrl+F2 Ctrl+F3三个热键组;

#include <windows.h>
#include <iostream>using namespace std;int main(int argc, char* argv[])
{// 分别注册三个热键 Ctrl+F1 , Ctrl+F2 , Ctrl+F3if (0 == RegisterHotKey(NULL, 1, MOD_CONTROL, VK_F1)){cout << GetLastError() << endl;}if (0 == RegisterHotKey(NULL, 2, MOD_CONTROL, VK_F2)){cout << GetLastError() << endl;}if (0 == RegisterHotKey(NULL, 3, MOD_CONTROL, VK_F3)){cout << GetLastError() << endl;}// 消息循环MSG msg = { 0 };// 从消息循环内读出按键码while (GetMessage(&msg, NULL, 0, 0)){switch (msg.message){case WM_HOTKEY:{if (1 == msg.wParam){std::cout << "CTRL + F1" << std::endl;}else if (2 == msg.wParam){std::cout << "CTRL + F2" << std::endl;}else if (3 == msg.wParam){std::cout << "CTRL + F3" << std::endl;}break;}default:break;}}return 0;
}

读者可自行编译上述代码片段,并运行,分别按下Ctrl+F1 Ctrl+F2 Ctrl+F3即可看到输出效果图;

当然上述方法是局部的,读者只能在当前进程内使用,如果离开了进程窗体则这类热键将会失效,此时我们就需要使用SetWindowsHookEx函数注册全局钩子,该函数可以在系统中安装钩子,以便监视或拦截特定的事件或消息。

以下是SetWindowsHookEx的函数原型:

HHOOK SetWindowsHookEx(int       idHook,HOOKPROC  lpfn,HINSTANCE hMod,DWORD     dwThreadId
);

参数说明:

  • idHook:钩子类型,可以是WH_KEYBOARD(键盘钩子)或WH_MOUSE(鼠标钩子)等
  • lpfn:回调函数,当特定事件或消息发生时,操作系统会调用此函数。该函数的返回值由钩子类型和参数决定
  • hMod:包含lpfnDLL句柄。如果lpfn参数在当前进程内,则该参数可以为NULL
  • dwThreadId:线程标识符,指定与钩子相关联的线程。如果dwThreadId参数为0,则钩子将应用于所有线程

函数会返回一个类型为HHOOK的句柄,该句柄可以在卸载钩子时使用,读者需要注意由于全局钩子会影响系统性能,因此在使用SetWindowsHookEx函数时应谨慎,并在使用结束后及时的通过UnhookWindowsHookEx释放钩子句柄。

如下所示代码则是一个键盘钩子监控案例,在该案例中我们通过SetWindowsHookEx注册一个全局钩子,并设置回调函数LowLevelKeyboardProc通过使用PeekMessageA监控键盘事件,当有键盘事件产生时则自动路由到LowLevelKeyboardProc函数内,此时即可得到按键的类型以及按下键位,如下所示;

#include <windows.h>
#include <iostream>
#include <conio.h>using namespace std;// 钩子句柄
HHOOK keyboardHook = 0;// 键盘钩子
LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;if (ks->flags == 128 || ks->flags == 129){// 监控按键状态if (nCode >= 0){switch (wParam){case WM_KEYDOWN:cout << "普通按键抬起" << endl;break;case WM_KEYUP:cout << "普通按鍵按下" << endl;break;case WM_SYSKEYDOWN:cout << "系统按键抬起" << endl;break;case WM_SYSKEYUP:cout << "系统按键按下" << endl;break;}}// 监控键盘,并判断键switch (ks->vkCode){case 0x41:cout << "检测到按键:" << "A" << endl;break;case 0x0D:cout << "检测到按键:" << "Enter" << endl;break;case 0xA0: case 0xA1:cout << "检测到按键:" << "Shift" << endl;break;case 0x08:cout << "检测到按键:" << "Backspace" << endl;break;case 0x20:cout << "检测到按键:" << "Space" << endl;break;}// 直接返回1可以使按键失效//return 1;}return CallNextHookEx(NULL, nCode, wParam, lParam);
}int main(int argc, char* argv[])
{// 安装钩子WH_KEYBOARD_LL为键盘钩子keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleA(NULL), NULL);if (keyboardHook == 0){cout << "挂钩键盘失败" << endl; return -1;}MSG msg;while (1){if (PeekMessageA(&msg, NULL, NULL, NULL, PM_REMOVE)){TranslateMessage(&msg);DispatchMessageW(&msg);}elseSleep(0);}UnhookWindowsHookEx(keyboardHook);return 0;
}

编译并运行上述代码,读者可自行按下键盘键位,则可看到如下图所示的输出;

鼠标钩子的挂钩与键盘基本一致,只是在调用SetWindowsHookEx传递参数时设置了WH_MOUSE_LL鼠标事件,当有鼠标消息时则通过MouseProc鼠标回调函数执行,

#include <windows.h>
#include <iostream>
#include <conio.h>using namespace std;// 钩子句柄
HHOOK keyboardHook = 0;// 鼠标钩子
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{LPMSLLHOOKSTRUCT p = (LPMSLLHOOKSTRUCT)lParam;POINT   pt = p->pt;DWORD   mouseData = p->time;const char* info = NULL;char text[60], pData[50], mData[50];PAINTSTRUCT ps;HDC hdc;if (nCode >= 0){if (wParam == WM_MOUSEMOVE){info = "鼠标 [移动]";}else if (wParam == WM_LBUTTONDOWN){info = "鼠标 [左键] 按下";}else if (wParam == WM_LBUTTONUP){info = "鼠标 [左键] 抬起";}else if (wParam == WM_LBUTTONDBLCLK){info = "鼠标 [左键] 双击";}else if (wParam == WM_RBUTTONDOWN){info = "鼠标 [右键] 按下";}else if (wParam == WM_RBUTTONUP){info = "鼠标 [右键] 抬起";}else if (wParam == WM_RBUTTONDBLCLK){info = "鼠标 [右键] 双击";}else if (wParam == WM_MBUTTONDOWN){info = "鼠标 [滚轮] 按下";}else if (wParam == WM_MBUTTONUP){info = "鼠标 [滚轮] 抬起";}else if (wParam == WM_MBUTTONDBLCLK){info = "鼠标 [滚轮] 双击";}else if (wParam == WM_MOUSEWHEEL){info = "鼠标 [滚轮] 滚动";}ZeroMemory(text, sizeof(text));ZeroMemory(pData, sizeof(pData));ZeroMemory(mData, sizeof(mData));std::cout << "鼠标状态: " << info << std::endl;std::cout << "X: " << pt.x << " Y: " << pt.y << std::endl;std::cout << "附加数据: " << mouseData << std::endl;}return CallNextHookEx(NULL, nCode, wParam, lParam);
}int main(int argc, char* argv[])
{// 安装钩子keyboardHook = SetWindowsHookEx(WH_MOUSE_LL,MouseProc,GetModuleHandleA(NULL),NULL);if (keyboardHook == 0){cout << "挂钩鼠标失败" << endl; return -1;}MSG msg;while (1){// 如果消息队列中有消息if (PeekMessageA(&msg,NULL,NULL,NULL,PM_REMOVE)){// 把按键消息传递给字符消息TranslateMessage(&msg);// 将消息分派给窗口程序DispatchMessageW(&msg);}elseSleep(0);}UnhookWindowsHookEx(keyboardHook);return 0;
}

读者可自行编译并运行上述代码片段,当挂钩后我们就可以看到鼠标的移动位置以及鼠标击键情况,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/bdb59f93.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/150767.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud Gateway2之路由详解

Spring Cloud Gateway路由 文章目录 1. 前言2. Gateway路由的基本概念3. 三种路由1. 静态路由2. 动态路由1. 利用外部存储2. API动态路由 3. 服务发现路由(自动路由)3.1. 配置方式3.2 自动路由&#xff08;服务发现&#xff09;原理核心源码GatewayDiscoveryClientAutoConfigur…

机器学习算法基础--KNN分类算法

文章目录 1.KNN算法原理介绍2.KNN分类决策原则3.KNN度量距离介绍3.1.闵可夫斯基距离3.2.曼哈顿距离3.3.欧式距离 4.KNN分类算法实现5.KNN分类算法效果6.参考文章与致谢 1.KNN算法原理介绍 KNN&#xff08;K-Nearest Neighbor&#xff09;工作原理&#xff1a; 在一个存在标签的…

Springcloud支付模块

客户端消费者80 order&#xff08;订单模块&#xff09; 微服务提供者8001 payment&#xff08;支付模块&#xff09; 订单模块可以调动支付模块 步骤&#xff1a; 1、建moudle 2、改写pom 3、写yml 4、主启类 5、业务类 1、建父工程&#xff08;创建spri…

架构师选择题--计算机网络

架构师选择题--计算机网络 22年考题21年考题20年考题19年真题2017考题 22年考题 d http:80 https:httpssl &#xff1a;443 b b pop3是邮件接收协议&#xff1a;110 SMTP是邮件发送协议&#xff1a;25 http:80 A 网络隔离&#xff1a;防火墙&#xff08;逻辑&#xff09;&…

C++(反向迭代器)

前言&#xff1a; 上一章我们介绍了适配器&#xff0c;也提了一下迭代器适配器&#xff0c;今天我们就从反向迭代器把迭代器适配器给解释一下。 既然 都叫迭代器容器了 就说名只要接口合适他可以封装实现各种容器需求包括vector list 。 目录 1.反向迭代器设计 1.1反向迭代…

Spring面试题学习: 单例Bean是单例模式吗?

单例Bean是单例模式吗 学习背景答案扩展知识单例模式Spring BeanJava Bean单例Bean 个人评价我的回答 学习背景 想换工作. 学习记录, 算是一个输出. 答案 通常来说, 单例模式是指在一个JVM中, 一个类只能构造出一个对象. 有很多方法来实现单例模式, 比如饿汉模式. 但是我们通…

【JVM】 类加载机制、类加载器、双亲委派模型详解

文章目录 前言一、类加载机制二、类加载器三、双亲委派模型总结 前言 &#x1f4d5;各位读者好, 我是小陈, 这是我的个人主页 &#x1f4d7;小陈还在持续努力学习编程, 努力通过博客输出所学知识 &#x1f4d8;如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽 &#x1f4d…

配置文件生成器-秒杀SSM的xml整合

配置文件生成器-秒杀SSM的xml整合 思路&#xff1a; 通过简单的配置&#xff0c;直接生成对应配置文件。 maven坐标 <dependencies><!-- 配置文件生成 --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker<…

Java中栈实现怎么选?Stack、Deque、ArrayDeque、LinkedList(含常用Api积累)

目录 Java中的Stack类 不用Stack有以下两点原因 1、从性能上来说应该使用Deque代替Stack。 2、Stack从Vector继承是个历史遗留问题&#xff0c;JDK官方已建议优先使用Deque的实现类来代替Stack。 该用ArrayDeque还是LinkedList&#xff1f; ArrayDeque与LinkList区别&#xff1…

mysql MVCC(多版本并发控制)理解

最近看MVCC相关资料&#xff0c;这边做一个记录总结&#xff0c;方便后续理解。 目录 一、MVCC相关概念 二、MVCC实现原理 1.隐藏字段 2.undo log 3.Read View 4.MVCC的整体处理流程 5. RC&#xff0c;RR级级别下的innoDB快照读有什么不同 6.总结 一、MVCC相关概念 1…

Mysql——创建数据库,对表的创建及字段定义、数据录入、字段增加及删除、重命名表。

一.创建数据库 create database db_classics default charsetutf8mb4;//创建数据库 use db_classics;//使用该数据库二.对表的创建及字段定义 create table if not exists t_hero ( id int primary key auto_increment, Name varchar(100) not null unique, Nickname varchar(1…

机器视觉工程师,公司设置奖金,真的为了奖励你吗?其实和你没关系

​据说某家大厂&#xff0c;超额罚款&#xff0c;有奖有罚很正常&#xff0c;但是我觉得你罚款代理商员工就不一样了&#xff0c;把代理商当成你的员工&#xff0c;我就觉得这些大厂的脑回路有问题。 有人从来没听说过项目奖金&#xff0c;更没有奖金。那么为什么设置奖金呢&a…

深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(二)

前言 在上一篇深入浅出&#xff0c;SpringBoot整合Quartz实现定时任务与Redis健康检测&#xff08;一&#xff09;_往事如烟隔多年的博客-CSDN博客 文章中对SpringBoot整合Quartz做了初步的介绍以及提供了一个基本的使用例子&#xff0c;因为实际各自的需求任务不尽相同因此并…

Neo4j深度学习

Neo4j的简介 Neo4j是用Java实现的开源NoSQL图数据库。从2003年开始开发&#xff0c;2007年正式发布第一版&#xff0c;其源码托管于GitHtb。Neo4j作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理、软件分析、组织和…

电梯安全监测丨S271W无线水浸传感器用于电梯机房/电梯基坑水浸监测

城市化进程中&#xff0c;电梯与我们的生活息息相关。高层住宅、医院、商场、学校、车站等各种商业体建筑、公共建筑中电梯为我们生活工作提供了诸多便利。 保障电梯系统的安全至关重要&#xff01;特别是电梯机房和电梯基坑可通过智能化改造提高其安全性和稳定性。例如在暴风…

参与现场问题解决总结(Kafka、Hbase)

一. 背景 Kafka和Hbase在现场应用广泛&#xff0c;现场问题也较多&#xff0c;本季度通过对现场问题就行跟踪和总结&#xff0c;同时结合一些调研&#xff0c;尝试提高难点问题的解决效率&#xff0c;从而提高客户和现场满意度。非难点问题&#xff08;历史遇到过问题&#xf…

乌班图22.04 kubeadm简单搭建k8s集群

1. 我遇到的问题 任何部署类问题实际上对于萌新来说都不算简单&#xff0c;因为没有经验&#xff0c;这里我简单将部署的步骤和想法给大家讲述一下 2. 简单安装步骤 准备 3台标准安装的乌班图server22.04&#xff08;采用vm虚拟机安装&#xff0c;ip为192.168.50.3&#xff0…

Flutter横屏实践

1、Flutter设置横屏 // 强制横屏 SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft,DeviceOrientation.landscapeRight ]); // 强制竖屏 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);另…

chromedriver下载与安装方法

下载与安装: 1.查看Chrome浏览器版本 首先&#xff0c;需要检查Chrome浏览器的版本。请按照以下步骤进行&#xff1a; 打开Chrome浏览器。 点击浏览器右上角的菜单图标&#xff08;三个垂直点&#xff09;。 选择“帮助”&#xff08;Help&#xff09;。 在下拉菜单中选择“…

el-table进阶(每条数据分行或合并)

最麻烦的还是css样式&#xff0c;表格样式自己调吧 <!-- ——————————————————————————————————根据数据拓展表格—————————————————————————————————— --> <div style"display: flex"&…