VC++深入详解:钩子函数
这得从Windows消息机制说起:当在应用程序窗口内点击鼠标左键时,操作系统会感知这一事件,然后把消息放到应用程序的消息响应队列中,应用程序通过GetMessage读取消息,然后通过DispatchMessage将消息调度给操作系统,操作系统会调用在设计窗口类时指定的应用程序窗口过程函数对消息进行处理。
假如我们希望对某个特殊的消息进行屏蔽,比如希望这个应用程序不响应回车和空格消息,就需要截获所有消息,然后进行判断,如果是这两种消息,则将它们屏蔽掉。为了实现这个功能,我们可以安装一个HOOK过程,在此过程中检查,再决定是否放行该消息。
我们先看代码。在对话框应用程序的OnInitDialog中设置一个钩子过程:
- g_hMouse = SetWindowsHookEx(WH_MOUSE, //截获鼠标消息
- MouseProc, //钩子函数
- NULL, //指定的线程
- GetCurrentThreadId()) //当前线程
其中g_hMouse是用来存放钩子过程句柄的全局变量函数,而MouseProc则是与鼠标消息相对应的钩子函数,如果这个函数返回非0值,则表示以对其进行了处理,系统将不会把这消息传递给窗口过程函数了。我们这里只是简单地让其返回1:
- LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
- {
- return 1;
- }
下面我们屏蔽键盘的空格键消息:首先设置一个钩子
- g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD,
- KeyboardProc,
- NULL,
- GetCurrentThreadId());
- LRESULT CALLBACK KeyboardProc( int code, WPARAM wParam, LPARAM lParam )
- {
- if(VK_SPACE == wParam)
- {
- return 1;
- }
- else
- {
- return CallNextHookEx(g_hKeyboard,code,wParam,lParam);
- }
- }
假如我们想屏蔽Alt+F4键,那么可以
- if(VK_F4 == wParam && (1 == (lParam>>29 &1)))
假如我们想实现屏蔽其他键,但是按F2键程序退出,那么我们应该在按键响应中判断是否为F2键,如果是就该发送退出消息。可是发送消息时,需要获取窗口的句柄,这可怎么办好呢?无奈之下,我们只能使用一个全局句柄,并在OnInitDialog将其设为m_hWnd。
钩子函数的内容改为
- if(VK_F2 == wParam)
- {
- ::SendMessage(g_hWnd,WM_CLOSE,0,0);
- UnhookWindowsHookEx(g_hKeyboard);
- UnhookWindowsHookEx(g_hMouse);
- }
- return 1;
- #include <windows.h>
- HHOOK g_hMouse = NULL;
- LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
- {
- return 1;
- }
- void SetHook()
- {
- g_hMouse = SetWindowsHookEx(WH_MOUSE, //鼠标消息
- MouseProc, //钩子函数
- GetModuleHandle("Hook"), //动态链接库句柄
- 0); //与所有线程相关联
- }
并为其指定模块定义文件,将导出函数名设为SetHook,序号为2
- LIBRARY CH_20_HOOk
- EXPORTS
- SetHook @2
下面我们在动态链接库中增加一个钩子函数,屏蔽键盘消息。首先,我们不能屏蔽所有的键盘消息,否则我们将无法退出程序,只能强者重新启动了。我们可以为键盘消息留一个F2键,当按下F2键时,程序将自动退出。程序的退出可以通过发送WM_CLOSE消息来实现,但是发送消息时需要获得当前窗口的句柄,这可如何是好呢?我们可以把窗口的句柄当做参数传递进去,然后在dll中用一个全局变量接收,然后在钩子函数中就能获得这个句柄了:
- #include <windows.h>
- HHOOK g_hMouse = NULL;
- HHOOK g_hKeyboard = NULL;
- HWND g_hWnd;
- LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
- {
- return 1;
- }
- LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam, LPARAM lParam )
- {
- if(VK_F2 == wParam)
- {
- SendMessage(g_hWnd,WM_CLOSE,0,0);
- UnhookWindowsHookEx(g_hMouse);
- UnhookWindowsHookEx(g_hKeyboard);
- }
- return 1;
- }
- void SetHook(HWND hwnd)
- {
- g_hWnd = hwnd;
- g_hMouse = SetWindowsHookEx(WH_MOUSE, //鼠标消息
- MouseProc, //钩子函数
- GetModuleHandle("CH_20_Hook"), //动态链接库句柄
- 0); //与所有线程相关联
- g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD,
- KeyboardProc,
- GetModuleHandle("CH_20_Hook"),
- 0);
- }
当运行程序时,在窗口激活时按下F2,程序就能退出,而当在别的窗口下按下F2,却不能退出。这与我们之前对dll的理解有一些出入:之前提到,当dll被多个进程使用时,这些进程可以共享DLL的代码和数据。因此dll中的全局变量g_hWnd应该是被所有进程所共享的。在其他进程下按下F2,也能够退出。可事实上并不是这样,这是为什么呢?这是因为Windows采用了写入时复制机制,当DLL被两个进程共享时,如果第二个进程想要修改其中的数据,那么他将会复制一份新的页面。这样做是为了安全着想,因为假设DLL中的变量时一个指针,第一个调用dll的进程修改了它的话,当第二个进程调用它时,指针可能已经指向了某个其他的地方。
如果非要让全局句柄g_hWnd可以被共享的话,可以将它放到一个共享的节中:
- #pragma data_seg("MySec")
- HWND g_hWnd = NULL;
- #pragma data_seg()
- #pragma comment(linker,"/section:MySec,RWS")
SECTION HEADER #5
MySec name
104 virtual size
37000 virtual address
1000 size of raw data
35000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
D0000040 flags
Initialized Data
Shared
Read Write
此时,在任意窗口下按F2,都能关闭程序了。
除了使用#pragma comment来指定节的属性外,我们也可以在模块定义文件中设定共享节的属性:
- SEGMENTS
- MySec READ WRITE SHARED
本文详细介绍了HOOK编程的概念及其实现方式,通过具体的代码示例解释了如何使用HOOK来拦截和处理Windows应用程序中的鼠标和键盘消息。
7813

被折叠的 条评论
为什么被折叠?



