钩子函数和回调函数

调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件,使吾辈编程者如此轻松愉快。若评选对人类影响最大之发明,在火与车轮之后,笔者当推压栈调用。   
        话虽这样说,此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。   
        回调函数使程序结构乱了许多。Windows   API   函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。   
        Windows   系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是   Windows   的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的   WParam   和   LParam   相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等   Windows   发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程   B   收到进程   A   发来的消息,启动了一段代码,其中又向进程   A   发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。   
        利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口   handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借   API   函数   SendMessage   向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借   API   函数   PostMessage   发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。   
        如今我们是活在一个   object   时代。只要与编程有关,无论何事都离不开   object。但   object   并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。   
        不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如   VB   和   Java),其代价是降低了程序效率。事件例程使编程者无需直接操作地址,但并不会使程序减速。更妙的是,此一改变,本是有损程序结构之奇技怪巧变成一种崭新设计理念,不仅免去被人抨击,而且逼得吾等凡人净手更衣,细细研读,仰慕至今。只是偶然静心思虑,发觉不过一瓶旧酒而已,故引得此番议论,让诸君见笑了。




一、引言   
钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。钩子的种类很多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩子程序先行截获该消息、得到对此消息的控制权。此时钩子函数可以对截获的消息进行加工处理,甚至可以强制结束消息的传递。这有点类似与MFC中的PreTranslateMessage函数,所不同的是该函数只能用于拦截本进程中的消息,而对系统消息则无能为力。   
二、Win32系统钩子的实现   
每种类型的钩子均由系统来维护一个钩子链,最近安装的钩子位于链的开始,拥有最高的优先级,而最先安装的钩子则处在链的末尾。要实现Win32的系统钩子,首先要调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,其原型是:   
HHOOK   SetWindowsHookEx(int   idHook,   
HOOKPROC   lpfn,   
HINSTANCE   hMod,   
DWORD   dwThreadId);   
其中,第一个参数是钩子的类型,常用的有WH_MOUSE、WH_KEYBOARD、WH_GETMESSAGE等;第二个参数是钩子函数的地址,当钩子钩到任何消息后便调用这个函数;第三个参数是钩子函数所在模块的句柄;第四个参数是钩子相关函数的ID用以指定想让钩子去钩哪个线程,为0时则拦截整个系统的消息此时为全局钩子。如果指定确定的线程,即为线程专用钩子。   
全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子则可包含在可执行文件中。得到控制权的钩子函数在处理完消息后,可以调用另外一个SDK中的API函数CallNextHookEx来继续传递该消息。也可以通过直接返回TRUE来丢弃该消息,阻止该消息的传递。   
使用全局钩子函数时需要以DLL为载体,VC6中有三种形式的MFC   DLL可供选择,即Regular   statically   linked   to   MFC   DLL(标准静态链接MFC   DLL)、Regular   using   the   shared   MFC   DLL(标准动态链接MFC   DLL)以及Extension   MFC   DLL(扩展MFC   DLL)。第一种DLL在编译时把使用的MFC代码链接到DLL中,执行程序时不需要其他MFC动态链接类库的支持,但体积较大;第二种DLL在运行时动态链接到MFC类库,因而体积较小,但却依赖于MFC动态链接类库的支持;这两种DLL均可被MFC程序和Win32程序使用。第三种DLL的也是动态连接,但做为MFC类库的扩展,只能被MFC程序使用。     
三、Win32   DLL   
Win32   DLL的入口和出口函数都是DLLMain这同Win16   DLL是有区别的。只要有进程或线程载入和卸载DLL时,都会调用该函数,其原型是:   
BOOL   WINAPI   DllMain(HINSTANCE   hinstDLL,DWORD   fdwReason,   LPVOID   lpvReserved);其中,第一个参数表示DLL的实例句柄;第三个参数系统保留;第二个参数指明了当前调用该动态连接库的状态,它有四个可能的值:DLL_PROCESS_ATTACH(进程载入)、DLL_THREAD_ATTACH(线程载入)、DLL_THREAD_DETACH(线程卸载)、DLL_PROCESS_DETACH(进程卸载)。在DLLMain函数中可以通过对传递进来的这个参数的值进行判别,根据不同的参数值对DLL进行必要的初始化或清理工作。由于在Win32环境下,所有进程的空间都是相互独立的,这减少了应用程序间的相互影响,但大大增加了编程的难度。当进程在动态加载DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。当DLL内存被映射到进程空间中,每个进程都有自己的全局内存拷贝,加载DLL的每一个新的进程都重新初始化这一内存区域,也就是说进程不能再共享DLL。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。一种方法便是把这些需要共享的数据单独分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享,建立一个内存共享的DLL。   
  
