屏幕抓词(或者叫动态翻译)是指随着鼠标的移动,软件能够随时获知屏幕上鼠标位置的单词或汉字,并翻译出来提示用户。它对於上网浏览、在线阅读外文文章等很有帮助作用,因此许多词典软件都提供了屏幕抓词功能。
屏幕抓词的关键是如何获得鼠标位置的字符串,Windows的动态链接和消息响应机制为之提供了实现途径。 概括地说,主要通过下面的几个步骤来取得屏幕上鼠标位置的字符串:
符串:
. 代码拦截:Windows以DLL方式提供系统服务,可以方便地获取Windows字符输出API的地址,修改其入口代码,拦截应用程序对它们的调用。
. 鼠标HOOK:安装WH-MOUSEPROC类型的全局鼠标HOOK过程,监视鼠标在整个屏幕上的移动。
. 屏幕刷新:使鼠标周围一块区域无效,并强制鼠标位置的窗口刷新屏幕输出。窗口过程响应WM-NCPAINT和WM-PAINT消息,调用 ExtTextOut/TextOut等字符输出API更新无效区域里面的字符串。这些调用被我们截获,从堆栈里取得窗口过程传给字符API的参数,如字 符串地址、长度、输出坐标、HDC、裁剪区等信息。
2 Windows 95/98的字符输出方法
Windows95/98不是一个纯32位的操作系统,它从16位操作系统发展而来,为了保持兼容,其内部仍然是32位和16位代码的混合体。系统通 过gdi.exe 、user.exe和krnl386.exe提供16位API,供16位程序调用;通过gdi32.dll、user32.dll和 kernel32.dll提归结如下:
图1 Windows 95/98的字符输出机制
. 16位程序通过16位gdi.exe的ExtTextOut/TextOut函数输出字符;
. 32位程序通过gdi32.dll的ExtTextOutA/TextOutA输出ANSI格式的字符,通过ExtTextOutW/TextOutW输出UNICODE格式的字符;
. 32位的ExtTextOutA/TextOutA转换到16位的ExtTextOut/TextOut,完成ANSI格式字符的输出;
. 32位的ExtTextOutW/TextOutW转换到16位gdi.exe的两个未公开API,完成UNICODE格式字符的输出。为方便叙述,本文对这两个未公开API称为ExtTextOut16W和TextOut16W。
t16W。
因此,只要拦截四个16位函数:TextOut、ExtTextOut、TextOut16W和ExtTextOut16W,就能截获32位和16位应用程序的所有字符串输出。
3 拦截字符输出API
3.1 代码拦截的基本思路
为了实现对ExtTextOut/TextOut等API的拦截,需要在函数入口放入一条动态生成的"JMP<替代函数>"指令,JMP 的操作数是我们提供的一个拦截替代函数的地址。当该API被调用时,JMP指令首先执行,跳转到替代函数。替代函数负责从堆栈中获取参数,计算字符串坐 标,分出鼠标位置的单词等工作。执行完成后,替代函数再调用原来的被拦截函数,完成正常的字符输出,然后返回。 图2表明了应用程序对TextOut的正常调用流程和TextOut被拦截后的流程。
a)
① 程序调用TextOut;
② 从TextOut返回程序。
① 程序调用TextOut(其入口已经被修改);
② 转入拦截替代函数myTextOut;
③ 从myTextOut调用原来的TextOut;
④ 从TextOut返回myTextOut;
⑤ 从myTextOut返回程序。
图2 对TextOut的正常调用流程
和TextOut被拦截后的流程
3.2 提供拦截替代函数
拦截替代函数插入被拦截API的流程中执行,需要有相同的参数和返回值原型。以对 拦截替代函数插入被拦截API的流程中执行,需要有相同的参数和返回值原型。以对TextOut的拦截为例,下面是替代函数myTextOut的伪代码:
BOOL myTextOut(HDC hdc,int x,int y,LPSTR lpstr,int cbstr)
{
DoSpy(hDC,x ,y,lpstr,cbstr);
//保存参数,作抓词的所有工作
RestoreCode();//恢复TextOut入口原来的指令
TextOut(hDC,x,y,lpstr,cbstr);//调用原来的API
SpyCode();//再次放入JMP指令
return TRUE;
}
函数首先调用DoSpy()来作抓词的具体工作,然后RestoreCode()函数恢复被拦截函
数入口的代码,再调用TextOut()执行正常的字符输出,接下来SpyCode()在被拦截函数
入口再次放入JMP指令,最后返回调用进程。
3.3 获取被拦截API的动态链接地址
TextOut和ExtTextOut的地址可以通过GetProcAddress取得,而TextOut16W和ExtTextOut16W既未公 开于文档,也没有用字符串或序号从gdi.exe中引出,无法使用GetProcAddress取得它们的地址。下面讨论如何解决这个问题。
第一种方法:用softICE可以跟踪发现,ExtTextOut16W位于公开函数GetTextMetrics 入口的偏移20H处,TextOut16W位于GetTextMetrics的偏移7CH处。所以可以先取得GetTextMetrics的地址,再分别 在加上20H和7CH,就是ExtTextOutW和TextOutW的地址。
第二种方法:从指令里面析出地址。
第二种方法:从指令里面析出地址。
Windows 95/98维持一个DWORD类型的数组,称为32位-16位替换表,表中每个元素存
放了一个Win32 API对应的16位API的段:偏移地址。需要转入16位运行的Win32API使用
不同的索引来从该表格里面取目的地址。ExtTextOutW和TextOutW分别使用索引B2H和30
H,取表中相应元素作为ExtTextOut16W和TextOut16W的地址。取得这个数组的起始位置
,根据索引号即可找到16位函数的地址。由于这个数组是一个重定位项,在内存的地址
不固定,所以需要从指令流里面动态析出它的地址。
3.4 动态生成JMP指令
动态生成的JMP指令占用5个字节,保存在一个数组里面。以生成跳转到TextOut的替
代函数myTextOut的JMP指令为例:
BYTE ins[5]; //保存JMP指令
ins[0] = 0xea; //操作码:1 byte
*(WORD *)(ins+1) = FP-OFF(myTextOut);
//操作数:2byte的偏移
*(WORD *)(ins+3) = FP-SEG(myTextOut);
//操作数:2byte的段
3.5 修改被拦截函数的入口
Windows运行在保护模式下,16位进程采用了段保护机制,代码段描述符的属性被标记为只读。如果直接向被拦截函数的入口写入JMP指令,会引起CPU保护错误,因此在修改代码前要采用下面的方法绕过段保护机制:
. 获取GDI代码段的基地址和界限;
. 用当前DS复制一个新的段选择子,该选择子的描述符具有可读写的属性;
. 将复制得到的选择子的基地址和界限设置为GDI代码段的基地址和界限;
. 将复制得到的选择子的基地址和界限设置为GDI代码段的基地址和界限;
. 用新的选择子作为段,同被拦截API的偏移组合成一个新的地址。
这样就获得了一个指向被拦截代码的可写指针。先保存5字节内容,再将数组ins[
]复制到该地址,即完成对TextOut等API的拦截修改。
4 使用HOOK监视鼠标移动
安装一个WH-MOUSEPROC类型的全局HOOK可以监视鼠标在全屏幕的移动。每当有鼠标事件产生,HOOK过程被调用,它判断出鼠标移动了后,就向主窗口发送消息,通知主窗口鼠标位置发生了变化。
主窗口收到消息后,设置定时器,监视鼠标在某一位置停留时间的长短。如果鼠标停留超过设定的时间,才启动抓词功能,否则抓词功能保持禁止状态。这样可以减少代码拦截对系统性能的影响。
全局HOOK过程需要放入DLL里面。
5 刷新屏幕输出
预先创建一个小的工具窗口,当鼠标停留在某个位置超过设定时间后,使工具窗口在鼠标位置上显示一下,然后隐含掉,这样就会在目标窗口--鼠标位置的窗 口产生无效区域。然后调用UpdateWindow强制目标窗口刷新屏幕输出。在响应WM-PAINT /WM-N
CPAINT中,目标窗口对字符输出API的调用将被我们截获和处理,完成一次抓词过程。
6 其它
本文所介绍的Windows 95/98环境下屏幕抓词的原理和取得鼠标位置字符串的实现方法。对于进一步的探索,笔者提出下面的几点看法:
. 只有通过响应WM-PAINT /WM-NCPAINT消息输出的字符串才会被捕捉到;
. 有些软件(例如Internet Explorer)在刷新屏幕时,为了消除闪烁,不直接把字符串输出到屏幕DC,而是创建一个兼容的内存DC,先将字符串写到内存DC,再复制到效区域。 然后调用UpdateWindow强制目标窗口刷新屏幕输出。在响应WM-PAINT /WM-N屏幕上去。由于内存DC坐标和屏幕DC坐标的不同,不能直接依赖内存DC的坐标来计算字符串的屏幕位置;
. 本文讨论的方法不适用于Windows NT下的抓词。Windows NT的系统功能完全由32位代码提供,NT下的抓词需要拦截Win32 API。其实现机制同在Windows 95/98下也不一样。在NT里,KERNEL、USER、GDI模块位於进程的私有地址空间内,所以需要使用HOOK来将拦截替代函数动态映射到不同的 被拦截进程的地址空间里面。而且由于控制台窗口禁止大多数HOOK,还需要创建远程线程来获取控制台窗口的字符输出。
=======================================
屏幕上的文字大都是由gdi32.dll的以下几个函数显示的:TextOutA、TextOutW、ExtTextOutA、ExtTextOutW。实现屏幕抓词的关键就是截获对这些函数的调用,得到程序发给它们的参数。
我的方法有以下三个步骤:
一、得到鼠标的当前位置
通过SetWindowsHookEx实现。
二、向鼠标下的窗口发重画消息,让它调用系统函数重画
通过WindowFromPoint,ScreenToClient,InvalidateRect 实现。
三、截获对系统函数的调用,取得参数(以TextOutA为例)
1.仿照TextOutA作成自己的函数MyTextOutA,与TextOutA有相同参数和返回值,放在系统钩子所在的DLL里。
SysFunc1=(DWORD)GetProcAddress(GetModuleHandle("gdi32.dll"),"TextOutA");
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)
{ //输出lpszString的处理
return ((FARPROC)SysFunc1)(hdc,nXStart,nYStart,lpszString,cbString);}
2.由于系统鼠标钩子已经完成注入其它GUI进程的工作,我们不需要为注入再做工作。
如果你知道所有系统钩子的函数必须要在动态库里,就不会对"注入"感到奇怪。当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到 这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈(见图1)。
图1 DLL映射到虚拟地址空间中
对系统钩子来说,系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。
3.当包含钩子的DLL注入其它进程后,寻找映射到这个进程虚拟内存里的各个模块(EXE和DLL)的基地址。EXE和DLL被映射到虚拟内存空间的 什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win32工程时,VC++链接器使用缺省的基地址 0x00400000。可以通过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x00400000处,DLL也随之有不同的基地 址,通常被映射到不同进程的相同的虚拟地址空间处。
如何知道EXE和DLL被映射到哪里了呢?
在Win32中,HMODULE和HINSTANCE是相同的。它们就是相应模块被装入进程的虚拟内存空间的基地址。比如:
HMODULE hmodule=GetModuleHandle(〃gdi32.dll〃);
返回的模块句柄强制转换为指针后,就是gdi32.dll被装入的基地址。
关于如何找到虚拟内存空间映射了哪些DLL?我用如下方式实现:
while(VirtualQuery (base, &mbi, sizeof (mbi))〉0)
{ if(mbi.Type==MEM-IMAGE)
ChangeFuncEntry((DWORD)mbi.BaseAddress,1);
base=(DWORD)mbi.BaseAddress+mbi.RegionSize; }
4.得到模块的基地址后,根据PE文件的格式穷举这个模块的IMAGE-IMPORT-DESCRIPTOR数组,看是否引入了gdi32.dll。如是,则穷举IMAGE-THUNK-DATA数组,看是否引入了TextOutA函数。
5.如果找到,将其替换为相应的自己的函数。
系统将EXE和DLL原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样的。即PE (Portable Executable) 文件格式。
所有对给定API函数的调用总是通过可执行文件的同一个地方转移。那就是一个模块(可以是EXE或DLL)的输入地址表(import address table)。那里有所有本模块调用的其它DLL的函数名及地址。对其它DLL的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到DLL真正的 函数入口。例如:
图2 对MessageBox()的调用跳转到输入地址表,从输入地址表再跳转到MessageBox函数
IMAGE-IMPORT-DESCRIPTOR和IMAGE-THUNK-DATA分别对应于DLL和函数。它们是PE文件的输入地址表的格式(数据结构参见winnt.h)。
BOOL ChangeFuncEntry(HMODULE hmodule)
{ PIMAGE-DOS-HEADER pDOSHeader;
PIMAGE-NT-HEADERS pNTHeader;
PIMAGE-IMPORT-DESCRIPTOR pImportDesc;
/ get system functions and my functions′entry /
pSysFunc1=(DWORD)GetProcAddress(GetModuleHandle(〃gdi32.dll〃),〃TextOutA〃);
pMyFunc1= (DWORD)GetProcAddress(GetModuleHandle(〃hookdll.dll〃),〃MyTextOutA〃);
pDOSHeader=(PIMAGE-DOS-HEADER)hmodule;
if (IsBadReadPtr(hmodule, sizeof(PIMAGE-NT-HEADERS)))
return FALSE;
if (pDOSHeader-〉e-magic != IMAGE-DOS-SIGNATURE)
return FALSE;
pNTHeader=(PIMAGE-NT-HEADERS)((DWORD)pDOSHeader+(DWORD)pDOSHeader-〉e-lfanew);
if (pNTHeader-〉Signature != IMAGE-NT-SIGNATURE)
return FALSE;
pImportDesc = (PIMAGE-IMPORT-DESCRIPTOR)((DWORD)hmodule+(DWORD)pNTHeader-〉OptionalHeader.DataDirectory
[IMAGE-DIRECTORY-ENTRY-IMPORT].VirtualAddress);
if (pImportDesc == (PIMAGE-IMPORT-DESCRIPTOR)pNTHeader)
return FALSE;
while (pImportDesc-〉Name)
{ PIMAGE-THUNK-DATA pThunk;
strcpy(buffer,(char )((DWORD)hmodule+(DWORD)pImportDesc-〉Name));
CharLower(buffer);
if(strcmp(buffer,"gdi32.dll"))
{ pImportDesc++;
continue;
}else
{ pThunk=(PIMAGE-THUNK-DATA)((DWORD)hmodule+(DWORD)pImportDesc-〉FirstThunk);
while (pThunk-〉u1.Function)
{ if ((pThunk-〉u1.Function) == pSysFunc1)
{ VirtualProtect((LPVOID)(&pThunk-〉u1.Function),
sizeof(DWORD),PAGE-EXECUTE-READWRITE,&dwProtect);
(pThunk-〉u1.Function)=pMyFunc1;
VirtualProtect((LPVOID)(&pThunk-〉u1.Function), sizeof(DWORD),dwProtect,&temp); }
pThunk++; } return 1;}}}
替换了输入地址表中TextOutA的入口为MyTextOutA后,截获系统函数调用的主要部分已经完成,当一个被注入进程调用TextOutA 时,其实调用的是MyTextOutA,只需在MyTextOutA中显示传进来的字符串,再交给TextOutA处理即可。
========================================
金山词霸抓词机理
-- HOOK
消息功能的使用
Win32
全局钩子在
VC5
中的实现
Windows
系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是
Windows
系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。本文在
VC5
编程环境下实现了一个简单的鼠标钩子程序,并对
Win32
全局钩子的运行机制、
Win32 DLL
的特点、
VC5
环境下的
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
环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。
三.
VC5
中
MFC DLL
的分类及特点
在
VC5
中有三种形式的
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
程序使用。
四.在
VC5
中全局共享数据的实现
在主文件中,用
#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);
font-size: 9pt; line-h
分享到:
评论