VC++/MFC消息映射机制(4):附:钩子函数原理

VC++/MFC消息映射机制(4):附:钩子函数原理

若对C++语法不熟悉,建议参阅《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解。

一、钩子SetWindowsHookEx

注意:本文的钩子和钩子函数是两个概念,请不要搞混淆了。
1、作用(重点):钩子主要作用是用于拦截消息。在消息发出还未到达目的窗口之前,钩子函数能先于系统捕获(拦截)到该消息,这时钩子函数就可以对拦截到的消息进行处理。
2、钩子分类:钩子按所拦截到的消息类型进行分类,比如键盘钩子(拦截键盘消息),鼠标钩子等。若拦截的是某个线程的钩子则称为线程钩子,若是拦截的所有线程的钩子则称为全局(系统)钩子。
3、因为钩子能拦截除了本线程之外的其他线程的消息(比如全局钩子),这就给木马病毒用以获取用户的键盘鼠标消息制造了机会。
4、钩子链表:SetWindowsHookEx可以安装多个类型的钩子,这就形成了一个钩子链表,该表由系统负责维护,其结构是先进后出原则,即后安装的钩子先获得控制权。钩子链表保存了钩子相关的信息。使用完钩子之后应进行卸载以释放资源。注意:钩子在卸载时的顺序并不遵守这个规则,当钩子被卸载时,系统便释放其占用的内存,然后更新整个钩子链表。
5、钩子函数:也被称为钩子子程,被钩子拦截到的消息在钩子函数中进行处理,钩子函数在SetWindowsHookEx函数中指定。钩子函数是一个由用户定义的回调函数,也就是说用户可以对拦截到的消息作出任何的处理。
6、钩子函数处理消息的步骤:
1)、首先使用函数SetWindowsHookEx安装需要拦截何种类型的钩子和钩子函数到钩子链表中。
2)、若拦截到指定的消息,就把该消息交给钩子链表中最开头的钩子函数进行处理。
3)、重点:钩子函数若返回非0值,则表示丢弃对该消息的处理,并阻止该消息的传递,这样目标窗口将不会再接收到该消息,使用此方法可以屏蔽掉某种消息。
4)、若要把该消息传递给钩子链表中的下一个钩子函数进行处理,则需要调用CallNextHookEx函数。
5)、使用UnhookWindowsHookEx函数可以移除(卸载)安装的钩子,并释放相应的资源。

二、与钩子有关的函数

1、SetWindowsHookEx原型(winuser.h):

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadID)
 示例:SetWindowsHookEx(WH_MOUSE, g, 0, ::GetCurrentThreadId()); 
表示拦截当前线程的鼠标消息(即WH_MOUSE),并把消息交给钩子函数g进行处理。其中::GetCurrentThreadId()表示返回当前线程的ID。第3个实参0,表示这是一个线程钩子。
   1)、idHook:需要安装的钩子的类型,即需要被拦截的消息的类型,在MSDN中共有15种钩子类型,详见表3.2
   2)、lpfn:是指向钩子函数的函数指针,表明调用SetWindowsHookEx安装钩子时,需要传递一个其类型为HOOKPROC的钩子函数地址作为实参,HOOKPROC的定义见后文。
   3)、hMod:由形参lpfn所指向的钩子函数所在的DLL文件的句柄,若第4个形参dwThreadID指定的是当前进程创建的线程,且相应的钩子函数定义于当前进程的相关代码中,则此参数的值必须被设置为NULL(即值0)。
   4)、dwThreadID:需要被拦截消息的线程标识,若指定的是明确的线程,则是线程专用钩子,若值为0,则表示安装的钩子是全局(或系统)钩子,指定全局钩子时,钩子函数必须包含在DLL(动态链接库)中,。
   5)、返回值:若函数成功,则返回所安装的钩子函数的句柄(即类型为HHOOK的变量),否则返回NULL。类型HHOOK是一个钩子句柄,其最后定义为一个结构体类型(与窗口句柄HWND等其他句柄是类似的)

2、HOOKPROC(即钩子函数的原型)定义为(winuser.h)

typedef LRESULT (CALLBACK* HOOKPROC)(int code, WPARAM wParam, LPARAM lParam)
1)、LRESULT的类型为long
2)、CALLBACK是一个stdcall调用方式,在此处表明该函数是一个回调函数。注意:在定义钩子函数时,此关键字不能省略。
3)、code:该值的取值与SetWindowsHookEx函数的第一个形参idHook(即钩子类型)有关,根据idHook的取值不同(或钩子类型的不同),code可以取不同的值,比如若idHook的值为WH_CBT时,其code值可取HCBT_CLICKSKIPPED、HCBT_CREATEWND等。其具体的取值详见MSDN或后面章节的内容。
   4)、wParam和lParam:这两个参数的取值与code有关联,此处暂不讲解。

3、UnhookWindowsHookEx原型(winuser.h):

UnhookWindowsHookEx(HHOOK hhk)
功能:卸载(移除)钩子hhk,形参hhk可以是SetWindowsHookEx的返回值。