四、全局共享数据的实现   
可以用#pragma   data_seg建立一个新的数据段并定义共享数据,其具体格式为:   
#pragma   data_seg   ( "shareddata ")     
HWND   sharedwnd=NULL;//共享数据     
#pragma   data_seg()     
所有在data_seg   pragmas语句之间声明的变量都将在shareddata段中。仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:     
SETCTIONS     
shareddata   READ   WRITE   SHARED     
另一种方法是在项目设置链接选项中加入如下语句:     
/SECTION:shareddata,rws     
五、鼠标钩子程序示例   
本示例程序用到全局钩子函数,程序分两部分:可执行程序MouseDemo和动态连接库MouseHook。首先编制MFC扩展动态连接库MouseHook.dll:   
(一)选择MFC   AppWizard(DLL)创建项目Mousehook;   
(二)选择MFC   Extension   DLL(MFC扩展DLL)类型;   
(三)通过Project菜单的AddToProject子菜单的 "New… "添加头文件MouseHook.h。   
(四)在头文件中建立钩子类:     
  class   AFX_EXT_CLASS   CMouseHook:public   CObject     
  {     
  public:     
  CMouseHook();  //钩子类的构造函数     
  ~CMouseHook();    //钩子类的析构函数     
  BOOL   StartHook(HWND   hWnd);     //安装钩子函数     
  BOOL   StopHook();        //卸载钩子函数     
};     
(五)在MouseHook.cpp文件中加入#include "MouseHook.h "语句;   
(六)加入全局共享数据变量:     
  #pragma   data_seg( "mydata ")     
  HWND   glhPrevTarWnd=NULL;    //上次鼠标所指的窗口句柄     
  HWND   glhDisplayWnd=NULL;    //显示目标窗口标题编辑框的句柄     
  HHOOK   glhHook=NULL;     //安装的鼠标勾子句柄     
  HINSTANCE   glhInstance=NULL;   //DLL实例句柄     
#pragma   data_seg()     
(七)在DEF文件中定义段属性:     
  SECTIONS     
  mydata   READ   WRITE   SHARED     
(八)在主文件MouseHook.cpp的DllMain函数中加入保存DLL实例句柄的语句:     
extern   "C "   int   APIENTRY     
DllMain(HINSTANCE   hInstance,   DWORD   dwReason,   LPVOID   lpReserved)   
{   
UNREFERENCED_PARAMETER(lpReserved);   
if   (dwReason   ==   DLL_PROCESS_ATTACH)   
{   
if   (!AfxInitExtensionModule(MouseHookDLL,   hInstance))   
return   0;   
new   CDynLinkLibrary(MouseHookDLL);   
glhInstance=hInstance;        //插入保存DLL实例句柄   
}   
else   if   (dwReason   ==   DLL_PROCESS_DETACH)   
{   
AfxTermExtensionModule(MouseHookDLL);   
}   
return   1;   //   ok   
}   
对我有用[1]丢个板砖[0]引用举报管理TOP
精华推荐:[原创]VC++下编译出极小的程序.


