一个简单的键盘钩子程序


      实现适时监视键盘,并将按键信息保存在TXT文件中的程序
 
      Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。本文在VC6编程环境下实现了一个简单的键盘钩子程序,并对Win32全局钩子的运行机制、Win32 DLL的特点、VC6环境下的MFC DLL以及共享数据等相关知识进行了简单的阐述。

      一.Win32全局钩子的运行机制

      钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,这个函数的原型是

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

      其中,第个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。其中,全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。

      二.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的分类及特点

      在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.建立钩子KeyboardHook.dll

      (1)选择MFC AppWizard(DLL)创建项目Mousehook;

      (2)选择MFC Extension DLL(共享MFC拷贝)类型;

      (3)由于VC6没有现成的钩子类,所以要在项目目录中创建KeyboardHook.h文件,在其中建立钩子类:
class AFX_EXT_CLASS CKeyboardHook : public CObject 
{
public:
      CKeyboardHook();//钩子类的构造函数

      virtual ~CKeyboardHook();//钩子类的析构函数

public:
      BOOL StartHook(); //安装钩子函数

      BOOL StopHook();//卸载钩子函数
};

      (4)在KeyboardHook.cpp文件的顶部加入#include "KeyboardHook.h"语句;

      (5)在KeyboardHook.cpp文件的顶部加入全局共享数据变量:
#pragma data_seg("mydata")
      HHOOK glhHook=NULL;         //安装的鼠标勾子句柄
      HINSTANCE glhInstance=NULL; //DLL实例句柄
#pragma data_seg()

      (6)在DEF文件中定义段属性:
SECTIONS
mydata READ WRITE SHARED

      (7)在主文件KeyboardHook.cpp的DllMain函数中加入保存DLL实例句柄的语句:
      glhInstance=hInstance;//插入保存DLL实例句柄

      (8)键盘钩子函数的实现:
//键盘钩子函数
LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
      char ch=0;
      FILE *fl;
      if( ((DWORD)lParam&0x40000000) && (HC_ACTION==nCode) ) //有键按下
      {
            if( (wParam==VK_SPACE)||(wParam==VK_RETURN)||(wParam>=0x2f ) &&(wParam<=0x100) )
            {
                  fl=fopen("key.txt","a+");    //输出到key.txt文件
                  if (wParam==VK_RETURN)
                  {
                        ch='/n';
                  }
                  else
                  {
                        BYTE ks[256];
                        GetKeyboardState(ks);
                        WORD w;
                        UINT scan=0;
                        ToAscii(wParam,scan,ks,&w,0);
                        //ch=MapVirtualKey(wParam,2); //把虚键代码变为字符
                        ch =char(w);
                  }
                  fwrite(&ch, sizeof(char), 1, fl);
            }
            fclose(fl);
      }
      return CallNextHookEx( glhHook, nCode, wParam, lParam );
}

      (9)类CKeyboardHook的成员函数的具体实现:
CKeyboardHook::CKeyboardHook()
{
}

CKeyboardHook::~CKeyboardHook()
{
      if(glhHook)
            UnhookWindowsHookEx(glhHook);
}

//安装钩子并设定接收显示窗口句柄
BOOL CKeyboardHook::StartHook()
{
      BOOL bResult=FALSE;
      glhHook=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);
      /*============================================================
      HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
      参数idHook表示钩子类型,它是和钩子函数类型一一对应的。
      比如,WH_KEYBOARD表示安装的是键盘钩子,WH_MOUSE表示是鼠标钩子等等。

      Lpfn是钩子函数的地址。

      HMod是钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL;对于系统钩子,
      该参数为钩子函数所在的DLL句柄。

      dwThreadId 指定钩子所监视的线程的线程号。对于全局钩子,该参数为NULL。

      SetWindowsHookEx返回所安装的钩子句柄。
      值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。
      线程钩子一般在当前线程或者当前线程派生的线程内,
      而系统钩子必须放在独立的动态链接库中,实现起来要麻烦一些。
      ===========================================================*/
      if(glhHook!=NULL)
      bResult=TRUE;
      return bResult;
}

//卸载钩子
BOOL CKeyboardHook::StopHook()
{
      BOOL bResult=FALSE;
      if(glhHook)
      {
            bResult= UnhookWindowsHookEx(glhHook);
            if(bResult)
                  glhHook=NULL;
      }
      return bResult;
}

      (10)编译项目生成KeyboardHook.dll。

      2.创建钩子可执行程序
      (1)用MFC的AppWizard(EXE)创建项目KeyHook;

      (2)选择“基于对话应用”并按下“完成”键;

      (3)在KeyHookDlg.h中加入包含语句#include "KeyboardHook.h";

      (4)在KeyHookDlg.h中添加私有数据成员:
      CKeyboardHook m_hook;//加入钩子类作为数据成员

      (5)链接DLL库,即把../KeyboardHook.lib加入到项目设置链接标签中;

      (6)把OK按钮ID改为ID_HOOK,写实现代码:
void CKeyHookDlg::OnHook()
{
      m_hook.StartHook();
}

      (7)关闭按钮实现:
void CKeyHookDlg::OnCancel()
{
      m_hook.StopHook();
      CDialog::OnCancel();
}

      (8)编译项目生成可执行文件;

      运行生成的KeyHook.exe程序,按HOOK!按钮,加载钩子后按下键盘上的一些键,可以发现EXE目录下自动生成了一个key.txt文件,该文件记载了你的按键信息.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是使用 VB6.0 编写的一个简单键盘钩子程序: 1. 创建新项目,选择“标准 EXE”模板。 2. 在工具箱中选择“模块”(Module),添加一个新模块。 3. 在模块中添加以下代码: ``` Option Explicit ' 定义钩子句柄和回调函数 Private hHook As Long Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" _ (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal nCode As Long, _ ByVal wParam As Long, lParam As Any) As Long ' 声明键盘钩子回调函数 Private Function KeyboardHook(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long If nCode = HC_ACTION Then ' 如果键盘事件是按键按下事件 If wParam = WM_KEYDOWN Then ' 输出按键码 Debug.Print "Key Down: " & lParam And &HFF End If End If ' 继续传递钩子消息 KeyboardHook = CallNextHookEx(hHook, nCode, wParam, lParam) End Function ' 安装钩子 Public Sub InstallHook() hHook = SetWindowsHookEx(WH_KEYBOARD_LL, AddressOf KeyboardHook, App.hInstance, 0) End Sub ' 卸载钩子 Public Sub UninstallHook() UnhookWindowsHookEx hHook End Sub ``` 4. 在“项目”(Project)菜单中选择“引用”(References),勾选“Microsoft Windows Common Controls 6.0”,以便使用调试输出窗口。 5. 在“项目”(Project)菜单中选择“属性”(Properties),在“启动对象”(Startup object)中选择“Sub Main”。 6. 在“Sub Main”中添加以下代码: ``` Sub Main() ' 安装钩子 InstallHook ' 进入消息循环 DoEvents ' 卸载钩子 UninstallHook End Sub ``` 7. 运行程序,按下键盘上的任意按键,可以在调试输出窗口中看到按键码的输出。 注意:由于钩子是全局的,可能会对系统稳定性产生影响,慎重使用。同时,此程序只是一个简单的示例,实际应用中还需要考虑更多的安全性和稳定性问题。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值