当时刚刚接触觉得有点无法理解,可是通过查msdn和上网搜索很快就有了粗浅的认识。对于一个屏蔽快捷键的钩子原理大概是这样的:
1.存在一个钩子处理函数(回调函数),和一个安置钩子的函数(vc下最常用的是SetWindowHookEx())
2.安置钩子的函数将钩子处理函数注入到某进程的消息响应链中,从而能达到截获某些消息的效果。(我的理解:))
3.钩子处理函数往往存在于一个dll文件中。(当然对于系统全局键盘钩子,和系统全局鼠标钩子可以放在其调用函数的可执行文件中,换句话说钩子处理函数可以和SetWindowHookEx()放在同一个exe中)
4.在使用钩子函数时要小心。设置了钩子要记得利用其句柄对其释放,在钩子处理函数中要记得调用CallNextHookEx()来保持消息处理链的完整等等。总之要非常小心。
起初我对钩子的认识就是这么简单。由于屏蔽热键仅仅用系统键盘钩子即可完成,所以在调用SetWindowHookEx()时最后两个重要参数均为零,所以与真正认识钩子的机会失之交臂。
时隔一年,再次有机会用到钩子函数(下文将介绍具体应用背景),目的是在一个进程中安置系统钩子,来监视另一个进程关闭时发出的消息。这次我认真的在网上搜索了相关材料,做了一些总结,一般来说钩子分为一下几种:
(1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。
(2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。
(3) 外壳钩子可以监视各种Shell事件消息。比如启动和关闭应用程序。
(4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。
(5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。
对于SetWindowsHookEx()中设定钩子类型的第一个参数的含义msdn中有详细说明,依据其中的描述WH_SHELL和WH_CBT都有获取另外一个进程关闭事件的功能。他们的翻译如下:
2、WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
13、WH_SHELL Hook
外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
好了钩子类型基本锁定在这两个宏。
第二个参数是钩子处理函数,要声明成回调函数,一旦系统中出现满足钩子类型的消息就会陷入钩子处理函数,一般都是在钩子处理函数中再次对消息的具体内容、类型等信息进行过滤,以判断采取什么处理。没有什么好说的,这里要重点谈谈倒数的两个参数。
再说说SetWindowsHookEx()的倒数第二个参数吧,它是dll文件的句柄,根据我个人的理解,钩子函数的作用是把一段dll中代码注入到某个进程空间中,所以这个dll句柄一定要声明成全局唯一的,内存共享的数据段中,不然会出现非法访问内存的错误。这些都有由于windows为每个进程设置了虚拟空间造成的。 我也没有什么好方法,能方便的得到dll的句柄,只好将DllMain函数参数列表中的dll句柄直接保存起来。
最后也是给我造成最大麻烦的是最后一个参数,线程id!!起初我的想法是天真的,而且用Windows中计算机做的实验。直接用FindWindow()找到特定窗口的句柄,再用GetWindowThreadProcessId()同时得到了线程id和进程id,两个api全部搞定,这样的方法用于件事calc.exe这样简单的程序是好使的,完全正常工作。问题就是有些程序窗口名是可变,随着工作状态、窗口内的内容在不短改变窗口的title。这样的例子并不少见,比如word、Microsoft visiual c++等等就属于这类窗口。
起初面对这样的问题我有两条思路,首先设法这类应用程序的住进程名是固顶的,可以清楚的从任务管理器中找到它。第二这类程序的窗口中总有一部分“公共内容”比如word中的Microsoft Word能否得到所有任务栏中窗口的名字一一提取出来进行解析呢?
第一种思路我是不知道有什么api能直接得到的,上网搜吧,倒霉吹的公司里的“肉食者”就是他妈的有网络限制,很多东西无法浏览,逼我在这里骂娘。看到csdn中的帖子大部分都是菜鸟们瞎吵吵,很乱套。受够了!!几经周折最后搜还是到一些代码的,其思想不过是枚举所有进程->提取其名字->找到想要进程后,枚举其所有线程->查找不依赖于其它任何线程的线程,即主线程。其效率暂且不谈,单说思路已经和第二种方案无异。效率大概还不及第二种思路。
思路二,很简单三个api搞定,::::GetTopWindow(),::GetNextWindow,::GetWindowText(),可是当我吧所有窗口名字在屏幕中打印出来的时候并不是我想要的样子,很多运行的程序窗口没找到,又有一些打印出来的名字,找不到窗口。看来这两个思路变成了死路。
搜索,坚持,直到所有的耐心全部耗光,知道彻底疯掉失去信心……
难道一个“线程id”如此难求吗。最后干脆搜索关键词“主线程”,找到了函数::CreateProcess(……)方法来啦!