钩子的原理

windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。可见,利用钩子可以实现许多特殊而有用的功能。因此,对于高级编程人员来说,掌握钩子的编程方法是很有必要的。

钩子的类型
  一. 按事件分类,有如下的几种常用类型
  (1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。
  (2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。
  (3) 外壳钩子可以监视各种Shell事件消息。比如启动和关闭应用程序。
  (4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。
  (5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。
  此外,还有一些特定事件的钩子提供给我们使用,不一一列举。
下面描述常用的Hook类型:
1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPRO
CRET Hook子程。WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
2、WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
2. 完成系统指令;
3. 来自系统消息队列中的移动鼠标,键盘事件;
4. 设置输入焦点事件;
5. 同步系统消息队列事件。


程序介绍:

利用这个程序:
1.可以监视在你的电脑运行的程序, 把在你的电脑运行过的程序的时间和名字记录下来;
2.可以阻止你规定的禁用程序的执行, 比如不让玩游戏。
3.这个程序需要加入注册表, 在系统启动时就运行, 达到监视的目的。注册表大概都不陌生,就是这里:
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run

程序的记录格式:

2003-02-03 17:31:25 - [System Startup - Windows XP 5.01.2600]
2003-02-03 17:31:29 "CabinetWClass" -> "我的电脑"
2003-02-03 17:31:59 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:32:19 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:32:35 "OpusApp" -> "Microsoft Word"
2003-02-03 17:32:50 - [System Shutdown - 0 days, 0 hrs, 1 mins, 25 secs]

2003-02-03 17:35:37 - [System Startup - Windows 98 SE 4.10.2222]
2003-02-03 17:35:53 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:36:05 "CabinetWClass" -> ""
2003-02-03 17:36:31 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:36:56 "ExploreWClass" -> ""
2003-02-03 17:37:07 - [System Shutdown - 0 days, 0 hrs, 1 mins, 30 secs]

程序运行只需要3个文件:
   hwhpapp.exe 可执行文件
   hwhpdrv.dll 安装钩子的动态链接库
   hwhpapp.cfg 禁用软件黑名单, 可用记事本修改
程序运行会自动产生记录文件:
   hwhpapp.sys 可以用记事本打开看

程序原理:

一.钩子
利用 API 函数 SetWindowsHookEx() 安装一个全局钩子, 钩子类型为 WH_SHELL。
WH_SHELL 钩子可监视所有应用程序的主窗口创建或者关闭。
最典型的应用就是 Windows 的状态栏,当程序运行时,把主窗口的标题加入状态栏,程序退出时,从状态栏删除。
如果你截获这个钩子,可以做到禁止状态栏的显示,也可以自己作一个状态栏,或者作一个历史记录,把所有在你电脑曾经运行的程序记录下来。如果运行的程序不是你希望的,你可以直接关闭这个程序,达到禁止运行的目的。

二.动态链接库
由于钩子是全局的,必须把这个钩子定义到 .DLL 的动态链接库里,这就涉及到建立动态链接库。

三.共享内存
由于钩子是安装到系统里的,钩子的运行是在操作系统里面,因此这个钩子不能使用你的程序所定义的任何全局变量!
既然如此,有什么办法解决呢?在本程序里利用共享内存技术,利用 API 函数 CreateFileMapping() 可创建共享内存,这个内存可以在任何运行的程序中使用,也就是说任何运行的 .EXE、.DLL 和其他程序都可以使用这块内存。在本程序中直接使用了 Victor 串口 VCL 控件里的 TSharedMemory 共享内存类。

四.记录文件和软件黑名单文件
把所有在你的电脑执行的程序记录保存在一个文本文件里, 因为扩展名为 .txt 很容易被发现,因此采用扩展名 .sys
软件黑名单文件保存在 .cfg 文件里,同样因为 .ini 文件很容易发现并且打开修改。
这两个文件都保存在与你的 .exe 文件的相同文件夹里,并且与 .exe 文件同名.

五.保证你的程序只能同时运行一个
如果你同时运行两个程序,记录文件就会乱套,所以必须保证只能运行一个。
当你的程序刚开始运行的时候,就是在 WinMain() 函数的最开始,就要判断是否已经运行了,如果已经运行,就直接退出。
判断的方法很简单,就是检查程序共享的内存是否存在,如果检查到共享的内存已经存在,那就是已经运行了。

六.程序隐身, 不能显示在任务栏和任务管理器里面
这个也很简单,只要在主程序的 Application->Run(); 前面加一句话:
SetWindowLong(Application->Handle, GWL_EXSTYLE, GetWindowLong(Application->Handle, GWL_EXSTYLE)|WS_EX_TOOLWINDOW);
就可以了。


--------------------------------------------------------------------------------

程序介绍
.DLL 文件:这是最关键的钩子的代码:

#include <vcl.h>
#include "yb_base.h" //Victor 串口控件里面的一个头文件

#define MYAPPMARK "VICTOR_APPMONI_20010612" //共享内存标志

class __export THookedProcs
{
public:
THookedProcs();
~THookedProcs();
void WINAPI InitFuncs(void);
void WINAPI UninitFuncs(void);
private:
HHOOK hThisHook; //保存钩子的句柄
static LRESULT CALLBACK HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam);
};

//定义共享的数据结构
typedef struct
{
HHOOK hHook; //当前使用的 HOOK
//... 此处可增加其他共享的数据
} THookSharedData;

THookedProcs::THookedProcs()
{
hThisHook = NULL;
}

THookedProcs::~THookedProcs()
{
UninitFuncs();
}

void WINAPI THookedProcs::InitFuncs(void)
{
UninitFuncs();
hThisHook = SetWindowsHookEx(WH_SHELL, (HOOKPROC) HookedShellProc, HInstance, 0);
TSharedMemory AppMem(MYAPPMARK,4096); //在 EXE 文件里共享的内存
THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
HookSharedData->hHook = hThisHook; //把 hThisHook 保存到共享内存里
}

void WINAPI THookedProcs::UninitFuncs(void)
{
if(hThisHook)
   {
    UnhookWindowsHookEx(hThisHook);
    hThisHook = NULL;
   }
}

LRESULT CALLBACK THookedProcs::HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam)
{
TSharedMemory AppMem(MYAPPMARK, 4096); //使用在 .EXE 文件里共享的内存
if(AppMem.Valid)if(AppMem.Exists) //如果共享内存存在
   {
    HWND hMainWnd = AppMem.AppInfo->hMainForm;

    if(hMainWnd)
     {
      if(nCode==HSHELL_WINDOWCREATED)
       {
         PostMessage(hMainWnd, WM_USERCMD, UC_WINHOOK, wParam);
       }
     }
   }

//在 Hook 里无法调用 hThisHook, 必须用共享内存里面的 hHook
THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
return CallNextHookEx(HookSharedData->hHook, nCode, wParam, lParam);
}


--------------------------------------------------------------------------------

EXE文件主程序的代码:

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int)
{
if(!AppMem.Valid)
   {
    return 1;
   }
if(AppMem.Exists) //已经存在 (程序以前已经运行过了, 并且正在运行)
   {
    if(stricmp(lpCmdLine, "/SHOW")==0) //如果监测到命令行参数 /SHOW 就显示出已经运行的程序的主窗口
     {
      PostMessage(AppMem.AppInfo->hMainForm, WM_USERCMD, UC_SHOWWIN, 0);
     }
    return 0;
   }
AppMem.ClearBuffer();

try
   {
     Application->Initialize();
     Application->CreateForm(__classid(TFormMain), &FormMain);
     //下面的语句是防止显示在状态栏和任务管理器
     SetWindowLong(Application->Handle, GWL_EXSTYLE, GetWindowLong(Application->Handle, GWL_EXSTYLE)|WS_EX_TOOLWINDOW);
     Application->Run();
   }
catch (Exception &exception)
   {
     Application->ShowException(&exception);
   }
catch (...)
   {
     try
     {
       throw Exception("");
     }
     catch (Exception &exception)
     {
       Application->ShowException(&exception);
     }
   }
return 0;
}


--------------------------------------------------------------------------------

主窗口程序:

TSharedMemory AppMem(MYAPPMARK, 4096); //定义共享的内存,这个内存是真正存在的

__fastcall TFormMain::TFormMain(TComponent* Owner)
: TForm(Owner)
{
   AppMem.AppInfo->hMainWnd = Application->Handle;
   AppMem.AppInfo->hMainForm = Handle;
   WriteStartupMessage(); //在记录文件里加入启动信息
   PostMessage(Handle, WM_USERCMD, UC_INITWIN, 0);

   MyHook = new THookedProcs;
   MyHook->InitFuncs(); //安装钩子
}
//---------------------------------------------------------------------------
__fastcall TFormMain::~TFormMain()
{
   MyHook->UninitFuncs(); //删除钩子
   delete MyHook;
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::FormCloseQuery(TObject *Sender, bool &CanClose)
{
   WriteShutdownMessage(); //在记录文件里加入退出信息
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::WndProc(Messages::TMessage &Message)
{
   if(Message.Msg == WM_USERCMD)
   {
     if(Message.WParam == UC_SHOWWIN)
     {
       Show();
       AppMem.ActiveAppWnd();
     }
     else if(Message.WParam == UC_INITWIN)
     {
       Hide();
       Left = (Screen->Width - Width) / 2;
       Top = (Screen->Height - Height) / 2;
     }
     else if(Message.WParam == UC_WINHOOK)
     {
       WinHookMessage(Message.LParam);
     }
   }
   TForm::WndProc(Message);
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::BnExitClick(TObject *Sender)
{
   Close();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::BnHideClick(TObject *Sender)
{
   Hide();
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::CreateParams(Controls::TCreateParams &Params)
{
   TForm::CreateParams(Params);
   Params.Style = WS_OVERLAPPED|WS_DLGFRAME|WS_CAPTION|WS_SYSMENU;
   Params.ExStyle |= WS_EX_TOPMOST;
   Params.X = Screen->Width - 8;
   Params.Y = Screen->Height - 8;
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::WinHookMessage(long Param)
{
   HWND hWnd = (HWND) Param;

   char szWinCaption[256];
   char szWinClass[256];
   AnsiString s;

   DateTimeX x;
   s += AnsiString().sprintf("%04d-%02d-%02d %02d:%02d:%02d ", x.Year, x.Month, x.Day, x.Hour, x.Minute, x.Second);

   if(!GetWindowText(hWnd, szWinCaption, 256))
    *szWinCaption = 0;
   if(!GetClassName(hWnd,szWinClass,256))
    *szWinClass = 0;
   s += "/"" + AnsiString(szWinClass) + "/" -> /"" + AnsiString(szWinCaption) + "/"";

   if(!ValidAppCheck(hWnd, szWinClass,szWinCaption))
   s += " (关闭禁用程序)";
   s += "/r/n";

   TBinFileFuncs::WriteLogFileInfo(TRelPath().Extension("sys").c_str(), s.c_str()); //与 .exe 同名的 .sys 文件
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::WriteStartupMessage(void)
{
   AnsiString s;
   DateTimeX x = StartupTime;
   TSysInfo si;

   s += "========== Copyright (C) Victor Chen ===== Email: victor@cppfans.com ==========/r/n";
   s += AnsiString().sprintf("%04d-%02d-%02d %02d:%02d:%02d - ", x.Year, x.Month, x.Day, x.Hour, x.Minute, x.Second);
   s += AnsiString().sprintf("[System Startup - %s %d.%02d.%04d]", si.OS->OSName, si.OS->MajorVer, si.OS->MinorVer, si.OS->BuildNum);
   s += "/r/n";

   TBinFileFuncs::WriteLogFileInfo(TRelPath().Extension("sys").c_str(), s.c_str()); //与 .exe 同名的 .sys 文件
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::WriteShutdownMessage(void)
{
   AnsiString s;
   DateTimeX x;
   TimeX t; t.S000 = (x - StartupTime) % (24*60*60);
   int d = (x - StartupTime) / (24*60*60);

   s += AnsiString().sprintf("%04d-%02d-%02d %02d:%02d:%02d - ", x.Year, x.Month, x.Day, x.Hour, x.Minute, x.Second);
   s += AnsiString().sprintf("[System Shutdown - %d days, %d hrs, %d mins, %d secs]", d, t.Hour, t.Minute, t.Second);
   s += "/r/n";

   TBinFileFuncs::WriteLogFileInfo(TRelPath().Extension("sys").c_str(), s.c_str()); //与 .exe 同名的 .sys 文件
}
//---------------------------------------------------------------------------
bool __fastcall TFormMain::ValidAppCheck(HWND hwnd, char *cls, char *cap)
{
   TBinFile f;
   f.FileName = TRelPath().Extension("cfg"); //与 .exe 同名的 .cfg 文件
   f.OpenMode = TBinFile::omRead; //准备读文件

   if(f.Exists) //如果文件存在
   {
     try
     {
       f.Active = true; //打开文件
       char aLine[2048];
       while(fgets(aLine,2000,f)) //读一行文本 (标准 C 函数)
       {
         if(strnicmp(aLine,"CLASS=",6)==0) //用类名识别
         {
           TBinFileFuncs::DeleteSpaces(aLine+6,1,1); //去除空格
           if(stricmp(aLine+6,cls)==0)
           {
             PostMessage(hwnd, WM_CLOSE, 0, 0); //关闭程序
             return false;
           }
         }
         else if(strnicmp(aLine,"CAPTION=",8)==0) //用标题识别
         {
           TBinFileFuncs::DeleteSpaces(aLine+8,1,1); //去除空格
           if(stricmp(aLine+8,cap)==0)
           {
             PostMessage(hwnd, WM_CLOSE, 0, 0); //关闭程序
             return false;
           }
         }
       }
     }
     catch(Exception &e)
     {
       //忽略错误s提示, 也可以在此处把错误信息添加到一个错误信息文件
     }
   }
   return true;
}

Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。
3、WH_DEBUG Hook
在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。
4、WH_FOREGROUNDIDLE Hook
当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。
5、WH_GETMESSAGE Hook
应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
6、WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。(估计按键精灵是用这个hook做的)
7、WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。
8、WH_KEYBOARD Hook
在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。
9、WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
10、WH_MOUSE Hook
WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。
11、WH_MOUSE_LL Hook
WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
13、WH_SHELL Hook
外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
WH_SHELL 共有5钟情況:
1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
2. 当Taskbar需要重画某个按钮;
3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
4. 当目前的键盘布局状态改变;
5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。
以上是13种常用的hook类型!
  二. 按使用范围分类,主要有线程钩子和系统钩子
  (1) 线程钩子监视指定线程的事件消息。
  (2) 系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)
中。这是系统钩子和线程钩子很大的不同之处。
   几点需要说明的地方:
  (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
  (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
  (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
编写钩子程序
   编写钩子程序的步骤分为三步:定义钩子函数、安装钩子和卸载钩子。
  1.定义钩子函数
  钩子函数是一种特殊的回调函数。钩子监视的特定事件发生后,系统会调用钩子函数进行处理。不同事件的钩子函数的形式是各不相同的。下面以鼠标钩子函数举例说明钩子函数的原型:
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam)
参数wParam和 lParam包含所钩消息的信息,比如鼠标位置、状态,键盘按键等。nCode包含有关消息本身的信息,比如是否从消息队列中移出。
我们先在钩子函数中实现自定义的功能,然后调用函数 CallNextHookEx.把钩子信息传递给钩子链的下一个钩子函数。CallNextHookEx.的原型如下:
LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam )
参数 hhk是钩子句柄。nCode、wParam和lParam 是钩子函数。
当然也可以通过直接返回TRUE来丢弃该消息,就阻止了该消息的传递。

2.安装钩子
  在程序初始化的时候,调用函数SetWindowsHookEx安装钩子。其函数原型为:
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
参数idHook表示钩子类型,它是和钩子函数类型一一对应的。比如,WH_KEYBOARD表示安装的是键盘钩子,WH_MOUSE表示是鼠标钩子等等。
  Lpfn是钩子函数的地址。
  HMod是钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL;对于系统钩子,该参数为钩子函数所在的DLL句柄。
   dwThreadId 指定钩子所监视的线程的线程号。对于全局钩子,该参数为NULL。
  SetWindowsHookEx返回所安装的钩子句柄。
  3.卸载钩子
   当不再使用钩子时,必须及时卸载。简单地调用函数 BOOL UnhookWindowsHookEx( HHOOK hhk)即可。
  
值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。线程钩子一般在当前线程或者当前线程派生的线程内,而系统钩子必须放在独立的动态链接库中,实现起来要麻烦一些。

 线程钩子的编程实例:

  按照上面介绍的方法实现一个线程级的鼠标钩子。钩子跟踪当前窗口鼠标移动的位置变化信息。并输出到窗口。
  (1)在VC++6.0中利用MFC
APPWizard(EXE)生成一个不使用文档/视结构的单文档应用mousehook。打开childview.cpp文件,加入全局变量:
HHOOK hHook;//鼠标钩子句柄
CPoint point;//鼠标位置信息
CChildView *pView;
// 鼠标钩子函数用到的输出窗口指针

  在CChildView::OnPaint()添加如下代码:
CPaintDC dc(this);
char str[256];
sprintf(str,“x=%d,y=%d",point.x,point.y);
//构造字符串
dc.TextOut(0,0,str); //显示字符串

  (2)childview.cpp文件中定义全局的鼠标钩子函数。
LRESULT CALLBACK MouseProc
(int nCode, WPARAM wParam, LPARAM lParam)
{//是鼠标移动消息
if(wParam==WM_MOUSEMOVE||wParam
==WM_NCMOUSEMOVE)
{
point=((MOUSEHOOKSTRUCT *)lParam)->pt;
//取鼠标信息
pView->Invalidate(); //窗口重画
}
return CallNextHookEx(hHook,nCode,wParam,lParam);
//传递钩子信息
}
(3)CChildView类的构造函数中安装钩子。
CChildView::CChildView()
{
pView=this;//获得输出窗口指针
hHook=SetWindowsHookEx(WH_MOUSE,MouseProc,0,GetCurrentThreadId());
}
(4)CChildView类的析构函数中卸载钩子。
CChildView::~CChildView()
{
if(hHook)
UnhookWindowsHookEx(hHook);
}

系统钩子的编程实例:
由于系统钩子要用到dll,所以先介绍下win32 dll的特点:
Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载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进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。
在VC6中有三种形式的MFC DLL(在该DLL中可以使用和继承已有的MFC类)可供选择,即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程序使用。
下面说说在VC6中全局共享数据的实现
  在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为:
  #pragma data_seg ("shareddata")
  HWND sharedwnd=NULL;//共享数据
  #pragma data_seg()
  仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:
SETCTIONS shareddata READ WRITE SHARED
  另一种方法是在项目设置链接选项中加入如下语句:
  /SECTION:shareddata,rws
好了,准备知识已经学完了,让我们开始编写个全局的钩子程序吧!

由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。
1.建立钩子Mousehook.DLL
  (1)选择MFC AppWizard(DLL)创建项目Mousehook;
  (2)选择MFC Extension DLL(共享MFC拷贝)类型;
  (3)由于VC5没有现成的钩子类,所以要在项目目录中创建Mousehook.h文件,在其中建立钩子类:
  class AFX_EXT_CLASS Cmousehook:public CObject
  {
  public:
  Cmousehook();
  //钩子类的构造函数
  ~Cmousehook();
  //钩子类的析构函数
  BOOL starthook(HWND hWnd);
  //安装钩子函数
  BOOL stophook();
  卸载钩子函数
  };
  (4)在Mousehook.app文件的顶部加入#include"Mousehook.h"语句;
  (5)加入全局共享数据变量:
  #pragma data_seg("mydata")
  HWND glhPrevTarWnd=NULL;
  //上次鼠标所指的窗口句柄
  HWND glhDisplayWnd=NULL;
  //显示目标窗口标题编辑框的句柄
  HHOOK glhHook=NULL;
  //安装的鼠标钩子句柄
  HINSTANCE glhInstance=NULL;
  //DLL实例句柄
  #pragma data_seg()
  (6)在DEF文件中定义段属性:
  SECTIONS
  mydata READ WRITE SHARED
  (7)在主文件Mousehook.cpp的DllMain函数中加入保存DLL实例句柄的语句:
  DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
  {
  //如果使用lpReserved参数则删除下面这行
  UNREFERENCED_PARAMETER(lpReserved);
  if (dwReason == DLL_PROCESS_ATTACH)
  {
   TRACE0("MOUSEHOOK.DLL Initializing!/n");
   //扩展DLL仅初始化一次
   if (!AfxInitExtensionModule(MousehookDLL, hInstance))
   return 0;
   new CDynLinkLibrary(MousehookDLL);
   //把DLL加入动态MFC类库中
   glhInstance=hInstance;
   //插入保存DLL实例句柄
  }
  else if (dwReason == DLL_PROCESS_DETACH)
  {
   TRACE0("MOUSEHOOK.DLL Terminating!/n");
   //终止这个链接库前调用它
   AfxTermExtensionModule(MousehookDLL);
  }
  return 1;
  }
  (8)类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;
  }
  (9)钩子函数的实现:
  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);
   //继续传递消息
  }
  (10)编译项目生成mousehook.dll。
  2.创建钩子可执行程序
  (1)用MFC的AppWizard(EXE)创建项目Mouse;
  (2)选择“基于对话应用”并按下“完成”键;
  (3)编辑对话框,删除其中原有的两个按钮,加入静态文本框和编辑框,用鼠标右键点击静态文本框,在弹出的菜单中选择“属性”,设置其标题为“鼠标所在的窗口标题”;

  (4)在Mouse.h中加入对Mousehook.h的包含语句#Include"../Mousehook/Mousehook.h";
  (5)在CMouseDlg.h的CMouseDlg类定义中添加私有数据成员:
  CMouseHook m_hook;//加入钩子类作为数据成员
  (6)修改CmouseDlg::OnInitDialog()函数:
  BOOL CMouseDlg::OnInitDialog()
  {
  CDialog::OnInitDialog();
  ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
  ASSERT(IDM_ABOUTBOX <0xF000);
  CMenu* pSysMenu = GetSystemMenu(FALSE);
  if (pSysMenu != NULL)
  {
   CString strAboutMenu;
   strAboutMenu.LoadString(IDS_ABOUTBOX);
   if (!strAboutMenu.IsEmpty())
   {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
   }
  }
  SetIcon(m_hIcon, TRUE);//Set big icon
  SetIcon(m_hIcon, FALSE);//Set small icon
  //TOD Add extra initialization here
  CWnd * pwnd=GetDlgItem(IDC_EDIT1);
  //取得编辑框的类指针
  m_hook.starthook(pwnd->GetSafeHwnd());
  //取得编辑框的窗口句柄并安装钩子
  return TRUE;
  //return TRUE unless you set the focus to a control
  }
  (7)链接DLL库,即把../Mousehook/debug/Mousehook.lib加入到项目设置链接标签中;
  (8)编译项目生成可执行文件;
  (9)把Mousehook.DLL拷贝到../mouse/debug目录中;
  (10)先运行几个可执行程序,然后运行Mouse.exe程序,把鼠标在不同窗口中移动,在Mouse.exe程序窗口中的编辑框内将显示出鼠标所在的应用程序主窗口的标题。

  钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入到系统。钩子的种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发往指定窗口的消息或特定事件,其监视的窗口即可以是本进程内的也可以是由其他进程所创建的。在特定的消息发出,并在到达目的窗口之前,钩子程序先行截获此消息并得到对其的控制权。此时在钩子函数中就可以对截获的消息进行各种修改处理,甚至强行终止该消息的继续传递。

  任何一个钩子都由系统来维护一个指针列表(钩子链表),其指针指向钩子的各个处理函数。最近安装的钩子放在链的开始,最早安装的钩子则放在最后,当钩子监视的消息出现时,操作系统调用链表开始处的第一个钩子处理函数进行处理,也就是说最后加入的钩子优先获得控制权。在这里提到的钩子处理函数必须是一个回调函数(callback function),而且不能定义为类成员函数,必须定义为普通的C函数。在使用钩子时可以根据其监视范围的不同将其分为全局钩子和线程钩子两大类,其中线程钩子只能监视某个线程,而全局钩子则可对在当前系统下运行的所有线程进行监视。显然,线程钩子可以看作是全局钩子的一个子集,全局钩子虽然功能强大但同时实现起来也比较烦琐:其钩子函数的实现必须封装在动态链接库中才可以使用。

  钩子的安装与卸载

  由于全局钩子具有相当的广泛性而且在功能上完全覆盖了线程钩子,因此下面就主要对应用较多的全局钩子的安装与使用进行讨论。前面已经提过,操作系统是通过调用钩子链表开始处的第一个钩子处理函数而进行消息拦截处理的。因此,为了设置钩子,只需将回调函数放置于链首即可,操作系统会使其首先被调用。在具体实现时由函数SetWindowsHookEx()负责将回调函数放置于钩子链表的开始位置。SetWindowsHookEx()函数原型声明如下:

HHOOK SetWindowsHookEx(int idHook;
HOOKPROC lpfn;
HINSTANCE hMod;
DWORD dwThreadId);

  其中:参数idHook 指定了钩子的类型,总共有如下13种:

   WH_CALLWNDPROC 系统将消息发送到指定窗口之前的"钩子"
   WH_CALLWNDPROCRET 消息已经在窗口中处理的"钩子"
   WH_CBT 基于计算机培训的"钩子"
   WH_DEBUG 差错"钩子"
   WH_FOREGROUNDIDLE 前台空闲窗口"钩子"
   WH_GETMESSAGE 接收消息投递的"钩子"
   WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD"钩子"记录的输入消息
   WH_JOURNALRECORD 输入消息记录"钩子"
   WH_KEYBOARD 键盘消息"钩子"
   WH_MOUSE 鼠标消息"钩子"
   WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息"钩子"
   WH_SHELL 外壳"钩子"
   WH_SYSMSGFILTER 系统消息"钩子"

  参数lpfn为指向钩子处理函数的指针,即回调函数的首地址;参数hMod则标识了钩子处理函数所处模块的句柄;第四个参数dwThreadId 指定被监视的线程,如果明确指定了某个线程的ID就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为0,则表示此钩子为监视系统所有线程的全局钩子。此函数在执行完后将返回一个钩子句柄。

  虽然对于线程钩子并不要求其象全局钩子一样必须放置于动态链接库中,但是推荐其也在动态链接库中实现。因为这样的处理不仅可使钩子可为系统内的多个进程访问,也可以在系统中被直接调用,而且对于一个只供单进程访问的钩子,还可以将其钩子处理过程放在安装钩子的同一个线程内,此时SetWindowsHookEx()函数的第三个参数也就是该线程的实例句柄。

  在SetWindowsHookEx()函数完成对钩子的安装后,如果被监视的事件发生,系统马上会调用位于相应钩子链表开始处的钩子处理函数进行处理,每一个钩子处理函数在进行相应的处理时都要考虑是否需要把事件传递给下一个钩子处理函数。如果要传递,就通过函数CallNestHookEx()来解决。尽管如此,在实际使用时还是强烈推荐无论是否需要事件传递而都在过程的最后调用一次CallNextHookEx( )函数,否则将会引起一些无法预知的系统行为或是系统锁定。该函数将返回位于钩子链表中的下一个钩子处理过程的地址,至于具体的返回值类型则要视所设置的钩子类型而定。该函数的原型声明如下:

LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam);

  其中,参数hhk为由SetWindowsHookEx()函数返回的当前钩子句柄;参数nCode为传给钩子过程的事件代码;参数wParam和lParam 则为传给钩子处理函数的参数值,其具体含义同设置的钩子类型有关。

  最后,由于安装钩子对系统的性能有一定的影响,所以在钩子使用完毕后应及时将其卸载以释放其所占资源。释放钩子的函数为UnhookWindowsHookEx(),该函数比较简单只有一个参数用于指定此前由SetWindowsHookEx()函数所返回的钩子句柄,原型声明如下:

BOOL UnhookWindowsHookEx(HHOOK hhk);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值