利用DLL构建全局钩子

我们知道,Windows应用程序的工作本质是:消息基础,事件驱动。操作系统为应用程序维护这一个消息列表,应用程序则从这个表中取得消息,并作出相关反应。而钩子则可以监视这些消息,在应用程序获得消息之前截获之,并处理之,处理完了还可以决定该消息的去向。所以利用钩子可以完成很多特殊的功能。

钩子分线程钩子和全局钩子两种。线程钩子只监视某一特定线程的消息,而全局钩子则监视所有线程。全局钩子一般都写在dll中。

一、钩子的安装。

首先我们看一下安装钩子的函数:

HHOOK SetWindowsHookEx(
  int idHook,        // 钩子类型
  HOOKPROC lpfn,     // 钩子函数
  HINSTANCE hMod,    // 模块句柄
  DWORD dwThreadId   // 线程ID
);

这个函数四个参数:

1、idHook:钩子有很多中类型,WH_CBT,WH_CALLWNDPROC,WH_DEBUG,WH_JOURNALRECORD等等。本文主要讲比较常用的两种:WH_KEYBOARD和WH_MOUSE,分别是监视键盘事件和鼠标事件。

2、lpfn:钩子函数。这是一个我们自己定义的回调函数,当系统监视到我们需要的事件时,就会调用该函数。该函数原型如下:

LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)
{
	//wParam和lParam为所钩的消息的参数,nCode与钩子的类型有关
	return CallNextHookEx(hook,nCode,wParam,lParam);
}

这个函数的名字HookPrco可以自己任意取。nCode值与不同的钩子类型相关,wParam和lParam的值和不同的窗口消息相关。这里主要讲下WH_KEYBOARD和WH_MOUSE两个钩子。

1)钩子类型为WH_KEYBOARD时:

键盘钩子,主要监视消息队列中的WM_KEYUP和WM_KEYDOWN消息。

nCode:指定钩子函数是否必须处理该消息,通常为HC_ACTION和HC_NOREMOVE两个值。

wParam:表示按键的虚拟码,即按下了哪个键。

lParam:一个32位的数值。

第0到15位表示你按下某个键的重复次数(当你按住某个键不放时,就会产生重复次数),对于WM_KEYUP消息,该重复次数总是1.

第16到23位表示键的扫描码。

第24位表示该键是否是一个扩展的按键。

第25位到28为保留不使用。

第29位,如果为1,表示同时按下了ALT键,否则为0.

第30位,表示先前该键的状态。为1表示之前该键是按下的,否则为0.

第31位,表示当前该键的状态。0表示按下,1表示释放。

2)钩子类型为WH_MOUSE时:

鼠标钩子,表示监视与鼠标相关的窗口消息,比如WM_MOUSEMOVE,WM_LBUTTONDOWN,WM_LBUTTONUP,WM_LBUTTONDBLCLK,WM_RBUTTONDOWN,WM_MBUTTONDOWN等等。

nCode:意义同上。

wParam:表示鼠标消息的类型,即上面的WM_系列。

lParam:一个指针,指向一个MOUSEHOOKSTRUCT结构。该结构定义如下:

typedef struct tagMOUSEHOOKSTRUCT { 
    POINT     pt; //一个POINT结构,表示当前鼠标指针的x坐标和y坐标。
    HWND      hwnd; //表示要收到该鼠标消息的窗口的句柄。
    UINT      wHitTestCode; //一个命中测试值,表示鼠标击中的部位。
    ULONG_PTR dwExtraInfo; //和该消息相关的额外信息。
} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT; 

3)钩子函数的返回值。

返回值为0,表示我们的钩子函数已经处理了该消息,则将该消息屏蔽。也就是说,本来应该收到这个消息的窗口现在收不到了。

返回值为非0值,表示放行该消息,继续让窗口收到该消息。

一般地,我们总是返回函数CallNextHookEx的返回值。这里就涉及到一个钩子链的概念。对于同一种类型的钩子,我们可以设置多个,比如这样:

	HHOOK hhk1,hhk2;
	hhk1=SetWindowsHookEx(WH_KEYBOARD,KeyProc1,NULL,GetCurrentThreadId());
	hhk2=SetWindowsHookEx(WH_KEYBOARD,KeyProc2,NULL,GetCurrentThreadId());
这里的KeyProc1和KeyProc2分别是我们自定义的两个钩子函数。

这样就形成了一个钩子链,而后设置的那个钩子(这里的hhk2)位于链首。也就是说,钩子函数KeyProc2会最先处理所钩的消息,如果在KeyProc2中最后调用return CallNextHookEx(hhk2,nCode,wParam,lParam),那么这条消息会继续传给KeyProc1,让它接着来处理这个消息。如果我们不这样调用,那么钩子1将收不到这条消息。

3、hMod

模块句柄。如果是线程钩子,该值可设为NULL。

我们这里讲全局钩子,必须指定为dll模块的句柄。

可以在DllMain入口函数中获取,也可以用hMod=GetModuleHandle("dll名")来获得。

4、dwThreadId

线程ID,指定与钩子函数相关的线程的ID。

全局钩子中设为0,表示监视所有的线程。


该函数返回一个HHOOK类型的句柄。


二、卸载钩子。

在应用程序退出时,不要忘了卸载钩子。

BOOL UnhookWindowsHookEx(HHOOK hhk);
这个函数很简单,只有一个参数hhk,即为SetWindowsHookEx的返回值。

三、一个简单的例子。

1)dll文件的编写。

//dll.cpp
#include 
#include 

HINSTANCE hdll;
HHOOK hhkKeyboard,hhkMouse;

LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
	if((1<<31 & lParam)==0)//键被按下
	{
		if(wParam=='a' || wParam== 'A')
			MessageBox(NULL,TEXT("你按下了A键"),0,0);
	}
	return CallNextHookEx(hhkKeyboard,nCode,wParam,lParam);
}

LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
	if(wParam==WM_LBUTTONDOWN)//鼠标被按下
	{
		MOUSEHOOKSTRUCT* pMHS=(MOUSEHOOKSTRUCT*)lParam;
		int x=pMHS->pt.x;
		int y=pMHS->pt.y;
		TCHAR szBuffer[50];
		_stprintf(szBuffer,TEXT("你的鼠标坐标为%d,%d,窗口句柄为%d"),x,y,pMHS->hwnd);
		MessageBox(NULL,szBuffer,0,0);
	}
	return CallNextHookEx(hhkMouse,nCode,wParam,lParam);
}

BOOL SetHook()
{
	hhkKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,hdll,0);
	hhkMouse   =SetWindowsHookEx(WH_MOUSE,MouseProc,hdll,0);
	if(hhkKeyboard && hhkMouse)
		return TRUE;
	else
		return FALSE;
}
VOID UnHook()
{
	UnhookWindowsHookEx(hhkMouse);
	UnhookWindowsHookEx(hhkKeyboard);
}

BOOL WINAPI DllMain(HINSTANCE hInstDll,DWORD fdwReason,PVOID fImpLoad)
{
	switch(fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		hdll=hInstDll;//获得hdll的值
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

;dll.def文件
EXPORTS
	SetHook
	UnHook
2)应用程序的编写
应用程序编写比较简单,只要在项目属性中添加依赖lib,并且声明导入函数,就可以使用SetHook安装钩子了。在程序退出的时候,要调用UnHook。




你可能感兴趣的:(VC学习)