C++程序调用SetWindowsHookEx全局拦截键盘按键消息和窗口消息的Hook实例分享

目录

1、Hook与Hook过程函数

2、SetWindowsHookEx函数说明

3、Hook实例1 - 使用SetWindowsHookEx在程序中拦截键盘消息

4、Hook实例2 - 使用SetWindowsHookEx在程序中拦截某个窗口的消息

5、最后


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(专栏文章已更新400多篇,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.htmlC++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       本文详细介绍一下可以给应用程序挂Hook钩子的系统API函数SetWindowsHookEx,并给出两个项目中使用的实例,以供大家借鉴和参考。

1、Hook与Hook过程函数

       Hook是应用程序截获消息、鼠标操作和击键等事件的机制。截获特定类型的事件的函数称为Hook过程函数。在Hook过程函数中,可以对其接收的每个事件执行操作,然后修改或放弃该事件。

       Windows系统支持许多不同类型的Hook,每种类型都提供对其消息处理机制的不同方面的访问,可以为一个类型的Hook指定一个Hook过程函数。比如,应用程序可以使用Hook类型WH_MOUSE来监视鼠标消息,在对应的Hook过程函数中处理拦截到的消息。

       可以通过调用 SetWindowsHookEx 函数并指定调用该过程的挂钩类型来安装挂钩过程,该过程是应与调用线程位于同一桌面中的所有线程相关联,还是与特定线程相关联,以及指向过程入口点的指针。

2、SetWindowsHookEx函数说明

       SetWindowsHookEx 是用于在Windows操作系统中设置全局或本地的钩子(hook)。钩子是一种用于监视并拦截特定事件或消息的机制,通常用于拦截和处理键盘输入、鼠标操作、窗口消息等。SetWindowsHookEx 允许你安装一个全局或本地的钩子过程,以便在事件发生时执行自定义的代码。

      SetWindowsHookEx的声明如下:

HOOK SetWindowsHookEx(
  [in] int       idHook,
  [in] HOOKPROC  lpfn,
  [in] HINSTANCE hmod,
  [in] DWORD     dwThreadId
);

参数说明:

1)idHook:指定要安装的钩子类型。可以是以下之一:

含义

WH_CALLWNDPROC

4

安装一个挂钩过程,用于在系统将消息发送到目标窗口过程之前监视消息。 有关详细信息,请参阅 CallWndProc 挂钩过程。

WH_CALLWNDPROCRET

12

安装一个挂钩过程,该过程在目标窗口过程处理消息后监视消息。 有关详细信息,请参阅 [HOOKPROC 回调函数] (nc-winuser-hookproc.md) 挂钩过程。

WH_CBT

5

安装用于接收对 CBT 应用程序有用的通知的挂钩过程。 有关详细信息,请参阅 CBTProc 挂钩过程。

WH_DEBUG

9

安装可用于调试其他挂钩过程的挂钩过程。 有关详细信息,请参阅 DebugProc 挂钩过程。

WH_FOREGROUNDIDLE

11

安装将在应用程序的前台线程变为空闲状态时调用的挂钩过程。 此挂钩可用于在空闲时间执行低优先级任务。 有关详细信息,请参阅 ForegroundIdleProc 挂钩过程。

WH_GETMESSAGE

3

安装用于监视发布到消息队列的消息的挂钩过程。 有关详细信息,请参阅 GetMsgProc 挂钩过程。

WH_JOURNALPLAYBACK

1

 警告

Windows 11及更新版本:不支持日记挂钩 API。 建议改用 SendInput TextInput API。

安装一个挂钩过程,该过程发布以前由 WH_JOURNALRECORD 挂钩过程记录的消息。 有关详细信息,请参阅 JournalPlaybackProc 挂钩过程。

WH_JOURNALRECORD

0

 警告

Windows 11及更新版本:不支持日记挂钩 API。 建议改用 SendInput TextInput API。

安装一个挂钩过程,用于记录发布到系统消息队列的输入消息。 此挂钩可用于记录宏。 有关详细信息,请参阅 JournalRecordProc 挂钩过程。

WH_KEYBOARD

2

安装用于监视击键消息的挂钩过程。 有关详细信息,请参阅 KeyboardProc 挂钩过程。

WH_KEYBOARD_LL

13

安装用于监视低级别键盘输入事件的挂钩过程。 有关详细信息,请参阅 [LowLevelKeyboardProc] (/windows/win32/winmsg/lowlevelkeyboardproc) 挂钩过程。

WH_MOUSE

7

安装监视鼠标消息的挂钩过程。 有关详细信息,请参阅 MouseProc 挂钩过程。

WH_MOUSE_LL

14

安装用于监视低级别鼠标输入事件的挂钩过程。 有关详细信息,请参阅 LowLevelMouseProc 挂钩过程。

WH_MSGFILTER

-1

安装挂钩过程,用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。 有关详细信息,请参阅 MessageProc 挂钩过程。

WH_SHELL

10