4、CallNextHookEx原型(winuser.h):

CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam);
   1)、作用:把消息传递给钩子链表中的下一下钩子函数处理,若不使用该函数,则其他安装了钩子的应用程序将不能接收到此钩子的通知。
   2)、hhk:表示当前的钩子句柄,该句柄可以是SetWindowsHookEx的返回值。
   3)、nCode:表示需要传递给下一个钩子函数处理的钩子类型(即消息类型)
   4)、wParam和lParam:这两个形参的值与nCode有关联。

在这里插入图片描述

示例3.4:钩子函数的使用
#include <afxwin.h>   //编写MFC程序,必须包含此头文件
HHOOK hk;    //用于保存安装钩子后的钩子句柄。
LRESULT CALLBACK f(HWND h,UINT u,WPARAM w, LPARAM l)  /*过程函数,对于键盘消息,形参w保存的是键盘按下的按键ASCII码。*/
{ switch(u){
case WM_KEYDOWN: {//当按下键盘上的按键时执行以下消息,除按下F键外,其他按键会被钩子拦截。
			if(w==0x41){::MessageBox(NULL,"AAA","",0);} //按下按键A时弹出对话框
			if(w==0x42){::MessageBox(NULL,"BBB","",0);}//按下按键B时弹出对话框
			if(w==0x43){::MessageBox(NULL,"CCC","",0);}
			if(w==0x44){::MessageBox(NULL,"DDD","",0);}
			if(w==0x45){::MessageBox(NULL,"EEE","",0);}
			if(w==0x46){::MessageBox(NULL,"FFF","",0);}//按下按键F
			break;	}
	case WM_DESTROY:{	PostQuitMessage(0);		break;}
	default:return ::DefWindowProc(h,u,w,l);	}	return 0; }   //f结束
//钩子函数g
LRESULT CALLBACK g(int code, WPARAM w, LPARAM l){  //钩子函数的返回类型CALLBACK不能省略。
	if(w==0x41) {					//拦截按键A的消息
::MessageBox(NULL,"A--","",0);  //弹出消息框
return 1;}     //返回非零值表示阻塞该消息传递到目标窗口
	if(w==0x42) {::MessageBox(NULL,"B--","",0);//拦截按键B的消息,
return 0;}    //返回0,表示不阻塞该消息传递到目标窗口
	if(w==0x43){::MessageBox(NULL,"C--","",0);//拦截按键C的消息
UnhookWindowsHookEx(hk); //移除(卸载)钩子链表中安装的钩子函数g(即本钩子函数)。
return 0;}    //不阻塞消息
	if(w==0x44) {			//拦截按键D的消息
CallNextHookEx(hk, code,w, l);/*把拦截到的消息传递给钩子链表中的下一个钩子函数(本例为gg函数)进行处理*/
return 0; } 
	return 0;}   //不阻塞其他按键消息传递到目标窗口