hydcumt
(hyd)
等 级:
#4楼 得分:0回复于:2003-08-22 10:24:56
这些文章你可以在GOOGLE里去搜索,很多!
对我有用[0]丢个板砖[0]引用举报管理TOP
精华推荐:有点意思的计算机故事


bluebohe
(薄荷)
等 级:
2
2
#5楼 得分:0回复于:2003-08-22 10:25:04
这个函数最重要的部分是调用AfxInitExtensionModule(),它初始化DLL使它在MFC框架中正确的工作。它需要传递给DllMain()的DLL实例句柄和AFX_EXTENSION_MODULE结构,结构中存在着对MFC有用的信息。   
(九)   类CMouseHook的成员函数的具体实现:   
Cmousehook::Cmousehook()   //类构造函数     
{     
}     
Cmousehook::~Cmousehook()   //类析构函数     
{     
  stophook();     
}     
BOOL   Cmousehook::starthook(HWND   hWnd)    //安装钩子并设定接收显示窗口句柄     
{     
BOOL   bResult=FALSE;     
glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);     
if(glhHook!=NULL)     
bResult=TRUE;     
glhDisplayWnd=hWnd;    //设置显示目标窗口标题编辑框的句柄     
return   bResult;     
}     
BOOL   Cmousehook::stophook()    //卸载钩子     
{     
BOOL   bResult=FALSE;     
if(glhHook)     
{     
bResult=   UnhookWindowsHookEx(glhHook);     
if(bResult)     
{     
glhPrevTarWnd=NULL;     
glhDisplayWnd=NULL;//清变量     
glhHook=NULL;     
}     
}     
return   bResult;     
}     
(十)   钩子函数的实现   
LRESULT   WINAPI   MouseProc(int   nCode,WPARAM   wparam,LPARAM   lparam)     
{     
LPMOUSEHOOKSTRUCT   pMouseHook=(MOUSEHOOKSTRUCT   FAR   *)   lparam;     
if   (nCode> =0)     
{     
HWND   glhTargetWnd=pMouseHook-> hwnd;    //取目标窗口句柄     
HWND   ParentWnd=glhTargetWnd;     
while   (ParentWnd   !=NULL)     
{     
glhTargetWnd=ParentWnd;     
ParentWnd=GetParent(glhTargetWnd);   //取应用程序主窗口句柄     
}     
if(glhTargetWnd!=glhPrevTarWnd)     
{     
char   szCaption[100];     
GetWindowText(glhTargetWnd,szCaption,100);   //取目标窗口标题     
if(IsWindow(glhDisplayWnd))     
SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption);     
glhPrevTarWnd=glhTargetWnd;       //保存目标窗口     
}     
}     
return   CallNextHookEx(glhHook,nCode,wparam,lparam);   //继续传递消息     
}     
编译完成便可得到运行时所需的鼠标钩子的动态连接库MouseHook.dll和链接时用到的MouseHook.lib。   
六、集成   
下面新建一调用鼠标钩子动态连接库的钩子可执行程序:   
(一)   用MFC的AppWizard(EXE)创建项目MouseDemo;   
(二)   选择 "基于对话应用 ",其余几步均为确省;   
(三)   在对话框上加入一个编辑框IDC_EDIT1;   
(四)   在MouseDemo.h中加入对Mousehook.h的包含语句:#Include "Mousehook.h ";   
(五)   在CMouseDemoDlg.h的CMouseDemoDlg类定义中添加私有数据成员:CMouseHook   m_hook;     
(六)   在OnInitDialog函数的 "TODO注释 "后添加:   
CWnd   *   pwnd=GetDlgItem(IDC_EDIT1);    //取得编辑框的类指针     
m_hook.StartHook(pwnd-> GetSafeHwnd());   //取得编辑框的窗口句柄并安装钩子     
(七)链接DLL库,即把Mousehook.lib加入到项目设置链接标签中;     
(八)把MouseHook.h和MouseHook.lib复制到MouseDemo工程目录中,MouseHook.dll复制到Debug目录下。编译执行程序即可。当鼠标滑过窗口时便会在编辑框中将此窗口的标题显示出来。   
结论:   
系统钩子具有相当强大的功能,通过这种技术可以对几乎所有的Windows   
系统消息进行拦截、监视、处理。这种技术可以广泛应用于各种软件,尤其是需要   
有监控、自动记录等对系统进行监测功能的软件。本程序只对鼠标消息进行拦截,   
相应的也可以在Win32环境下对键盘、端口等应用此技术完成特定的功能。   
对我有用[1]丢个板砖[0]引用举报管理TOP
精华推荐:CCTV8于3月25日开播《郑和下西洋


