http://www.paper.edu.cn
- 1 -
VisualC_6_0下局部钩子技术的应用
万静
重庆邮电大学计算机学院,重庆 (400065)
E-mail: 13708530123@139gz.com
摘 要:本文主要介绍了使用 VC6.0作为开发语言,以MFC实现“飞信 Recorder工具”的方
法。作者采用 MFC 生成动态链接库(dll)的方式,通过实现一个截获并处理飞信 2008 版
聊天消息的局部钩子,来跟踪记录与某人的聊天内容,并整理成清晰的内容清单,保存到文
件中;另外,为了保证该工具使用的方便、可靠,作者还实现了向任务栏
通知
关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知
区添加应用程
序图标,并确保只有唯一运行实例的功能。文中所叙述的实现方法,不仅向钩子技术的学习
者展示了具体实例,而且对那些需要自己保存飞信聊天的用户,也具有很好的参考价值。
关键词:Dll, Hook,消息
1 引言
众所周知,基于 Win32 的应用程序是事件驱动的(event-driven),即消息驱动的,应用
程序所采取的任何动作都依赖于它所获得的消息类型及其内容;Windows 的消息处理机制为
了能在应用程序中监控系统的各种事件消息,提供了挂接各种回调
函数
excel方差函数excelsd函数已知函数 2 f x m x mx m 2 1 4 2拉格朗日函数pdf函数公式下载
(Hook)的功能[1],即
钩子(hook)机制。通过它,应用程序可以监视系统中的消息传递,并能够在它们到达目标
窗口之前对其进行处理。这可以帮助应用程序实现某些特殊目的,如控制键盘或鼠标的输入
消息,监视窗口的打开关闭以及实现纪录宏等等,应用灵活,功能强大。
2 Hook基础
2.1 钩子概念
钩子(Hook):是 Windows 消息处理机制的一个平台,实际上是一个处理消息的程序段,
通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就
先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数既可以加工处理(改变)该消息,
也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
2.2 钩子链
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
系统为每一种钩子建立一个钩子链(Hook chain),一个钩子链实际上是一个指针列表
[2],由系统来维护,这些指针指向程序定义的回调函数(callback function),这些回调函数
便是钩子子程(hook procedure)。当与指定的 Hook 类型关联的消息发生时,系统就把这个
消息传递到 Hook 子程。一些 Hook 子程可以只监视消息,或者修改消息,或者停止消息的
前进,避免这些消息传递到下一个 Hook 子程或者目的窗口。最近安装的钩子放在链的开始,
而最早安装的钩子放在最后,也就是后加入的先获得控制权。
Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸
载,Windows 便释放其占用的内存,并更新整个 Hook 链表。如果程序安装了钩子,但是在
尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
2.3 钩子范围
系统中成功注册的钩子,按范围可分为:
全局钩子--监视系统中所有线程的消息传递;因为全局钩子的处理程序需要能够被所
http://www.paper.edu.cn
- 2 -
有应用程序调用,即它需要处于任何应用程序上下文中,因此全局钩子的处理程序必须位于
单独一个动态链接库(dynamic-link library)模块中;
局部钩子--仅仅监视某一线程的消息传递;对于局部钩子的处理程序,如果应用程序
需要监视所属线程的消息传递,它可以位于该应用程序的源码中或动态链接库中;另一种情
况是应用程序需要监视其它应用程序的某一线程的消息传递,这时钩子处理程序也必须位于
动态链接库中;
对于位于动态连接库中的钩子处理程序,系统在调用它之前(针对非安装钩子的应用程
序),会隐含调用 LoadLibrary,加载整个动态连接库,从而动态连接库中的其他代码段也会
随钩子处理程序强行注入到应用程序的进程空间,以这个进程的身份执行,使用这个进程的
堆栈,屏幕取词软件中就采用了这种技术。
2.4 钩子类型
Win32 系统支持 14 种不同类型的钩子,具体如下:
WH_CALLWNDPROC/WH_CALLWNDPROCRET Hooks、WH_CBT Hook、
WH_DEBUG Hook 、 WH_FOREGROUNDIDLE Hook 、 WH_GETMESSAGE Hook 、
WH_JOURNALPLAYBACK Hook、WH_JOURNALRECORD Hook、
WH_KEYBOARD Hook、WH_KEYBOARD_LL Hook、WH_MOUSE Hook、
WH_MOUSE_LL Hook、WH_MSGFILTER/WH_SYSMSGFILTER Hooks和WH_SHELL Hook
每一种类型的 Hook 可以使应用程序能够监视不同类型的系统消息处理机制;下面主要
描述作者在实现“飞信 Recorder 工具”时,使用到的钩子;其它类型的钩子,参见 MSDN 有
关部分。
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过
程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC Hook 子程,并
且在窗口过程处理完消息之后调用 WH_CALLWNDPROCRET Hook 子程。每当系统要调用
某个钩子子程时,会自动将对应的参数传入该子程序,比如:lParam里面是CWPRETSTRUCT
结构的指针,该结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关
联的消息参数。
3 Hook编程
3.1 定义钩子
钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类
的成员函数,只能定义为普通的 C 函数,用以监视系统或某一特定类型的事件,这些事件
可以是与某一特定线程关联的,也可以是系统中所有线程的事件,它必须按如下语法定义:
LRESULT CALLBACK HookProc (
int nCode,
WPARAM wParam,
LPARAM lParam
);//HookProc 是应用程序定义的名字,可以自己更换;
nCode 参数是 Hook 代码,Hook 子程使用这个参数来确定任务。这个参数的值依赖于
Hook 类型,每一种 Hook 都有自己的 Hook 代码特征字符集。
wParam 和 lParam 参数的值依赖于 Hook 代码,但是它们的典型值是包含了关于发送或
http://www.paper.edu.cn
- 3 -
者接收消息的信息。
3.2 安装钩子
使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。
SetWindowsHookEx 函数总是在 Hook 链的开头安装 Hook 子程。当指定类型的 Hook 监视的
事件发生时,系统就调用与这个 Hook 关联的 Hook 链的开头的 Hook 子程。每一个 Hook 链
中的 Hook 子程都决定是否把这个事件传递到下一个 Hook 子程。
HHOOK SetWindowsHookEx (
int idHook, //钩子的类型,即它处理的消息类型
HOOKPROC lpfn, //钩子子程的地址指针。如果 dwThreadId 参数为 0
// 或是一个由别的进程创建的线程的标识,
// lpfn 必须指向 DLL 中的钩子子程。
// 除此以外,lpfn 可以指向当前进程的一段钩子子程代码。
// 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
HINSTANCE hMod,// 应用程序实例的句柄。标识包含 lpfn 所指的子程的 DLL。
// 如果 dwThreadId 标识当前进程创建的一个线程,
// 而且子程代码位于当前进程,hMod 必须为 NULL。
// 可以很简单的设定其为本应用程序的实例句柄。
DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
// 如果为 0,钩子子程与所有的线程关联,即为全局钩子。
);
函数成功则返回钩子子程的句柄,失败返回 NULL。
以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给
钩子子程,且被钩子子程先处理。
3.3 传递钩子
在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续
传递,那么它必须调用另外一个 SDK 中的 API 函数 CallNextHookEx 来传递它,以执行钩子
链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值,返回
值的类型依赖于钩子的类型。这个函数的原型如下:
LRESULT CallNextHookEx (
HHOOK hhk;// hhk 为当前钩子的句柄,由 SetWindowsHookEx()函数返回
int nCode;// nCode 为传给钩子过程的事件代码
WPARAM wParam;// 传给钩子子程的 wParam 值,其具体含义与钩子类型有关
LPARAM lParam;// 传给钩子子程的 lParam 值,其具体含义与钩子类型有关
);
另外,钩子函数也可以通过直接返回 TRUE 来丢弃该消息,并阻止该消息的传递。否
则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还可能产生不正确结果。
3.4 释放钩子
钩子在使用完之后需要用 UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子
比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:
http://www.paper.edu.cn
- 4 -
UnHookWindowsHookEx (HHOOK hhk);
函数成功返回 TRUE,否则返回 FALSE。
几点说明:
(1) 如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自
动先调用线程勾子,然后调用系统勾子。
(2) 对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子
处理结束后应把勾子信息传递给下一个勾子函数。
(3) 勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾
子,在使用完毕后要及时卸载。
3.5 共享钩子数据
在 Win16 环境中,DLL 的全局数据对每个载入它的进程来说都是相同的;而在 Win32
环境中,情况却发生了变化,DLL 函数中的代码所创建的任何对象(包括变量)都归调用
它的线程或进程所有。当进程在载入 DLL 时,操作系统自动把 DLL 地址映射到该进程的私
有空间,也就是进程的虚拟地址空间,而且也复制该 DLL 的全局数据的一份拷贝到该进程
空间。也就是说每个进程所拥有的相同的 DLL 的全局数据,它们的名称相同,但其值却并
不一定是相同的,而且是互不干涉的。
因此,在 Win32 环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问
同一个 Dll 的各个进程之间共享存储器,是通过存储器映射文件技术实现的。也可以把这些
需要共享的数据分离出来,放置在一个独立的数据段里,并把该段属性设置为共享。必须给
这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
#pragma data_seg 预处理指令用于设置共享数据段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()
在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问
该 Dll 的所有进程看到和共享。
4 程序实现
4.1 设计阶段
为了对飞信聊天内容进行跟踪,作者采用的主要策略是:通过借助局部钩子,监控聊天
记录回显窗口中的有关消息,一旦截获带有聊天内容的消息,就把聊天内容整理成统一的格
式,再保存到备份文件中,从而达到了聊天跟踪的目的。具体来说,实现上述策略的步骤如
下:首先找出与聊天内容直接相关的窗口消息;再实现截获该消息的钩子子程;最后完成“飞
信 Recorder 工具”跟踪,以及缩小到系统托盘等功能的实现。
4.2 开发阶段
首先用 MFC 生成一个基于对话框的应用程序 InnerHook;然后,在当前工程中,再用
MFC 添加一个动态链接库的工程 onlyHook。
http://www.paper.edu.cn
- 5 -
4.2.1 筛选被截获消息
作者通过 VC6.0 自带的 SPY++工具,对聊天记录回显窗口中的消息进行观察
分析
定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析
,最
后发现,每当有新的聊天加入进来时,都会发生 EM_REPLACESEL 消息,并且携带着具体
的聊天内容,这正是钩子需要截获的消息;确定之后,就可以进行开发了。
4.2.2 实现钩子动态链接库
为了达到对其它应用程序消息的监控,其钩子函数必须包含在独立的动态链接库(DLL)
中,这样才能被各种相关联的应用程序调用;另外,为了尽量降低对系统性能的影响,作者
使用的是局部钩子。
4.2.2.1 定义数据段
在 onlyHook.cpp 文件中,添加如下定义:
#pragma data_seg("MySec")
HHOOK g_hwindow=NULL; //窗口钩子句柄
HWND g_hwnd=NULL; //保存被跟踪聊天主窗口的句柄
HWND g_showwindow=NULL; //保存聊天窗口中显示聊天记录的窗口
//存放系统当前日期,格式为“(XXXX-XX-XX ”
char g_date[40]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0', '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
int g_date_len=0; //日期字符串长度
//存放保存聊天记录的文件路径全名,格式为“c:\与 XXX 的聊天.txt”
char g_filename[80]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
//存放被跟踪的飞信聊天窗口标题
char g_chatcaption[80]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',\
'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
#pragma data_seg()
4.2.2.2 实现钩子子程
主要功能就是:判断当前截获的消息是否是目标窗口中发生的EM_REPLACESEL消息,
如果是,就按统一格式整理、保存聊天内容;不是,就不做处理,原样传递消息。
LRESULT CALLBACK WindowHookProc (int code,WPARAM wParam,LPARAM lParam)
{ boolean flag=false;
char crln[]={'\x0d','\x0a','\x0d','\x0a','\0'}; //下移两行的 ASCII 字符串
char content_crln[]={'\x0d','\x0a',' ',' ','\0'}; //换新行并空两格的 ASCII 字符串
char mod1[]=" 说",mod2[]="):";
int mod1_len=sizeof(mod1)-1,mod2_len=sizeof(mod2)-1;
CWPSTRUCT* pStruct = (CWPSTRUCT*)lParam;
if (code == HC_ACTION) {
http://www.paper.edu.cn
- 6 -
if(pStruct->message ==EM_REPLACESEL) //当前的消息是 EM_REPLACESEL
{ g_hwnd=::FindWindow(NULL,g_chatcaption);//查找被跟踪聊天窗口的句柄
//未打开该窗口,则直接传递到下一个钩子子程去处理
if (!g_hwnd) return CallNextHookEx(g_hwindow, code, wParam, lParam);
while (g_hwnd) //查找聊天窗口中显示聊天记录的窗口
{ g_showwindow=g_hwnd;
g_hwnd=::GetWindow(g_hwnd,GW_CHILD); }
if (pStruct->hwnd==g_showwindow) //消息目标窗口是显示聊天记录的窗口
{ LPCTSTR mymsg=(LPCTSTR)pStruct->lParam;
int mymsg_len=strlen(mymsg);//取得当前消息内容的长度
//以可读写、添加的方式打开聊天记录的保存文件
CFile chatfile(g_filename,CFile::modeReadWrite | CFile::modeNoTruncate | CFile::modeCreate);
chatfile.SeekToEnd();//移动文件指针到文件末尾
//当前消息内容以" 说"结尾时,则往文件中写入两个回车换行符号,起到下移两行的效果
if (! lstrcmp((mymsg+mymsg_len-mod1_len),mod1)) chatfile.Write(crln,4);
else if (! lstrcmp((mymsg+mymsg_len-mod2_len),mod2))
//当前消息内容以"):"结尾时,则往文件中写入“(XXXX-XX-XX”
{ chatfile.Write(g_date,g_date_len);
flag=true;
mymsg+=1;
mymsg_len-=2; }
//把消息内容写入文件中
chatfile.Write(mymsg,mymsg_len);
//当前消息内容以"):"结尾时,则往文件中写入回车换行符,并空两格
if (flag) chatfile.Write(content_crln,4);
//将缓冲的内容写入文件
chatfile.Flush();
//关闭文件句柄
chatfile.Close(); } } }
return CallNextHookEx(g_hwindow, code, wParam, lParam);}
4.2.2.3 实现安装钩子
主要功能是:找出飞信聊天窗口所属的线程,并安装上面实现的局部钩子。
void SetHook (HWND _hwnd,CString _chatcaption,CString _date,CString _file)
{ g_date[0]='(';
lstrcpyn(g_date+1,_date,38);//将系统日期按需要的格式拷入 dll 数据段中
g_date_len=lstrlen(g_date);
lstrcpyn(g_filename,_file,79);//将保存聊天记录的文件名拷入 dll 数据段中
lstrcpyn(g_chatcaption,_chatcaption,79);//将聊天窗口标题拷入 dll 数据段中
HWND hwindow=_hwnd;
DWORD processID;
//获取聊天窗口的创建线程 ID
DWORD threadID=::GetWindowThreadProcessId(hwindow,&processID);
http://www.paper.edu.cn
- 7 -
//注册针对聊天窗口线程的 WH_CALLWNDPROC 局部钩子
g_hwindow=SetWindowsHookEx(WH_CALLWNDPROC,WindowHookProc,GetModuleHa
ndle("onlyHook"),threadID);
while (hwindow) //查找聊天窗口中显示聊天记录的窗口
{ g_showwindow=hwindow;
hwindow=::GetWindow(hwindow,GW_CHILD); } }
4.2.2.4 实现卸载钩子
主要功能是:如果尚未卸载,就释放已经安装的钩子。
void UninstallHook() {
if (g_hwindow) //如果窗口钩子还未释放
{ UnhookWindowsHookEx(g_hwindow);
g_hwindow=NULL;//置空钩子变量
} }
4.2.2.5 导出安装/卸载函数
作者在 onlyHook.def 文件中,添加如下内容:[3]
SetHook @ 1
UninstallHook @ 2
SEGMENTS
MySec READ WRITE SHARED
到此,钩子 dll 的编码就完成了;对其成功编译后,在 Debug 文件夹下面会生成两个文
件:onlyHook.dll 和 onlyHook.lib 文件,它们是实现“飞信 Recorder 工具”的基础。
4.2.3 实现 Recorder 工具
4.2.3.1 准备阶段
(1) 将 onlyHook.dll 文件拷到与 InnerHook.exe 文件同目录下,以备调用;
(2) 在 CinnerHookDlg 类中,添加如下成员变量:
CString m_date;//存放当前系统日期字符串
CString m_rec_caption;//存放跟踪聊天时的窗口标题
CString m_unrec_caption;//存放未跟踪时的窗口标题
CString m_recfile;//存放保存聊天记录的文件名
(3) 在 CInnerHookDlg::OnInitDialog()函数中,添加如下内容:
CTime cur_date=CTime::GetCurrentTime();//获取当前系统日期
m_date="";
m_date.Format("%d-%d-%d ",cur_date.GetYear(),cur_date.GetMonth(),cur_date.GetDay());
m_unrec_caption="(非跟踪态)飞信聊天跟踪工具";
m_rec_caption="(跟踪态)飞信聊天跟踪工具";
4.2.3.2 跟踪/停止功能
(1) 在 InnerHookDlg.cpp 文件开头,加入如下对动态链接库.lib 文件的使用:
#pragma comment(lib,".\\onlyHook\\debug\\onlyHook.lib")
(2) 然后,加入需要调用的 dll 函数申明:
http://www.paper.edu.cn
- 8 -
extern void SetHook (HWND _hwnd,CString _chatcaption,CString _date,CString _file);
extern void UninstallHook ();
(3) 在对话框资源编辑器中,拖入一个按钮,标题改为“开始跟踪”,改写其单击消息处理函
数如下:
CString chatcaption="";
GetDlgItem(IDC_EDIT1)->GetWindowText(chatcaption);
if (chatcaption.IsEmpty())
{ AfxMessageBox("请先指定被跟踪的聊天窗口标题!");
return; }
HWND hwindow=::FindWindow(NULL,chatcaption);//"聪聪" 杰子哥 - .......
if (!hwindow) {chatcaption+="聊天窗口未打开!";
AfxMessageBox(chatcaption);
return ; }
m_recfile="c:\\与";
m_recfile+=chatcaption;
m_recfile+="的聊天.txt";
SetHook(hwindow,chatcaption,m_date,m_recfile);//安装钩子,开始跟踪聊天记录
SetWindowText(m_rec_caption);
GetDlgItem(IDC_BUTTON3)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(false);
GetDlgItem(IDC_EDIT1)->EnableWindow(false);
(4) 在对话框资源编辑器中,拖入另一个按钮,标题改为“停止跟踪”,改写其单击消息处理
函数如下:
UninstallHook();//卸载钩子,停止跟踪聊天记录
SetWindowText(m_unrec_caption);
GetDlgItem(IDC_BUTTON3)->EnableWindow(false);
GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT1)->EnableWindow(TRUE);
4.2.3.3 系统托盘功能
为了向系统托盘里增加/删除图标,需要调用如下函数:WINSHELLAPI BOOL WINAPI
Shell_NotifyIcon(DWORD dwMessage,PNOTIFYICONDATA pnid);[4]
dwMessage 参数表示发送消息的含义,取值如下:
NIM_ADD-向任务栏状态区增加图标;
NIM_MODIFY-修改任务栏状态区中的图标;
NIM_DELETE-删除任务栏状态区中的图标;
pnid 参数为 NOTIFYICONDATA 结构的指针,具体说明如下:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;//以字节为单位这个结构的大小
HWND hWnd;//接收托盘图表通知消息的窗口句柄
UINT uID;//应用程序定义的该图标 ID 号
UINT uFlags;//图标功能控制位,可以是以下值的组合:
//NIF_MESSAGE-发送控制消息;
http://www.paper.edu.cn
- 9 -
//NIF_ICON-显示该任务栏图标;
//NIF_TIP-动态提示文字;
UINT uCallbackMessage;//应用程序定义的消息 ID 号,此消息传递给 hWnd
HICON hIcon;//图标句柄
char szTip[64];//鼠标停留在该图标上提示信息
} NOTIFYICONDATA,*PNOTIFYICONDATA;
掌握了上面函数的使用,就可以完成此功能了;
(1) 在 StdAfx.h 文件中,加入如下自定义消息:
#define WM_SYSTEMTRAY WM_USER+1000
(2) 在对话框资源编辑器中,勾选“样式”属性页中的“最小化框”;
(3) 创建一个“退出”菜单项,ID 为 ID_ICON_EXIT;
(4) 程序运行时,往系统托盘增添小图标;
在 CInnerHookDlg::OnInitDialog()函数中,添加如下内容:
NOTIFYICONDATA nid;
nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = m_hWnd; //接收图标消息的窗口句柄
nid.uID = IDR_MAINFRAME; // 图标 ID
nid.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP;
nid.uCallbackMessage = WM_SYSTEMTRAY;//任务栏上点击图标时发送的消息
nid.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );
strcpy( nid.szTip, "飞信 Recorder 工具" ); //图标提示为“飞信 Recorder 工具”
::Shell_NotifyIcon( NIM_ADD,&nid );//向任务栏添加图标
(5) 退出程序时删除小图标;
添加 WM_DESTROY 消息响应函数代码如下:
NOTIFYICONDATA nid;
nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME;
nid.uFlags = 0;
::Shell_NotifyIcon( NIM_DELETE,&nid );//删除系统托盘小图标
(6) 将程序最小化到系统托盘里;
改写 WM_SYSCOMMAND 消息处理函数:
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{ CAboutDlg dlgAbout;
dlgAbout.DoModal(); }
else if (nID==SC_MAXIMIZE) //最大化
{ this->ShowWindow(SW_SHOWMAXIMIZED); }
else if (nID == SC_MINIMIZE)//最小化,把它隐藏起来
{ this->ShowWindow(FALSE); }
else {CDialog::OnSysCommand(nID, lParam);}
(7) 实现从系统托盘里恢复程序主窗口;
添加如下处理自定义消息的函数申明:
afx_msg LRESULT OnSystemTray(WPARAM wParam, LPARAM lParam);
http://www.paper.edu.cn
- 10 -
并在 END_MESSAGE_MAP()前,添加如下消息映射;
ON_MESSAGE(WM_SYSTEMTRAY, OnSystemTray)
其 CInnerHookDlg::OnSystemTray()实现代码如下:
if ( wParam = IDR_MAINFRAME )
{ switch( lParam )
{case WM_LBUTTONDOWN:
this->ShowWindow(SW_NORMAL);//鼠标左键点击,则恢复正常显示
break;
case WM_RBUTTONDOWN: //鼠标右键点击,则在点击处,打开浮动退出菜单
CMenu menu;
menu.LoadMenu(IDR_MENU2);
POINT pt;
::GetCursorPos(&pt);
menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTALIGN, pt.x, pt.y,
AfxGetMainWnd());
break; }}
return 1;
(8) 实现“退出”菜单功能
针对 ID_ICON_EXIT 菜单项,添加其 COMMAND 消息的处理函数如下:
void CInnerHookDlg::OnIconExit()
{ OnButton3();//主要是释放钩子
CDialog::OnCancel();//结束程序
}
4.2.3.4 唯一实例功能
作者通过在程序运行之初,判断某一互斥对象是否存在,以此来保证只有唯一实例运行。
具体来说,如果不存在这样的对象,则说明此时是第一个实例,允许执行,并需要建立这样
的互斥对象,来标识已有运行实例了;如果存在这样的对象,则说明此时程序不是第一个执
行实例,应该退出执行;完成此功能的详细过程如下:
在 CinnerHookApp 类中,增加如下函数:
boolean CInnerHookApp::OnlyInstance()
{ HANDLE hMutex = NULL;
//如果没有,就创建新的互斥对象;已经有了,则直接返回已有互斥对象;
hMutex = CreateMutex(NULL, FALSE, "飞信 Recorder 器");
if (hMutex != NULL) {
//此时错误状态不是 ERROR_ALREADY_EXISTS,则表示是第一个执行实例
if (GetLastError() != ERROR_ALREADY_EXISTS) return true;
}
//关闭互斥对象
CloseHandle(hMutex);
return false;
}
http://www.paper.edu.cn
- 11 -
另外,在 CInnerHookApp::InitInstance()开头,增加如下判断语句:
if (!OnlyInstance()) return false;//不是第一个执行实例,则退出执行
5 总结
利用钩子技术可以对 Windows 系统消息和事件进行拦截处理,进而实现一般应用程序
无法实现的功能。本文通过演示一个针对飞信 2008 版的聊天记录工具的开发,详细描述了
钩子的开发使用技术,为相关开发人员提供了很好的技术支持。但是钩子技术也是一把双刃
剑,不要利用钩子技术进行窃取别人帐号和密码信息等非法活动。
参考文献
[1] Hook 技术及其在监控键盘输入中的应用 庄继晖 微处理机 200/(2)
[2] Windows 的钩子技术及实现 倪步喜 计算机与现代化 2007(1)
[3] 在 Visual C++6.0 下应用 Win32 系统钩子技术.郎锐.电脑编程技巧与维护.2001(8)
[4] 用 VC++设计 Win32 全局钩子 谢志鹏 计算机应用 2001(4)
The Local_Hook Applications under VisualC_6_0
Wan Jing
College of Computer Science,Chongqing University of Posts and Telecommunications,
Chongqing (400065)
Abstract
This article mainly describes the realization of "Fetion_Recorder tool" by using VC6.0/MFC.The
author realizes a local hook by the way of creating dynamic link library (dll) which is generated by
MFC.This hook can intercept and process chat_message of the Fetion2008 version.On the basis of
it,the following Funcitons is finished:Tracking the chat_content with somebody;Saving the
chat_content into the file,according to a specific format; In addition, to ensure that the tools is used in
convenient, reliable, the author also finish extra two funcitons:Adding application icon into system
tray;Ensuring that there is only running_instance at the same time. The methods described in the paper,
not only demonstrate a concrete example to the persons that are studying the hook technology,but also
provide a good reference value to those Fetion2008_users in need of preservation of their own
chat_contents.
Keywords:Dll, Hook, Message
作者简介:万静,女,1981 年 2 月出生,硕士研究生,主要研究方向为计算机网络与通信。
<<
/ASCII85EncodePages false
/AllowTransparency false
/AutoPositionEPSFiles true
/AutoRotatePages /All
/Binding /Left
/CalGrayProfile (Dot Gain 20%)
/CalRGBProfile (sRGB IEC61966-2.1)
/CalCMYKProfile (U.S. Web Coated \050SWOP\051 v2)
/sRGBProfile (sRGB IEC61966-2.1)
/CannotEmbedFontPolicy /Warning
/CompatibilityLevel 1.4
/CompressObjects /Tags
/CompressPages true
/ConvertImagesToIndexed true
/PassThroughJPEGImages true
/CreateJDFFile false
/CreateJobTicket false
/DefaultRenderingIntent /Default
/DetectBlends true
/ColorConversionStrategy /LeaveColorUnchanged
/DoThumbnails false
/EmbedAllFonts true
/EmbedJobOptions true
/DSCReportingLevel 0
/EmitDSCWarnings false
/EndPage -1
/ImageMemory 1048576
/LockDistillerParams false
/MaxSubsetPct 100
/Optimize true
/OPM 1
/ParseDSCComments true
/ParseDSCCommentsForDocInfo true
/PreserveCopyPage true
/PreserveEPSInfo true
/PreserveHalftoneInfo false
/PreserveOPIComments false
/PreserveOverprintSettings true
/StartPage 1
/SubsetFonts true
/TransferFunctionInfo /Apply
/UCRandBGInfo /Preserve
/UsePrologue false
/ColorSettingsFile ()
/AlwaysEmbed [ true
]
/NeverEmbed [ true
]
/AntiAliasColorImages false
/DownsampleColorImages true
/ColorImageDownsampleType /Bicubic
/ColorImageResolution 300
/ColorImageDepth -1
/ColorImageDownsampleThreshold 1.50000
/EncodeColorImages true
/ColorImageFilter /DCTEncode
/AutoFilterColorImages true
/ColorImageAutoFilterStrategy /JPEG
/ColorACSImageDict <<
/QFactor 0.15
/HSamples [1 1 1 1] /VSamples [1 1 1 1]
>>
/ColorImageDict <<
/QFactor 0.15
/HSamples [1 1 1 1] /VSamples [1 1 1 1]
>>
/JPEG2000ColorACSImageDict <<
/TileWidth 256
/TileHeight 256
/Quality 30
>>
/JPEG2000ColorImageDict <<
/TileWidth 256
/TileHeight 256
/Quality 30
>>
/AntiAliasGrayImages false
/DownsampleGrayImages true
/GrayImageDownsampleType /Bicubic
/GrayImageResolution 300
/GrayImageDepth -1
/GrayImageDownsampleThreshold 1.50000
/EncodeGrayImages true
/GrayImageFilter /DCTEncode
/AutoFilterGrayImages true
/GrayImageAutoFilterStrategy /JPEG
/GrayACSImageDict <<
/QFactor 0.15
/HSamples [1 1 1 1] /VSamples [1 1 1 1]
>>
/GrayImageDict <<
/QFactor 0.15
/HSamples [1 1 1 1] /VSamples [1 1 1 1]
>>
/JPEG2000GrayACSImageDict <<
/TileWidth 256
/TileHeight 256
/Quality 30
>>
/JPEG2000GrayImageDict <<
/TileWidth 256
/TileHeight 256
/Quality 30
>>
/AntiAliasMonoImages false
/DownsampleMonoImages true
/MonoImageDownsampleType /Bicubic
/MonoImageResolution 1200
/MonoImageDepth -1
/MonoImageDownsampleThreshold 1.50000
/EncodeMonoImages true
/MonoImageFilter /CCITTFaxEncode
/MonoImageDict <<
/K -1
>>
/AllowPSXObjects false
/PDFX1aCheck false
/PDFX3Check false
/PDFXCompliantPDFOnly false
/PDFXNoTrimBoxError true
/PDFXTrimBoxToMediaBoxOffset [
0.00000
0.00000
0.00000
0.00000
]
/PDFXSetBleedBoxToMediaBox true
/PDFXBleedBoxToTrimBoxOffset [
0.0