安装一个挂钩过程,用于接收对 shell 应用程序有用的通知。 有关详细信息,请参阅 ShellProc 挂钩过程。

WH_SYSMSGFILTER

6

安装挂钩过程,用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。 挂钩过程监视与调用线程位于同一桌面中的所有应用程序的消息。 有关详细信息,请参阅 SysMsgProc 挂钩过程。

根据自己需要拦截的对象的类型,选择对应的钩子类型。 比如本文中要拦截键盘的按键消息,选择WH_KEYBOARD_LL;要拦截某个窗口的消息,选择WH_GETMESSAGE。

2)lpfn:指定一个回调函数的地址(挂钩过程函数),当特定事件发生时,该函数将被调用。每种类型的钩子,对应的回调函数的声明是不同的,可以参见上述表格中的说明,点击挂钩过程函数的说明链接去查看。

3)hMod:指定包含钩子函数的模块句柄。通常情况下,可以将其设置为 NULL,表示使用当前进程的模块句柄。

4)dwThreadId:指定要关联钩子的线程ID。如果是全局钩子,可以将其设置为 0,将钩子应用到所有线程。

返回值

       SetWindowsHookEx 返回一个非零值,表示成功安装钩子,或者返回 NULL 表示安装失败。安装成功后,钩子函数将开始监视并处理指定类型的事件。

3、Hook实例1 - 使用SetWindowsHookEx在程序中拦截键盘消息

       要在程序中拦截键盘按键消息,选择钩子类型,对应的钩子过程处理函数的声明为:

LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)

相关代码如下:(在钩子过程处理函数中拦截ESC按键、CTrl+W按键消息等,按下这些按键时程序要做一些操作的)

//1、安装监控键盘消息的hook
HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0);

// 2、钩子过程函数,就是调用SetWindowsHookEx设置的回调函数
LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
	// 对于拦截键盘消息的过程函数,此处的code只有HC_ACTION一个值
	if (code == HC_ACTION)
	{
		KBDLLHOOKSTRUCT *pLowLevelKBD = (KBDLLHOOKSTRUCT*)lParam;
		if (pLowLevelKBD->vkCode == VK_ESCAPE
			|| (pLowLevelKBD->vkCode == 0x57 && GetKeyState(VK_CONTROL) < 0) ) // 0x57 - 按键W
		{
			// 具体处理代码省略
		}
	}

	return CallNextHookEx(g_hHook, code, wParam, lParam);
}

// 3、卸载hook(利用SetWindowsHookEx返回的钩子句柄去卸载)
if (g_hHook != NULL)
{
	::UnhookWindowsHookEx(g_hHook);
	g_hHook = NULL;
}

       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到510多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏3:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到400多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


4、Hook实例2 - 使用SetWindowsHookEx在程序中拦截某个窗口的消息

       我们的软件在接收桌面共享图像时需要实现新的功能,需要做到以下几点细节:

1)要支持桌面共享图像的缩放;
2)当播放视频图像的窗口尺寸小于共享图像的分辨率尺寸时,要支持通过鼠标拖动,将不可见的区域拖出来显示;
3)要做到实时的拖动,用户一拖动鼠标就要立即响应,不能有明显的延迟。

       特别是第三点,不能有掩饰,但UI层到底层的音视频编解码层之间经过了好几层,函数调用是异步的,如果由UI层感知到用户鼠标拖动的坐标信息发给音视频编解码层,会有延时,体验会非常不好。并且拖动鼠标的过程中要频繁地调用接口给编解码层传递位置坐标信息,这样频繁的调用函数会有一定的开销,会拖慢程序的运行效率。

       首先,要拦截窗口的消息,可以选择使用WH_GETMESSAGE钩子类型。

       其次,这个显示远程发过来的桌面共享视频图像的窗口,在UI层,属于UI线程的,我们只需要拦截UI线程的窗口消息即可,所以通过UI层传过来的窗口句柄值,调用GetWindowThreadProcessId获取UI主线程的线程id,作为第四个参数传给SetWindowsHookEx。 

       给显示桌面共享视频图像的窗口所在的UI线程安装拦截消息的hook钩子

// 获得窗口所属进程ID和线程ID
DWORD dwProcessId = 0;
DWORD dwThreadId = GetWindowThreadProcessId(g_hDrawWnd, &dwProcessId);
DWORD dwThreadId2  = GetCurrentThreadId();
LogPrintf("window ProcessId: %d, window ThreadId: %d, current ThreadId: %d\n", dwProcessId, dwThreadId, dwThreadId2);

// 这段代码只能写在dll工程中
// 安装一个钩子子程,在系统将消息发送到目标窗口过程之前监视消息;
// WH_GETMESSAGE:可过滤和修改post消息;
// CallWndProc:处理函数
// 输入的线程ID 必须是创建窗口的线程ID,就是程序UI主线程的ID
g_hHook = SetWindowsHookEx( WH_GETMESSAGE, CallWndProc, 0, dwThreadId );
if (g_hHook == NULL)
{
    DWORD dwErrId = GetLastError();
    LogError("[quit] SetWindowsHookEx() failed. err=%d:%s\n", dwErrId, strerror(dwErrId));
    return FALSE;
}

       经过一番对友商软件相同功能的研究后,确定我们只要在钩子过程处理函数中拦截WM_LBUTTONDOWN鼠标左键按下、WM_LBUTTONUP鼠标左键弹起、WM_MOUSEMOVE鼠标移动等消息。

