Windows系统都是通过消息机制来管理应用程序的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来
完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩
子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。可见,利用钩子可以实现许多特殊而有用的功能。因
此,对于高级编程人员来说,掌握钩子的编程方法是很有必要的。
钩子的类型一共分为十三种,如日志钩子,键盘钩子,鼠标钩子,消息钩子等等。按作用
域分钩子又可以分为全局钩子和局部钩子,也就是大家常说的线程钩子和系统钩子,线程钩子只有在当前线程中有效,如果要实现系统钩子就必须把钩子放到DLL
文件中。由于系统钩子要用到DLL所以我们先用C++Builder的向导生成一个空的DLL文件,并加入自己的代码,在讲代码之前我先向大家讲关于编写
HOOK的几相基本的API函数
我们就最常用的键盘钩子为例,给大家讲讲编写钩子的基本过程,下面我们分三步讲解在C++Builder下如何安装钩子,定义钩子和卸载钩子。
1.安装钩子
在程序初始化的时候,调用函数SetWindowsHookEx安装钩子。其函数原型为:
HHOOK SetWindowsHookEx(
int idHook, // type of hook to install
HOOKPROC lpfn, // address of hook procedure
HINSTANCE hMod, // handle of application instance
DWORD dwThreadId // identity of thread to install hook for
);
这
个API函数在CB的帮助文档中有介绍,参数idHook指的是钩子类型,指的是键盘钩子或是鼠标钩子还是其它。我们这里要写的是一个键盘钩子,所以是
WH_KEYBOARD。参数lpfn,是指向执行这个钩子的一个过程的地址。参数hMod是指这个调用钩子应用程序实例。参数dwThreadId就是
线程钩子用到的线程ID,如果是全局钩子规定这个参数为0。
消息如果被钩子拦截不能继续传下去,务必会造成系统不能正常运行,所以就要用到了函数CallNextHookEx, CallNextHookEx.可以把钩子信息传递给钩子链的下一个钩子函数。其函数原型为:
LRESULT CallNextHookEx(
HHOOK hhk, // handle to current hook
int nCode, // hook code passed to hook procedure
WPARAM wParam, // value passed to hook procedure
LPARAM lParam // value passed to hook procedure
);
参数hhk是钩子句柄。nCode、wParam和lParam个是钩子函数。当然也可以通过直接返回TRUE来丢弃该消息,就阻止了该消息的传递。
2.定义钩子函数
我们要钩子做什么呢?所以我们必须定义钩子要做的事,钩子监视的特定事件发生后,会调用钩子函数进行相应的处理。钩子函数也是一种特殊的回调函数。钩子监视的特定事件发生后,系统会调用钩子函数进行处理。其函数原型为:
LRESULT CALLBACK KeyboardProc(
int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam // keystroke-message information
);
参数wParam和 lParam包含所钩消息的信息,键盘按键状态。nCode包含有关消息本身的信息。
3.卸载钩子
当不再使用钩子时,必须及时卸载。简单地调用函数UnhookWindowsHookEx即可,其函数原型为:
BOOL UnhookWindowsHookEx(
HHOOK hhk // handle of hook procedure to remove
);
参数hhk为钩子句柄。
好了,准备知识已经学完了,让我们开始编写个全局的钩子程序吧!我们写一个捕捉Ctrl+V的键盘钩子。让其粘贴出来的总是我们事先定义好的内容。
下面是后面要用的三个全局变量。
static HHOOK hkb = NULL;
TCHAR buf[] = "都是钩子惹的祸";
HINSTANCE hinstDLL;
在DLL入口函数中我们初始化钩子所属实例的句柄。
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
hinstDLL = hinst;
return 1;
}
下面这个就是定义钩子的过程了,在这个函数里面写下自己的代码,只要有键盘动作就会触发这个函数,这里我用了条件,只捕捉Ctrl+V键。无论你剪贴板里是什么内容,粘出来的是“都是钩子惹的祸”。
extern "C" __declspec(dllexport) __stdcall LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if (wParam == 0x56 && GetAsyncKeyState(VK_CONTROL) < 0 && lParam >= 0)
{
HGLOBAL hMem;
char* pStr;
//分配内存空间,这里返回一个内存块句柄。
hMem = GlobalAlloc(GHND | GMEM_SHARE, sizeof(buf));
//pStr指向锁定的一段内存地址,只能通过pStr来修改这块内存
pStr = (char*)GlobalLock(hMem);
//更新这段地址内容
strcpy(pStr, buf);
//内存块解锁
GlobalUnlock(hMem);
//打开剪贴板,如果没有打开剪贴板执行到EmptyClipboard就会出错
OpenClipboard(NULL);
EmptyClipboard();
//设置剪贴板文本
SetClipboardData(CF_TEXT, hMem);
CloseClipboard();
//释放内存空间
GlobalFree(hMem);
}
//将钩子传下去,如果不写这么一句那么Ctrl+V就失灵了。
LRESULT RetVal = CallNextHookEx(hkb,
nCode,
wParam,
lParam);
return RetVal;
}
下面的这个installhook是我提供给外界的一个接口,只要其它应用程序调用这个接口就可以完成钩子的安装。
extern "C" __declspec(dllexport) __stdcall BOOL installhook()
{
hkb=SetWindowsHookEx(WH_KEYBOARD, //键盘钩子
(HOOKPROC)KeyboardProc, //处理钩子的事件
hinstDLL, //钩子所属实例的句柄
0); //全局钩子
if (hkb == NULL)
return FALSE;
return TRUE;
}
用完了记得一定要释放,不然DLL会常驻内存,直到你注销或是重启,所以编程的时候一定要养成释放内存空间的好习惯。直接调用UnHook来释放钩子。
extern "C" __declspec(dllexport) __stdcall BOOL UnHook()
{
BOOL unHooked = UnhookWindowsHookEx(hkb);
return unHooked;
}
DLL写完了,保存。打开菜单Project->Build All Project编译。会在原目录下产生一个相应的DLL文件和一个Lib文件。
好了,现在我们就验证这个HOOK的功能,为了方便我先用静态方法调用链接库。
1、 现在新建一个Application程序,放两个按钮,在菜单Project->Add to Project添加这个Lib文件。
2、 再在程序中前面先声明一下
extern "C" __declspec(dllimport) __stdcall BOOL UnHook();
extern "C" __declspec(dllimport) __stdcall BOOL installhook();
3、 然后分别在两个按钮事件中写下installhook();和UnHook();就可以了。一个装载钩子,一个卸载钩子。简单吧?此程序稍做修改就可以达到像记忆按键的功能,有兴趣的同学可以自己动手修改一下。
4、 加载钩子后再试试打开记事本或是Word按一下Ctrl+V看看有什么变化?
阅读(1262) | 评论(0) | 转发(1) |