A. 屏幕取词
“鼠标屏幕取词”技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在WINDOWS系统中实现却是非常复杂的,总的来说有两种实现方式:
第一种:采用截获对部分GDI的API调用来实现,如TextOut,TextOutA等。
第二种:对每个设备上下文(DC)做一分Copy,并跟踪所有修改上下文(DC)的操作。
第二种方法更强大,但兼容性不好,而第一种方法使用的截获WindowsAPI的调用,这项技术的强大可能远远超出了您的想象,毫不夸张的说,利用WindowsAPI拦截技术,你可以改造整个操作系统,事实上很多外挂式Windows中文平台就是这么实现的!而这项技术也正是这篇文章的主题。
截WindowsAPI的调用,具体的说来也可以分为两种方法:
第一种方法通过直接改写WinAPI 在内存中的映像,嵌入汇编代码,使之被调用时跳转到指定的地址运行来截获;第二种方法则改写IAT(Import Address Table 输入地址表),重定向WinAPI函数的调用来实现对WinAPI的截获。
第一种方法的实现较为繁琐,而且在Win95、98下面更有难度,这是因为虽然微软说WIN16的API只是为了兼容性才保留下来,程序员应该尽可能地调用32位的API,实际上根本就不是这样!WIN 9X内部的大部分32位API经过变换调用了同名的16位API,也就是说我们需要在拦截的函数中嵌入16位汇编代码!
我们将要介绍的是第二种拦截方法,这种方法在Win95、98和NT下面运行都比较稳定,兼容性较好。由于需要用到关于Windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、PE(Portable Executable)文件格式和IAT(输入地址表)等较底层的知识,所以我们先对涉及到的这些知识大概地做一个介绍,最后会给出拦截部分的关键代码。
先说Windows虚拟内存的管理。Windows9X给每一个进程分配了4GB的地址空间,对于NT来说,这个数字是2GB,系统保留了2GB到 4GB之间的地址空间禁止进程访问,而在Win9X中,2GB到4GB这部分虚拟地址空间实际上是由所有的WIN32进程所共享的,这部分地址空间加载了共享Win32 DLL、内存映射文件和VXD、内存管理器和文件系统码,Win9X中这部分对于每一个进程都是可见的,这也是Win9X操作系统不够健壮的原因。
Win9X中为16位操作系统保留了0到4MB的地址空间,而在4MB到2GB之间也就是Win32进程私有的地址空间,由于 每个进程的地址空间都是相对独立的,也就是说,如果程序想截获其它进程中的API调用,就必须打破进程边界墙,向其它的进程中注入截获API调用的代码,这项工作我们交给钩子函数(SetWindowsHookEx)来完成,关于如何创建一个包含系统钩子的动态链接库,《电脑高手杂志》已经有过专题介绍了,这里就不赘述了。
所有系统钩子的函数必须要在动态库里,这样的话,当进程隐式或显式调用一个动态库里的函数时,系统会把这个动态库映射到这个进程的虚拟地址空间里,这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈,也就是说动态链接库中的代码被钩子函数注入了其它GUI进程的地址空间(非GUI进程,钩子函数就无能为力了),当包含钩子的DLL注入其它进程后,就可以取得映射到这个进程虚拟内存里的各个模块(EXE和DLL)的基地址,如:
HMODULE hmole=GetMoleHandle(“Mypro.exe”);
在MFC程序中,我们可以用AfxGetInstanceHandle()函数来得到模块的基地址。EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win32工程时,VC++链接器使用缺省的基地址0x00400000。可以通过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x00400000处,DLL也随之有不同的基地址,通常被映射到不同进程的相同的虚拟地址空间处。
系统将EXE和DLL原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样的。即PE (Portable Executable) 文件格式。我们得到了进程模块的基地址以后,就可以根据PE文件的格式穷举这个模块的IMAGE_IMPORT_DESCRIPTOR数组,看看进程空间中是否引入了我们需要截获的函数所在的动态链接库,比如需要截获“TextOutA”,就必须检查“Gdi32.dll”是否被引入了。
说到这里,我们有必要介绍一下PE文件的格式,如右图,这是PE文件格式的大致框图,最前面是文件头,我们不必理会,从PE File Optional Header后面开始,就是文件中各个段的说明,说明后面才是真正的段数据,而实际上我们关心的只有一个段,那就是“.idata”段,这个段中包含了所有的引入函数信息,还有IAT(Import Address Table)的RVA(Relative Virtual Address)地址。
说到这里,截获WindowsAPI的整个原理就要真相大白了。实际上所有进程对给定的API函数的调用总是通过PE文件的一个地方来转移的,这就是一个该模块(可以是EXE或DLL)的“.idata”段中的IAT输入地址表(Import Address Table)。在那里有所有本模块调用的其它DLL的函数名及地址。对其它DLL的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到DLL真正的函数入口。
具体来说,我们将通过IMAGE_IMPORT_DESCRIPTOR数组来访问“.idata”段中引入的DLL的信息,然后通过IMAGE_THUNK_DATA数组来针对一个被引入的DLL访问该DLL中被引入的每个函数的信息,找到我们需要截获的函数的跳转地址,然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。
讲了这么多原理,现在让我们回到“鼠标屏幕取词”的专题上来。除了API函数的截获,要实现“鼠标屏幕取词”,还需要做一些其它的工作,简单的说来,可以把一个完整的取词过程归纳成以下几个步骤:
1. 安装鼠标钩子,通过钩子函数获得鼠标消息。
使用到的API函数:SetWindowsHookEx
2. 得到鼠标的当前位置,向鼠标下的窗口发重画消息,让它调用系统函数重画窗口。
使用到的API函数:WindowFromPoint,ScreenToClient,InvalidateRect
3. 截获对系统函数的调用,取得参数,也就是我们要取的词。
对于大多数的Windows应用程序来说,如果要取词,我们需要截获的是“Gdi32.dll”中的“TextOutA”函数。
我们先仿照TextOutA函数写一个自己的MyTextOutA函数,如:
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)
{
// 这里进行输出lpszString的处理
// 然后调用正版的TextOutA函数
}
把这个函数放在安装了钩子的动态连接库中,然后调用我们最后给出的HookImportFunction函数来截获进程对TextOutA函数的调用,跳转到我们的MyTextOutA函数,完成对输出字符串的捕捉。
HookImportFunction的用法:
HOOKFUNCDESC hd;
PROC pOrigFuns;
hd.szFunc="TextOutA";
hd.pProc=(PROC)MyTextOutA;
HookImportFunction (AfxGetInstanceHandle(),"gdi32.dll",&hd,pOrigFuns);
下面给出了HookImportFunction的源代码,相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的很难,Ok,Let’s Go:
///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////
#include <crtdbg.h>
// 这里定义了一个产生指针的宏
#define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))
// 定义了HOOKFUNCDESC结构,我们用这个结构作为参数传给HookImportFunction函数
typedef struct tag_HOOKFUNCDESC
{
LPCSTR szFunc; // The name of the function to hook.
PROC pProc; // The procere to blast in.
} HOOKFUNCDESC , * LPHOOKFUNCDESC;
// 这个函数监测当前系统是否是WindowNT
BOOL IsNT();
// 这个函数得到hMole -- 即我们需要截获的函数所在的DLL模块的引入描述符(import descriptor)
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hMole, LPCSTR szImportMole);
// 我们的主函数
BOOL HookImportFunction(HMODULE hMole, LPCSTR szImportMole,
LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)
{
/////////////////////// 下面的代码检测参数的有效性 ////////////////////////////
_ASSERT(szImportMole);
_ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));
#ifdef _DEBUG
if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));
_ASSERT(paHookFunc.szFunc);
_ASSERT(*paHookFunc.szFunc != '\0');
_ASSERT(!IsBadCodePtr(paHookFunc.pProc));
#endif
if ((szImportMole == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))
{
_ASSERT(FALSE);
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return FALSE;
}
//////////////////////////////////////////////////////////////////////////////
// 监测当前模块是否是在2GB虚拟内存空间之上
// 这部分的地址内存是属于Win32进程共享的
if (!IsNT() && ((DWORD)hMole >= 0x80000000))
{
_ASSERT(FALSE);
SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);
return FALSE;
}
// 清零
if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC));
// 调用GetNamedImportDescriptor()函数,来得到hMole -- 即我们需要
// 截获的函数所在的DLL模块的引入描述符(import descriptor)
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hMole, szImportMole);
if (pImportDesc == NULL)
return FALSE; // 若为空,则模块未被当前进程所引入
// 从DLL模块中得到原始的THUNK信息,因为pImportDesc->FirstThunk数组中的原始信息已经
// 在应用程序引入该DLL时覆盖上了所有的引入信息,所以我们需要通过取得pImportDesc->OriginalFirstThunk
// 指针来访问引入函数名等信息
PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hMole,
pImportDesc->OriginalFirstThunk);
// 从pImportDesc->FirstThunk得到IMAGE_THUNK_DATA数组的指针,由于这里在DLL被引入时已经填充了
// 所有的引入信息,所以真正的截获实际上正是在这里进行的
PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hMole, pImportDesc->FirstThunk);
// 穷举IMAGE_THUNK_DATA数组,寻找我们需要截获的函数,这是最关键的部分!
while (pOrigThunk->u1.Function)
{
// 只寻找那些按函数名而不是序号引入的函数
if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))
{
// 得到引入函数的函数名
PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hMole,
pOrigThunk->u1.AddressOfData);
// 如果函数名以NULL开始,跳过,继续下一个函数
if ('\0' == pByName->Name[0])
continue;
// bDoHook用来检查是否截获成功
BOOL bDoHook = FALSE;
// 检查是否当前函数是我们需要截获的函数
if ((paHookFunc.szFunc[0] == pByName->Name[0]) &&
(strcmpi(paHookFunc.szFunc, (char*)pByName->Name) == 0))
{
// 找到了!
if (paHookFunc.pProc)
bDoHook = TRUE;
}
if (bDoHook)
{
// 我们已经找到了所要截获的函数,那么就开始动手吧
// 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取
MEMORY_BASIC_INFORMATION mbi_thunk;
VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
_ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
PAGE_READWRITE, &mbi_thunk.Protect));
// 保存我们所要截获的函数的正确跳转地址
if (paOrigFuncs)
paOrigFuncs = (PROC)pRealThunk->u1.Function;
// 将IMAGE_THUNK_DATA数组中的函数跳转地址改写为我们自己的函数地址!
// 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用
pRealThunk->u1.Function = (PDWORD)paHookFunc.pProc;
// 操作完毕!将这一块虚拟内存改回原来的保护状态
DWORD dwOldProtect;
_ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
mbi_thunk.Protect, &dwOldProtect));
SetLastError(ERROR_SUCCESS);
return TRUE;
}
}
// 访问IMAGE_THUNK_DATA数组中的下一个元素
pOrigThunk++;
pRealThunk++;
}
return TRUE;
}
// GetNamedImportDescriptor函数的实现
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hMole, LPCSTR szImportMole)
{
// 检测参数
_ASSERT(szImportMole);
_ASSERT(hMole);
if ((szImportMole == NULL) || (hMole == NULL))
{
_ASSERT(FALSE);
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return NULL;
}
// 得到Dos文件头
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hMole;
// 检测是否MZ文件头
if (IsBadReadPtr(pDOSHeader, sizeof(IMAGE_DOS_HEADER)) ||
(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE))
{
_ASSERT(FALSE);
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return NULL;
}
// 取得PE文件头
PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);
// 检测是否PE映像文件
if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||
(pNTHeader->Signature != IMAGE_NT_SIGNATURE))
{
_ASSERT(FALSE);
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return NULL;
}
// 检查PE文件的引入段(即 .idata section)
if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
return NULL;
// 得到引入段(即 .idata section)的指针
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
// 穷举PIMAGE_IMPORT_DESCRIPTOR数组寻找我们需要截获的函数所在的模块
while (pImportDesc->Name)
{
PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc->Name);
if (stricmp(szCurrMod, szImportMole) == 0)
break; // 找到!中断循环
// 下一个元素
pImportDesc++;
}
// 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!
if (pImportDesc->Name == NULL)
return NULL;
// 返回函数所找到的模块描述符(import descriptor)
return pImportDesc;
}
// IsNT()函数的实现
BOOL IsNT()
{
OSVERSIONINFO stOSVI;
memset(&stOSVI, NULL, sizeof(OSVERSIONINFO));
stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
BOOL bRet = GetVersionEx(&stOSVI);
_ASSERT(TRUE == bRet);
if (FALSE == bRet) return FALSE;
return (VER_PLATFORM_WIN32_NT == stOSVI.dwPlatformId);
}
/////////////////////////////////////////////// End //////////////////////////////////////////////////////////////////////
不知道在之前,有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术,也只有尝试过的朋友才能体会到其间的不易,尤其在探索API函数的截获时,手头的几篇资料没有一篇是涉及到关键代码的,重要的地方都是一笔代过,MSDN更是显得苍白而无力,也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA,微软还隐藏了多少秘密,好在硬着头皮还是把它给攻克了,希望这篇文章对大家能有所帮助。
B. 我的电脑之前还可以屏幕取词(有道),后来突然就无法取词了,请问该怎么办
你去软件那儿设置一下!!!
C. 电脑版有道词典如何取词有道词典取词常见问题分析
第一页:电脑版有道词典如何取词?有道词典取词常见问题分析
第二页:电脑版有道词典如何取词?有道词典取词常见问题分析2
第三页:电脑版有道词典如何取词?有道词典取词常见问题分析3有道词典是一款很小很强大的翻译软件,它除了具备中英、英中、英英翻译功能外,创新的“网络释义”功能将各类新兴词汇和英文缩写收录其中,并且拥有专业权威的大词典。在生活中它是贴心实用的词典,能强力智能取词,帮助用户进行翻译。目前已经有超过8000万用户使用,所以小编就来与用户们分享一下有道词典怎么屏幕取词、有道词典怎么图片取词,还有一点可能用户不太清楚,有道词典也可以在谷歌浏览器(Chrome)中取词,今天小编就一一的介绍一下,希望能帮助到大家。
有道词典如何屏幕取词?
1、打开“有道词典”,如图1所示:
(图1)
2、点击有道词典面板的“左下角”,如图2所示:
(图2)
3、先点击“设置”,接着点击“软件设置”按钮,如图3所示:
(图3)
4、在“软件设置”的对话框中,选择“取词划词”按钮,如图4所示:
(图4)
5、勾选“启用屏幕取词”对话框,在下面选择你要的取词方式,一般选择鼠标取词(也可以选择其它取词,但每个人的习惯不一样,因人而异),如图5所示:
(图5)
6、最后保存设置,返回界面,用鼠标选择你要翻译的文字,如图6所示:(小编在这里以网络的新闻两个字为例子)
(图6)
7、选择之后,鼠标在选择的内容上停留一下,就会弹出翻译,如图7所示:
(图7)
更多有道词典教程,尽在词典软件
http://www.gezila.com/special/cdcd
D. 有道词典怎么关闭屏幕取词 屏幕取词关闭方法
屏幕取词是有道词典桌面版的一大特色,但是有时候又不想进行屏幕取词。那么,有道词典怎么关闭屏幕取词呢?下面,小编就为大家带来屏幕取词关闭方法,一起来了解一下吧!
点击查看==>有道词典常见问题汇总
方法一、
1、打开有道词典桌面端,点击左下角的有道图标;
2、在弹出的功能菜单中选择“设置”,然后点击“软件设置”;
3、在出现的“软件设置”界面中,选择“内容设置”;
4、在“内容设置”界面中,将“启动屏幕取词”全面的勾去掉结果即可。
方法二、
1、鼠标右击电脑右下角消息通知栏中的“有道词典”;
2、在弹出的功能列表中,如果“屏幕取词”全面的图标是灰色状态,说明屏幕取词功能是关闭的。
E. 能屏幕取词的词典哪个最好
灵格斯软件,集成多种软件特点,速度快效率高
F. 有道词典中的屏幕取词与划词有什么区别
有道词典中的取词和划词的区别在于取词是自动翻译,划词是手动选词翻译。
取词的翻译是鼠标即指即译,有道会自动设别单词并翻译;即鼠标放在那里,就会翻译哪里。
划词的翻译是自己选择要翻译的单词,有道词典再翻译。划词需要手动操作选择需要翻译的词。有时候会遇到有道无法自动取词,这是就可以选择试试划词的方式,手动选取词汇进行翻译。
有道词典的特色功能:
多国语言翻译发音
有道词典集成中、英、日、韩、法多语种专业词典,切换语言环境,即可快速翻译所需内容,网页版有道翻译还支持中、英、日、韩、法、西、俄七种语言的在线翻译。
有道搜索引擎在抓取并获得多达数十亿的海量网页数据后,利用有道独创的“网页萃取”技术挖掘并评价互为翻译关系的中外文词汇和句子,经过优化调整,得到最佳的翻译结果。英日韩法语的单词及例句都可点击发音,清晰流畅的真人发音使得用户可以轻松学习多国纯正口语。
网页全文翻译
有道词典全新增加网页翻译功能,用户可直接在翻译框内输入网址,点击翻译即可得到翻译后的该网址页面,实现快速准确的中英日韩法五国语言全文翻译,还可自动检测语言环境,轻松翻译长句及文章段落。
专业权威大词典
有道词典完整收录《柯林斯英汉双解大词典》《21世纪大英汉词典》《新汉英大辞典》《现代汉语大词典》等多部专业权威词典,词库大而全,查词快又准,实现汉语成语、生僻字的直观释义,为用户提供准确高效的翻译宝典。
海量例句一键查询
2300万条例句一键查询,还可根据单词释义选择对应例句,帮用户更加准确全面地理解单词,在语境中活学活用。
专业词汇学科标注
标注收纳60万专业词汇,词条涵盖逾200个学科领域,180万网络词条内容丰富[5],提供一站式知识查询服务。
G. 请问我在电脑上用的“有道词典”在屏幕上取词速度慢是怎么回事
有道的屏幕取词的反应速度太慢了,在同样的网络环境下,使用同一台电脑和同样的系统(譬如XP SP3),相比起金山来说,这个有道慢的让人感到难受:在金山界面里,鼠标一点,结果就出来了,而在有道里,鼠标点了几秒钟,才出结果。卸载后重装几次无改善,甚至重装系统后再安装有道,也是一个德行;只好暂时用金山了_但是金山的翻译似乎不如有道的更贴近一点。
H. 求一个能屏幕取词的翻译软件。 首先不能是金山词霸,这个在我办公电脑上无法取词
热土迷你词霸v1.0,
有道桌面词典,
Babylon
I. 为什么我的电脑无法对英文单词屏幕取词
如果是金山词霸的话,屏幕取词功能应该是被关闭了。到界面选择屏幕取词功能开就可以了,或者用键盘操作:ctrl+alt+F1就可以重新开启此功能了。如果用的是词霸豆豆,屏幕取词须待鼠标停在需要取意的词上时按一下ctrl键。如果这些都不行的话,关闭再重新打开软件。
J. 电脑上安装了有道词典总是自动取词的解决方法
方法:
1、原因分析,为什么回事这样呢?
为什么安装了有道词典之后,电脑上会自动取词呢?后来小编分析,原因肯定是以为暗转了有道词典,有道词典相关的取词设置打开了!!只要关闭相关设置就好了!!!那应该怎么办呢?请看2
2、应该怎么办呢?
打开有道词典,在电脑桌面有下角,找到有道词典缩略图,点击鼠标右键,找到“
软件设置”,并打开“软件设置”,如图
3、在“软件设置”里面找到“取词划词”选项卡,如图
4、将“取词划词”选项卡里面的“启用屏幕取词”前面的对号去掉,如图
5、设置完成之后,点击“确定”按钮,保存设置,亲测实用!!希望对有需要的小伙伴有所帮助!!!
以上就是专题栏小编带来的有道词典教程,更多教程请看“http://www.zhuantilan.com/zt/youcidian/”