一次完整的拖动操作,从鼠标按下开始拖动,一直按着鼠标左键拖动鼠标,鼠标左键松开就完成了此次拖动操作。所以要关注WM_LBUTTONDOWN、WM_LBUTTONUP和WM_MOUSEMOVE等消息。

       窗口处理过程函数中拦截绘制桌面共享视频图像窗口消息的代码如下:

LRESULT CALLBACK CallWndProc(int code, WPARAM wParam, LPARAM lParam)
{
        if (g_nDecLogon == 9)
        {
                LogPrintf("------------CallWndProc\n");
        }
        //DWORD dwThreadId2  = GetCurrentThreadId(); //查看实际处理线程 是不是 UI主线程
        MSG* pWpsStruct =(MSG*)lParam; 
        switch ( pWpsStruct->message )
        {
        case WM_LBUTTONDOWN:
                if ( pWpsStruct->hwnd == g_hDrawWnd )  // 判断响应事件的窗口句柄
                {
                        if ( pWpsStruct->wParam ==  MK_LBUTTON) // 鼠标左键按下
                        {
                                g_bStartMove = true; 
                                g_tCursorPoint.x = LOWORD(pWpsStruct->lParam);
                                g_tCursorPoint.y = HIWORD(pWpsStruct->lParam);
                                if (g_nDecLogon == 9)
                                {
                                        LogPrintf("------------鼠标左键按下: client coordinate (%d,%d)\n", g_tCursorPoint.x, g_tCursorPoint.y);
                                }
                        }
                }
                break;
        case WM_LBUTTONUP: //鼠标左键松开
                {
                        g_bStartMove = false;
                        g_bTracking = false;
                        g_tCursorPoint.x = LOWORD(pWpsStruct->lParam); 
                        g_tCursorPoint.y = HIWORD(pWpsStruct->lParam);
                        if (g_nDecLogon == 9)
                        {
                                LogPrintf("------------鼠标左键松开: client coordinate (%d,%d)\n", g_tCursorPoint.x, g_tCursorPoint.y);
                        }
                }
                break;
        case WM_MOUSEMOVE: // 鼠标抓取移动
                if ( g_bStartMove )
                {
                        if (!g_bTracking)
                        {
                                TRACKMOUSEEVENT tme;
                                tme.cbSize = sizeof(tme);
                                tme.hwndTrack = g_hDrawWnd;
                                tme.dwFlags = TME_LEAVE;
                                tme.dwHoverTime = 1;
                                g_bTracking = TrackMouseEvent(&tme);
                        }
 
                        g_tCursorPoint.x = LOWORD(pWpsStruct->lParam); 
                        g_tCursorPoint.y = HIWORD(pWpsStruct->lParam);
                        if (g_nDecLogon == 9)
                        {
                                LogPrintf("------------鼠标抓取移动: g_bTracking=%d, cursor client coordinate (%d,%d)\n", g_bTracking, g_tCursorPoint.x, g_tCursorPoint.y);
                        }
                }
                break;
        case WM_MOUSELEAVE: 
                {
                        POINT point = {0};
                        RECT rcDlg = {0};
                        GetCursorPos(&point);
                        GetClientRect(g_hDrawWnd, &rcDlg);
                        ClientToScreen(g_hDrawWnd, (LPPOINT)&rcDlg);
                        ClientToScreen(g_hDrawWnd, ((LPPOINT)&rcDlg) + 1);
 
                        if ( !::PtInRect( &rcDlg, point ) )
                        {
                                // 拖动时,鼠标已离开双流画面,
                                int a = 0;
                        }
                        g_bStartMove = false;
                        g_bTracking = false;
                        g_tCursorPoint.x = LOWORD(pWpsStruct->lParam); 
                        g_tCursorPoint.y = HIWORD(pWpsStruct->lParam);
                        if (g_nDecLogon == 9)
                        {
                                LogPrintf("------------鼠标离开窗口: g_bTracking=%d, cursor client coordinate (%d,%d)\n", g_bTracking, g_tCursorPoint.x, g_tCursorPoint.y);
                        }
                }
                break;
        default:
                break;
        }
 
        return CallNextHookEx( g_hHook, code, wParam, lParam );
}

       关于这个实例的详细细节和说明,可以参见我之前写的文章:

通过SetWindowHookEx去实时拦截窗口消息,实现视频会议中桌面共享图像的实时拖动(附源码)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/139594004 

5、最后

        本文详细介绍了给程序安装钩子的SetWindowsHookEx,并给出了项目中使用SetWindowsHookEx的两个实例,有一定的参考价值,希望对大家能有所帮助。

  • 35
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值