csdn_lee
(混口饭吃)
等 级:
#6楼 得分:0回复于:2003-08-22 10:42:49
关注
对我有用[0]丢个板砖[0]引用举报管理TOP


farfh
(阿远之哈儿)
等 级:
#7楼 得分:0回复于:2003-08-22 11:45:39
up
对我有用[0]丢个板砖[0]引用举报管理TOP


wj59
(wj59)
等 级:
#8楼 得分:0回复于:2003-08-22 11:52:31
对应关系: 


Specifies   the   type   of   hook   procedure   to   be   installed.   This   parameter   can   be   one   of   the   following   values.   Value   Description   


WH_CALLWNDPROC   Installs   a   hook   procedure   that   monitors   messages   before   the   system   sends   them   to   the   destination   window   procedure.   For   more   information,   see   the   CallWndProc   hook   procedure.   
WH_CALLWNDPROCRET   Installs   a   hook   procedure   that   monitors   messages   after   they   have   been   processed   by   the   destination   window   procedure.   For   more   information,   see   the   CallWndRetProc   hook   procedure.   
WH_CBT   Installs   a   hook   procedure   that   receives   notifications   useful   to   a   computer-based   training   (CBT)   application.   For   more   information,   see   the   CBTProc   hook   procedure.   
WH_DEBUG   Installs   a   hook   procedure   useful   for   debugging   other   hook   procedures.   For   more   information,   see   the   DebugProc   hook   procedure.   
WH_FOREGROUNDIDLE   Installs   a   hook   procedure   that   will   be   called   when   the   application 's   foreground   thread   is   about   to   become   idle.   This   hook   is   useful   for   performing   low   priority   tasks   during   idle   time.   For   more   information,   see   the   ForegroundIdleProc   hook   procedure.     
WH_GETMESSAGE   Installs   a   hook   procedure   that   monitors   messages   posted   to   a   message   queue.   For   more   information,   see   the   GetMsgProc   hook   procedure.   
WH_JOURNALPLAYBACK   Installs   a   hook   procedure   that   posts   messages   previously   recorded   by   a   WH_JOURNALRECORD   hook   procedure.   For   more   information,   see   the   JournalPlaybackProc   hook   procedure.   
WH_JOURNALRECORD   Installs   a   hook   procedure   that   records   input   messages   posted   to   the   system   message   queue.   This   hook   is   useful   for   recording   macros.   For   more   information,   see   the   JournalRecordProc   hook   procedure.   
WH_KEYBOARD   Installs   a   hook   procedure   that   monitors   keystroke   messages.   For   more   information,   see   the   KeyboardProc   hook   procedure.   
WH_KEYBOARD_LL   Windows   NT/2000/XP:   Installs   a   hook   procedure   that   monitors   low-level   keyboard   input   events.   For   more   information,   see   the   LowLevelKeyboardProc   hook   procedure.   
WH_MOUSE   Installs   a   hook   procedure   that   monitors   mouse   messages.   For   more   information,   see   the   MouseProc   hook   procedure.   
WH_MOUSE_LL   Windows   NT/2000/XP:   Installs   a   hook   procedure   that   monitors   low-level   mouse   input   events.   For   more   information,   see   the   LowLevelMouseProc   hook   procedure.   
WH_MSGFILTER   Installs   a   hook   procedure   that   monitors   messages   generated   as   a   result   of   an   input   event   in   a   dialog   box,   message   box,   menu,   or   scroll   bar.   For   more   information,   see   the   MessageProc   hook   procedure.   
WH_SHELL   Installs   a   hook   procedure   that   receives   notifications   useful   to   shell   applications.   For   more   information,   see   the   ShellProc   hook   procedure.   
WH_SYSMSGFILTER   Installs   a   hook   procedure   that   monitors   messages   generated   as   a   result   of   an   input   event   in   a   dialog   box,   message   box,   menu,   or   scroll   bar.   The   hook   procedure   monitors   these   messages   for   all   applications   in   the   same   desktop   as   the   calling   thread.   For   more   information,   see   the   SysMsgProc   hook   procedure.   
对我有用[0]丢个板砖[0]引用举报管理TOP


