一个简单(dān)的键盘钩子程序
实现适时监视键盘,并将按键信(xìn)息保存(cún)在TXT文(wén)件中的程序
Windows系统是建立在事件驱动(dòng)的机制上的,说穿了就(jiù)是整个系统都(dōu)是通过消息的传递来(lái)实现的。而钩子是Windows系统中(zhōng)非(fēi)常重要的系统接口,用它可以截获(huò)并处(chù)理送给其(qí)他应(yīng)用程序的消息(xī),来(lái)完成普(pǔ)通应用程序难以(yǐ)实现的功能。钩子的种类很多,每种钩子可以截获并处(chù)理相应的消息,如键盘钩(gōu)子(zǐ)可(kě)以截获(huò)键盘消息,外壳钩子可(kě)以截取(qǔ)、启动(dòng)和关(guān)闭(bì)应用程序(xù)的消息等。本文在VC6编程环境下实现了一(yī)个简单的(de)键盘(pán)钩子程(chéng)序,并对Win32全局钩子的运行机制、Win32 DLL的特点、VC6环境下(xià)的MFC DLL以(yǐ)及共(gòng)享数据等相(xiàng)关(guān)知识进行了简单的阐述。
一.Win32全局钩子的运行机制
钩子实际上(shàng)是一个处理(lǐ)消息的程序段,通过系统调(diào)用(yòng),把它挂入系统。每当特定的消息发出,在没有到(dào)达目的窗口前,钩子程序(xù)就(jiù)先捕(bǔ)获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该(gāi)消息,也可以不作处理而继续传递该(gāi)消息,还可以(yǐ)强(qiáng)制结束(shù)消息的(de)传递。对每种(zhǒng)类型的钩(gōu)子由系统来维护一个钩(gōu)子链,最近(jìn)安装的(de)钩(gōu)子放在链的开始(shǐ),而最先安(ān)装的钩(gōu)子放在最后,也就是后加入(rù)的先获得(dé)控制(zhì)权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来(lái)安装这个钩子(zǐ)函(hán)数,这个函数的原型是
HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);
其中(zhōng),第个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包(bāo)含(hán)钩(gōu)子函数的模块(kuài)句柄;第四个参(cān)数(shù)指定监视的线程。如果指定确定的(de)线程,即为线(xiàn)程专用钩子(zǐ);如果指定(dìng)为空,即为全(quán)局钩子。其(qí)中,全(quán)局(jú)钩子(zǐ)函数(shù)必(bì)须包含在(zài)DLL(动态(tài)链接库)中,而线(xiàn)程专用钩子还可以包含在可执行(háng)文(wén)件(jiàn)中。得到控(kòng)制权的(de)钩子函数在完成对消(xiāo)息的处理后,如(rú)果想要该消息继续传递,那么它(tā)必须调用另(lìng)外一个SDK中的API函数CallNextHookEx来传(chuán)递它。钩子(zǐ)函数也(yě)可以通过直接返回TRUE来丢弃该(gāi)消息,并(bìng)阻止该消息的传递。
二.Win32 DLL的特点
Win32 DLL与 Win16 DLL有很大的区别,这主(zhǔ)要是由操作系(xì)统的(de)设计思想决(jué)定的。一方面,在Win16 DLL中程序入口点函数和出(chū)口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同(tóng)一函数DLLMain来实现。无论何时,当(dāng)一个进程或线(xiàn)程载入和卸载DLL时,都要调用(yòng)该(gāi)函数,它的原型是
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);
其中,第一个参数(shù)表(biǎo)示(shì)DLL的实例句柄;第三个参数系统保留;这里主要(yào)介绍一下第二个(gè)参数,它有四个(gè)可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线(xiàn)程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载(zǎi)),在DLLMain函(hán)数中可(kě)以对传递进来的这个(gè)参数的(de)值进行判别,并根据不同的(de)参数值对(duì)DLL进行(háng)必要的初始化或清(qīng)理工作。举个(gè)例子(zǐ)来(lái)说,当有(yǒu)一个进(jìn)程载入一个DLL时,系统分派给DLL的第二(èr)个参数为DLL_PROCESS_ATTACH,这时,你可(kě)以根据这个参数(shù)初始(shǐ)化特定(dìng)的数据。另(lìng)一(yī)方面,在Win16环境下(xià),所有应用程序(xù)都在同一地址空间(jiān);而在Win32环境下(xià),所有(yǒu)应用程序(xù)都有自己(jǐ)的私有空间,每个(gè)进(jìn)程的空间(jiān)都是(shì)相互独立的(de),这减少了应用程序间的相互影(yǐng)响,但同时也(yě)增加了编程的(de)难度。大家知道,在Win16环境(jìng)中,DLL的全(quán)局数据(jù)对每(měi)个(gè)载入它的进(jìn)程来说都是相同的;而在(zài)Win32环境中,情况(kuàng)却发生了变化,当(dāng)进程在载入DLL时(shí),系统自动把DLL地址(zhǐ)映(yìng)射到该进程的私(sī)有空间,而且也复制(zhì)该DLL的全局(jú)数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据(jù)其值却并不一定是相同(tóng)的。因(yīn)此(cǐ),在(zài)Win32环境下要想(xiǎng)在多个进(jìn)程中共享数据,就必(bì)须进(jìn)行(háng)必要的设置。亦即把这(zhè)些需要共享的数据分离(lí)出来,放置在一(yī)个独立的数据段里,并把(bǎ)该段的(de)属(shǔ)性设(shè)置为(wéi)共享。
三(sān).VC6中MFC DLL的分类及特点
在(zài)VC6中有三种形式的MFC DLL(在该DLL中可以(yǐ)使用和继承(chéng)已有(yǒu)的(de)MFC类(lèi))可供选择,即(jí)Regular statically linked to MFC DLL(标准静态链接MFC DLL)和Regular using the shared MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。第(dì)一种DLL的特点是,在编(biān)译(yì)时把使用的(de)MFC代码加(jiā)入到DLL中,因此,在使(shǐ)用该程序时不需要其他MFC动态链接类库的存在,但(dàn)占用磁(cí)盘空(kōng)间比(bǐ)较(jiào)大;第二(èr)种DLL的特点是,在(zài)运行时,动态链(liàn)接到MFC类库,因此减少了(le)空间的占用,但是在运行时却依赖于MFC动态链接类库;这两种DLL既可(kě)以(yǐ)被MFC程序使用也可以被Win32程序使(shǐ)用。第(dì)三(sān)种(zhǒng)DLL的特点类(lèi)似于第二种,做为MFC类库的扩展,只能被MFC程序使用(yòng)。
四(sì).在(zài)VC6中全(quán)局共享(xiǎng)数据的实现
在主文件中,用#pragma data_seg建(jiàn)立一(yī)个新的数据(jù)段并(bìng)定义(yì)共享数据(jù),其具(jù)体格(gé)式为:
#pragma data_seg ("shareddata")
HWND sharedwnd=NULL;//共享(xiǎng)数据(jù)
#pragma data_seg()
仅定义(yì)一个(gè)数(shù)据段还不能达到(dào)共享数(shù)据的目的,还要告诉编译器该段的属性,有两种方法可以(yǐ)实现该(gāi)目的(其效果是相同的),一种方法(fǎ)是在.DEF文件中加(jiā)入如(rú)下语句:
SETCTIONS
shareddata READ WRITE SHARED
另一种(zhǒng)方法(fǎ)是在项目设置(zhì)链接(jiē)选项中加入如下语句:
/SECTION:shareddata,rws
五.具(jù)体实现步骤
由于全局钩(gōu)子函数(shù)必须包含在动(dòng)态链接(jiē)库中,所以本例(lì)由两个程序体来实现。
1.建立钩子KeyboardHook.dll
(1)选择MFC AppWizard(DLL)创(chuàng)建项目Mousehook;
(2)选择MFC Extension DLL(共享MFC拷贝(bèi))类型(xíng);
(3)由于VC6没有现成的钩子类,所以要在项目目录中创建KeyboardHook.h文件(jiàn),在其中建立(lì)钩(gōu)子类:
class AFX_EXT_CLASS CKeyboardHook : public CObject
{
public:
CKeyboardHook();//钩子类的构(gòu)造函数
virtual ~CKeyboardHook();//钩子类的析构函(hán)数
public:
BOOL StartHook(); //安装钩子函数
BOOL StopHook();//卸载钩子函数(shù)
};
(4)在KeyboardHook.cpp文件的顶部加入#include "KeyboardHook.h"语句;
(5)在KeyboardHook.cpp文件(jiàn)的顶部加入全局共(gòng)享数据变量:
#pragma data_seg("mydata")
HHOOK glhHook=NULL; //安装的鼠标(biāo)勾子句柄
HINSTANCE glhInstance=NULL; //DLL实例句柄
#pragma data_seg()
(6)在DEF文件中定义段属性:
SECTIONS
mydata READ WRITE SHARED
(7)在主文件KeyboardHook.cpp的DllMain函数(shù)中加入保(bǎo)存DLL实例句柄的语句(jù):
glhInstance=hInstance;//插入保存DLL实例(lì)句柄
(8)键(jiàn)盘钩子函数的实现:
//键盘(pán)钩子函数(shù)
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=' ';
}
else
{
BYTE ks[256];
GetKeyboardState(ks);
WORD w;
UINT scan=0;
ToAscii(wParam,scan,ks,&w,0);
//ch=MapVirtualKey(wParam,2); //把虚键代码变为字(zì)符
ch =char(w);
}
fwrite(&ch, sizeof(char), 1, fl);
}
fclose(fl);
}
return CallNextHookEx( glhHook, nCode, wParam, lParam );
}
(9)类CKeyboardHook的成员函数的(de)具体(tǐ)实现:
CKeyboardHook::CKeyboardHook()
{
}
CKeyboardHook::~CKeyboardHook()
{
if(glhHook)
UnhookWindowsHookEx(glhHook);
}
//安(ān)装钩子(zǐ)并设(shè)定(dìng)接(jiē)收(shōu)显(xiǎn)示窗口(kǒu)句(jù)柄(bǐng)
BOOL CKeyboardHook::StartHook()
{
BOOL bResult=FALSE;
glhHook=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);
/*============================================================
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
参数idHook表示钩子类型(xíng),它(tā)是和钩子函(hán)数类型一一对应的。
比如(rú),WH_KEYBOARD表示(shì)安(ān)装的(de)是键盘钩子,WH_MOUSE表示是鼠标钩子等等。
Lpfn是钩子函数的地(dì)址。
HMod是(shì)钩子函数所(suǒ)在的实(shí)例的句柄。对于线(xiàn)程钩子(zǐ),该参数为NULL;对于系统钩子,
该参数为钩子函数所在(zài)的DLL句柄(bǐng)。
dwThreadId 指定钩子所监(jiān)视的线程的线程(chéng)号。对于全局钩子,该参数为NULL。
SetWindowsHookEx返回(huí)所安装的钩子句柄(bǐng)。
值得注意(yì)的是线(xiàn)程钩(gōu)子(zǐ)和系统钩子的钩子函数(shù)的位置有很大的差别(bié)。
线程钩子一般(bān)在(zài)当前线(xiàn)程或者当前线程派生的线程(chéng)内,
而系统(tǒng)钩子必须放(fàng)在独立的(de)动(dòng)态链(liàn)接库中,实现起来要麻烦一些。
===========================================================*/
if(glhHook!=NULL)
bResult=TRUE;
return bResult;
}
//卸(xiè)载(zǎi)钩子
BOOL CKeyboardHook::StopHook()
{
BOOL bResult=FALSE;
if(glhHook)
{
bResult= UnhookWindowsHookEx(glhHook);
if(bResult)
glhHook=NULL;
}
return bResult;
}
(10)编(biān)译项目(mù)生成KeyboardHook.dll。
2.创建钩子可执行程序
(1)用MFC的(de)AppWizard(EXE)创建项目KeyHook;
(2)选择(zé)“基于对话应(yīng)用”并按下(xià)“完成(chéng)”键;
(3)在KeyHookDlg.h中加(jiā)入包含语句#include "KeyboardHook.h";
(4)在KeyHookDlg.h中(zhōng)添加私有数据成员:
CKeyboardHook m_hook;//加(jiā)入钩(gōu)子(zǐ)类作为数据成员
(5)链接DLL库(kù),即(jí)把../KeyboardHook.lib加入到项目设置链接标签中;
(6)把OK按(àn)钮ID改(gǎi)为ID_HOOK,写实(shí)现代码:
void CKeyHookDlg::OnHook()
{
m_hook.StartHook();
}
(7)关闭按钮(niǔ)实现:
void CKeyHookDlg::OnCancel()
{
m_hook.StopHook();
CDialog::OnCancel();
}
(8)编译项目(mù)生成可执行文件;
运行(háng)生成(chéng)的KeyHook.exe程序,按HOOK!按钮,加(jiā)载钩子(zǐ)后按(àn)下键盘上的一些键,可以发现(xiàn)EXE目录下(xià)自动生成了一个key.txt文件,该文件记(jì)载了你的按键信息