//钩子函数gg
LRESULT CALLBACK gg(int code, WPARAM w, LPARAM l){
if(w==0x44) {::MessageBox(NULL,"D--","",0);return 1;}  /*拦截按键D的消息,并阻塞该消息传递到目标窗口*/
	if(w==0x45) {::MessageBox(NULL,"E--","",0);return 1;}
return 1; } //阻塞其他按键消息传递到目标窗口,gg结束
//创建窗口
class B:public CFrameWnd{public:B(){Create("HH","H",WS_OVERLAPPEDWINDOW);} 
BOOL CreateEx(       //重定义CWnd::CreateEx创建自定义的窗口
DWORD dwExStyle, LPCTSTR lpszClassName,	LPCTSTR lpszWindowName, 
DWORD dwStyle,int x, int y, int nWidth, int nHeight,
				HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{	HINSTANCE hs=AfxGetModuleState()->m_hCurrentInstanceHandle;
		WNDCLASSEX w;  
		memset(&w,0,sizeof(w)); 	 //把结构体w的所有成员的值初始化为。
		w.hInstance=hs;  	w.cbSize=sizeof(w);  	w.lpszClassName="HH"; 	
		w.lpfnWndProc=f;  //设置过程函数为f,本示例需要该过程函数进行演示
		::RegisterClassEx(&w); 
		HWND h=::CreateWindow("HH","H",WS_OVERLAPPEDWINDOW,0,0,350,280,0,0,hs,0);  
		::ShowWindow(h,1);  ::UpdateWindow(h);    //重载CreateEx创建的窗口应在此处显示
		return 1;}  };  //类B结束
class A:public CWinApp{public:
	BOOL InitInstance(){	m_pMainWnd=new B(); 
		//安装钩子拦截键盘消息,后安装的钩子函数先执行,即钩子函数g先于gg执行。
		hk=::SetWindowsHookEx(WH_KEYBOARD,gg,0,::GetCurrentThreadId());
		hk=::SetWindowsHookEx(WH_KEYBOARD,g,0,::GetCurrentThreadId());
		return 1;		}	};   //类A结束
A ma;

程序运行结果分析
重要:每次重新按键时,都需要关闭弹出的所有消息框,然后再按键,因为弹出的消息框也会接收到键盘消息,此消息同样会被钩子函数拦截到,本示例主要用于说明钩子函数的几个原理,关闭消息框可避免复杂性。
情形1:未被钩子拦截的消息,只会弹出类似情形1的消息框,被钩子拦截到的消息会弹出类似情形2含“C–”字符的消息框。
在这里插入图片描述
情形2:验证钩子函数执行顺序:后安装的钩子函数g先于先安装的钩子函数gg执行。因此本示例把拦截到的按键消息传递到钩子函数g去执行,
验证方式,请重新执行程序(重要),然后按以下方式按键盘进行验证(此处仅对函数g和gg的执行顺序进行讨论,其他情形的讨论详见后文)
1)、按下键盘ABD,都会弹出的类似情形二的消息框,证明钩子函数g被执行。
2)、然后关闭所有消息框,按键盘E,弹出类似情形1的消息框,说明执行的是过程函数f,钩子函数gg未被执行。
3)、以上步骤顺序无关紧要,按下按键时弹出的消息框也不止一个。
4)、最好不要按键盘C,因为C会卸载掉钩子函数g ,从而使g不能再接收到由钩子拦截到的消息。
5)、注意:若没有使用CallNextHookEx函数把消息传递给钩子链表中的下一个钩子函数,下一个钩子函数不会接收到消息,这也是为什么按下键盘E,没有执行钩子函数gg弹出消息框的原因。
情形3:验证钩子函数返回NULL(值0)未阻塞消息(图3.2)。
重新执行程序(重要),按以下方式按键盘进行验证(不要连续按着键盘不放,因为这样会连续重复发送消息):
按下键B,此时执行钩子函数g中if(w==0x42)之后的程序,弹出两个内容为“B–”的消息框(按下和弹起按键各弹出一个)。因为此时过程函数g返回0表示未阻塞按键B的消息传递到目标窗口,因此把这两个消息框关闭之后(此时钩子函数g才执行完毕),执行过程函数f中的语句,并弹出一个内容为“BBB”的消息框。
在这里插入图片描述

情形4:验证钩子函数返回非零值阻塞消息(图3.3)。
重新执行程序(重要),按以下方式按键盘进行验证(不要连续按着键盘不放):
按下键A,此时执行钩子函数g中if(w==0x41)之后的程序,弹出两个内容为“A–”的消息框(按下和弹起按键各弹出一个)。因为此时过程函数g返回1表示阻塞按键A的消息传递到目标窗口,因此把这两个消息框关闭之后,没有执行过程函数f中的语句,程序不会弹出一个内容为“AAA”的消息框。
在这里插入图片描述

情形5:卸载钩子。重新执行程序(重要),然后按以下方式按键盘进行验证(不要连续按着键盘不放,因为这样会连续重复发送消息):
1)、首先按下键F,弹出内容为“FFF”的消息框。此时执行的是钩子函数g,因为F未与g中的任何if语句匹配,因此钩子函数g返回0,不阻塞该消息,执行过程函数f弹出消息框。
2)、关闭弹出的消息框之后按下键C,此时弹出三个消息框,并卸载钩子函数g,消息框分别由钩子函数g和过程函数f产生。
3)、按下键C卸载钩子函数g之后(需关闭完所有消息框),由钩子拦截到的键盘消息现在由钩子函数gg负责处理(因为卸载钩子函数之后钩子链表会被更新,更新之后的钩子链表只有钩子函数gg了),因此按下D和E时会弹出由gg产生的消息框,除此之外的其他按键都没有反应,因为gg对其他按键返回的都是非零值,其消息被阻塞了。
情形7:传递钩子函数。重新执行程序(重要),然后按以下方式按键盘进行验证(不要连续按着键盘不放,因为这样会连续重复发送消息):
1)、按下键D,弹出三个消息框,分别由钩子函数gg和过程函数f产生。此时钩子函数g把接收到的按键D消息传递给钩子链表中的下一个钩子函数gg进行处理,因此首先由gg产生两个消息框,因为钩子函数g此时返回值0,未阻塞消息,所以再把此按键消息传递给过程函数f处理,因此关闭这两个消息框之后(此时钩子函数g才执行完毕),弹出一个由过程函数f产生的消息框。
2)、按下键E,弹出一个由过程函数f产生的内容为“EEE”的消息框。因为此时处理由钩子拦截到的键盘消息的钩子函数是g,但g并未对该消息进行处理,也未调用CallNextHookEx函数把消息传递给钩子链表中的下一个钩子函数gg进行处理,因此钩子函数gg不能接收到该按键消息,最后g返回0,未阻塞该消息,因此最后把该按键消息交给过程函数f处理,从而产生一个内容为“EEE”的消息框。

本文作者:黄邦勇帅(原名:黄勇)

猜你喜欢

转载自blog.csdn.net/hyongilfmmm/article/details/83141152
今日推荐