daineng
(纸玉鸢尾)
等 级:
#9楼 得分:0回复于:2003-08-22 13:34:04
hydcumt(hyd)的文章不错,不过好象一直就callback来说callback,把‘被调用者回头调用调用者的函数’的意思解释了一遍。也许这是根本,但我更想具体知道点实例,比如多线程中Wait...Ex函数,光是一个‘被调用者回头调用调用者的函数’未必解释的适合(充分)。 
顺便问问,这个东西的出处是什么?可以发给我吗(如果只有几M的话发到我的邮箱daineng@handsome.com.cn,谢谢!不行的话告诉我名称也行)
对我有用[0]丢个板砖[0]引用举报管理TOP


daineng
(纸玉鸢尾)
等 级:
#10楼 得分:0回复于:2003-08-22 13:50:47
bluebohe(薄荷)   的这篇文章原来的题目叫‘Win32全局钩子在VC5中的实现’是吧!可能你的来源并不一样,至少我这里没有最后的‘结论’!   ;)   不过我这里又比你的多了一句:(作者地址:辽宁省铁岭县委机要局   112000   收稿日期:1998.12.14)   呵呵
对我有用[0]丢个板砖[0]引用举报管理TOP


daineng
(纸玉鸢尾)
等 级:
#11楼 得分:0回复于:2003-08-22 13:55:40
还有   wj59(wj59)   啊,你的这些东西在MSDN里的地址是ms-help://MS.VSCC.2003/MS.MSDNQTR.2003APR.1033/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.htm 
我以后会仔细看的
对我有用[0]丢个板砖[0]引用举报管理TOP


bluebohe
(薄荷)
等 级:
2
2
#12楼 得分:0回复于:2003-08-22 14:43:48
daineng(纸玉鸢尾)   求别人不入求自己,你既然有相关资料,那你还问个P? 


这些资料是在我机器的一个.txt文件里面的内容,文件来源已不可考究,但我发誓,这是整个txt文件中的所有文字,没有缺一个。 


不用MSDN,不用任何帮助的话,你能编出一个像样的程序?如何把资源比如书籍网络上的知识举一反三消化成自己的本事,才叫厉害,至少我,从这篇文章得到了启迪 


如果是文章的作者对我说,这是我的作品,不该转载,那么我会向他做最诚挚的赔礼道歉。 
但是,如果我把我自己觉得好的东西共享给你你却不领情的话,那我想这和对     弹琴没有两样。 


总之,以后我在CSDN回帖子的时候,如果注意到搂主用户名是daineng(纸玉鸢尾)   ,我是不会回的,这也是我在CSDN回你的最后一个帖子
对我有用[0]丢个板砖[0]引用举报管理TOP


vcforever
(累)
等 级:
#13楼 得分:0回复于:2003-08-22 18:15:15
大侠们还真能写啊!佩服 
学习学习!
对我有用[0]丢个板砖[0]引用举报管理TOP


brytison
(辉子)
等 级:
#14楼 得分:0回复于:2004-01-29 17:01:40
MARK
对我有用[0]丢个板砖[0]引用举报管理TOP
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值