久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

鼠標(biāo)屏幕取詞技術(shù)的原理和實(shí)現(xiàn) - 編程開發(fā) - Visual C - 新云網(wǎng)絡(luò)

 kakaxi 2007-08-30

鼠標(biāo)屏幕取詞技術(shù)的原理和實(shí)現(xiàn)

作者:佚名  來源:本站整理  發(fā)布時間:2006-3-27 16:48:35

   鼠標(biāo)屏幕取詞技術(shù)的原理和實(shí)現(xiàn)   
                       
白瑜

   “鼠標(biāo)屏幕取詞”技術(shù)是在電子字典中得到廣泛地應(yīng)用的,,如四通利方和金山詞霸等軟件,,這個技術(shù)看似簡單,,其實(shí)在WINDOWS系統(tǒng)中實(shí)現(xiàn)卻是非常復(fù)雜的,總的來說有兩種實(shí)現(xiàn)方式:
    第一種:采用截獲對部分GDI的API調(diào)用來實(shí)現(xiàn),如TextOut,TextOutA等,。
    第二種:對每個設(shè)備上下文(DC)做一分Copy,并跟蹤所有修改上下文(DC)的操作,。     
    第二種方法更強(qiáng)大,但兼容性不好,而第一種方法使用的截獲WindowsAPI的調(diào)用,,這項(xiàng)技術(shù)的強(qiáng)大可能遠(yuǎn)遠(yuǎn)超出了您的想象,,毫不夸張的說,利用WindowsAPI攔截技術(shù),,你可以改造整個操作系統(tǒng),,事實(shí)上很多外掛式Windows中文平臺就是這么實(shí)現(xiàn)的,!而這項(xiàng)技術(shù)也正是這篇文章的主題。
    截WindowsAPI的調(diào)用,,具體的說來也可以分為兩種方法:
    第一種方法通過直接改寫WinAPI 在內(nèi)存中的映像,,嵌入?yún)R編代碼,,使之被調(diào)用時跳轉(zhuǎn)到指定的地址運(yùn)行來截獲,;第二種方法則改寫IAT(Import Address Table 輸入地址表),重定向WinAPI函數(shù)的調(diào)用來實(shí)現(xiàn)對WinAPI的截獲,。
    第一種方法的實(shí)現(xiàn)較為繁瑣,,而且在Win95、98下面更有難度,,這是因?yàn)殡m然微軟說WIN16的API只是為了兼容性才保留下來,,程序員應(yīng)該盡可能地調(diào)用32位的API,實(shí)際上根本就不是這樣!WIN 9X內(nèi)部的大部分32位API經(jīng)過變換調(diào)用了同名的16位API,,也就是說我們需要在攔截的函數(shù)中嵌入16位匯編代碼,!
    我們將要介紹的是第二種攔截方法,這種方法在Win95,、98和NT下面運(yùn)行都比較穩(wěn)定,,兼容性較好。由于需要用到關(guān)于Windows虛擬內(nèi)存的管理,、打破進(jìn)程邊界墻,、向應(yīng)用程序的進(jìn)程空間中注入代碼、PE(Portable Executable)文件格式和IAT(輸入地址表)等較底層的知識,,所以我們先對涉及到的這些知識大概地做一個介紹,,最后會給出攔截部分的關(guān)鍵代碼。
      先說Windows虛擬內(nèi)存的管理,。Windows9X給每一個進(jìn)程分配了4GB的地址空間,,對于NT來說,這個數(shù)字是2GB,,系統(tǒng)保留了2GB到 4GB之間的地址空間禁止進(jìn)程訪問,,而在Win9X中,2GB到4GB這部分虛擬地址空間實(shí)際上是由所有的WIN32進(jìn)程所共享的,,這部分地址空間加載了共享Win32 DLL,、內(nèi)存映射文件和VXD、內(nèi)存管理器和文件系統(tǒng)碼,,Win9X中這部分對于每一個進(jìn)程都是可見的,,這也是Win9X操作系統(tǒng)不夠健壯的原因。Win9X中為16位操作系統(tǒng)保留了0到4MB的地址空間,,而在4MB到2GB之間也就是Win32進(jìn)程私有的地址空間,,由于 每個進(jìn)程的地址空間都是相對獨(dú)立的,也就是說,如果程序想截獲其它進(jìn)程中的API調(diào)用,,就必須打破進(jìn)程邊界墻,,向其它的進(jìn)程中注入截獲API調(diào)用的代碼,這項(xiàng)工作我們交給鉤子函數(shù)(SetWindowsHookEx)來完成,,關(guān)于如何創(chuàng)建一個包含系統(tǒng)鉤子的動態(tài)鏈接庫,,《電腦高手雜志》在第?期已經(jīng)有過專題介紹了,,這里就不贅述了,。所有系統(tǒng)鉤子的函數(shù)必須要在動態(tài)庫里,這樣的話,,當(dāng)進(jìn)程隱式或顯式調(diào)用一個動態(tài)庫里的函數(shù)時,,系統(tǒng)會把這個動態(tài)庫映射到這個進(jìn)程的虛擬地址空間里,這使得DLL成為進(jìn)程的一部分,,以這個進(jìn)程的身份執(zhí)行,,使用這個進(jìn)程的堆棧,也就是說動態(tài)鏈接庫中的代碼被鉤子函數(shù)注入了其它GUI進(jìn)程的地址空間(非GUI進(jìn)程,,鉤子函數(shù)就無能為力了),,
當(dāng)包含鉤子的DLL注入其它進(jìn)程后,就可以取得映射到這個進(jìn)程虛擬內(nèi)存里的各個模塊(EXE和DLL)的基地址,,如:
HMODULE hmodule=GetModuleHandle(“Mypro.exe”);
在MFC程序中,我們可以用AfxGetInstanceHandle()函數(shù)來得到模塊的基地址,。EXE和DLL被映射到虛擬內(nèi)存空間的什么地方是由它們的基地址決定的。它們的基地址是在鏈接時由鏈接器決定的,。當(dāng)你新建一個Win32工程時,,VC++鏈接器使用缺省的基地址0x00400000??梢酝ㄟ^鏈接器的BASE選項(xiàng)改變模塊的基地址,。EXE通常被映射到虛擬內(nèi)存的0x00400000處,DLL也隨之有不同的基地址,,通常被映射到不同進(jìn)程
的相同的虛擬地址空間處,。
系統(tǒng)將EXE和DLL原封不動映射到虛擬內(nèi)存空間中,它們在內(nèi)存中的結(jié)構(gòu)與磁盤上的靜態(tài)文件結(jié)構(gòu)是一樣的,。即PE (Portable Executable) 文件格式,。我們得到了進(jìn)程模塊的基地址以后,就可以根據(jù)PE文件的格式窮舉這個模塊的IMAGE_IMPORT_DESCRIPTOR數(shù)組,,看看進(jìn)程空間中是否引入了我們需要截獲的函數(shù)所在的動態(tài)鏈接庫,,比如需要截獲“TextOutA”,就必須檢查“Gdi32.dll”是否被引入了,。說到這里,,我們有必要介紹一下PE文件的格式,,如右圖,這是PE文件格式的大致框圖,,最前面是文件頭,,我們不必理會,從PE File Optional Header后面開始,,就是文件中各個段的說明,,說明后面才是真正的段數(shù)據(jù),而實(shí)際上我們關(guān)心的只有一個段,,那就是“.idata”段,,這個段中包含了所有的引入函數(shù)信息,,還有IAT(Import Address Table)的RVA(Relative Virtual Address)地址,。
說到這里,截獲WindowsAPI的整個原理就要真相大白了,。實(shí)際上所有進(jìn)程對給定的API函數(shù)的調(diào)用總是通過PE文件的一個地方來轉(zhuǎn)移的,,這就是一個該模塊(可以是EXE或DLL)的“.idata”段中的IAT輸入地址表(Import Address Table)。在那里有所有本模塊調(diào)用的其它DLL的函數(shù)名及地址,。對其它DLL的函數(shù)調(diào)用實(shí)際上只是跳轉(zhuǎn)到輸入地址表,,由輸入地址表再跳轉(zhuǎn)到DLL真正的函數(shù)入口。

具體來說,,我們將通過IMAGE_IMPORT_DESCRIPTOR數(shù)組來訪問“.idata”段中引入的DLL的信息,,然后通過IMAGE_THUNK_DATA數(shù)組來針對一個被引入的DLL訪問該DLL中被引入的每個函數(shù)的信息,找到我們需要截獲的函數(shù)的跳轉(zhuǎn)地址,,然后改成我們自己的函數(shù)的地址……具體的做法在后面的關(guān)鍵代碼中會有詳細(xì)的講解,。
   講了這么多原理,現(xiàn)在讓我們回到“鼠標(biāo)屏幕取詞”的專題上來,。除了API函數(shù)的截獲,,要實(shí)現(xiàn)“鼠標(biāo)屏幕取詞”,還需要做一些其它的工作,,簡單的說來,,可以把一個完整的取詞過程歸納成以下幾個步驟:
1. 安裝鼠標(biāo)鉤子,通過鉤子函數(shù)獲得鼠標(biāo)消息,。
使用到的API函數(shù):SetWindowsHookEx
2. 得到鼠標(biāo)的當(dāng)前位置,,向鼠標(biāo)下的窗口發(fā)重畫消息,讓它調(diào)用系統(tǒng)函數(shù)重畫窗口,。
     使用到的API函數(shù):WindowFromPoint,,ScreenToClient,InvalidateRect
3. 截獲對系統(tǒng)函數(shù)的調(diào)用,,取得參數(shù),,也就是我們要取的詞,。
對于大多數(shù)的Windows應(yīng)用程序來說,如果要取詞,,我們需要截獲的是“Gdi32.dll”中的“TextOutA”函數(shù),。
我們先仿照TextOutA函數(shù)寫一個自己的MyTextOutA函數(shù),如:
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)
{
       // 這里進(jìn)行輸出lpszString的處理
           // 然后調(diào)用正版的TextOutA函數(shù)
}
把這個函數(shù)放在安裝了鉤子的動態(tài)連接庫中,,然后調(diào)用我們最后給出的HookImportFunction函數(shù)來截獲進(jìn)程
對TextOutA函數(shù)的調(diào)用,,跳轉(zhuǎn)到我們的MyTextOutA函數(shù),完成對輸出字符串的捕捉,。HookImportFunction的
用法:
 HOOKFUNCDESC hd;
 PROC         pOrigFuns;
 hd.szFunc="TextOutA";
 hd.pProc=(PROC)MyTextOutA;
 HookImportFunction (AfxGetInstanceHandle(),"gdi32.dll",&hd,pOrigFuns);
下面給出了HookImportFunction的源代碼,,相信詳盡的注釋一定不會讓您覺得理解截獲到底是怎么實(shí)現(xiàn)的
很難,Ok,Let’s Go:

///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////
#include <crtdbg.h>

// 這里定義了一個產(chǎn)生指針的宏
#define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))

// 定義了HOOKFUNCDESC結(jié)構(gòu),我們用這個結(jié)構(gòu)作為參數(shù)傳給HookImportFunction函數(shù)
typedef struct tag_HOOKFUNCDESC
{
  LPCSTR szFunc; // The name of the function to hook.
  PROC pProc;    // The procedure to blast in.
} HOOKFUNCDESC , * LPHOOKFUNCDESC;

// 這個函數(shù)監(jiān)測當(dāng)前系統(tǒng)是否是WindowNT
BOOL IsNT();

// 這個函數(shù)得到hModule -- 即我們需要截獲的函數(shù)所在的DLL模塊的引入描述符(import descriptor)
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);

// 我們的主函數(shù)
BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule,
                         LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)
{
/////////////////////// 下面的代碼檢測參數(shù)的有效性 ////////////////////////////
 _ASSERT(szImportModule);
 _ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));
#ifdef _DEBUG
 if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));
 _ASSERT(paHookFunc.szFunc);
 _ASSERT(*paHookFunc.szFunc != '');
        _ASSERT(!IsBadCodePtr(paHookFunc.pProc));
#endif
 if ((szImportModule == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))
 {
  _ASSERT(FALSE);
  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
  return FALSE;
 }
//////////////////////////////////////////////////////////////////////////////

 // 監(jiān)測當(dāng)前模塊是否是在2GB虛擬內(nèi)存空間之上
 // 這部分的地址內(nèi)存是屬于Win32進(jìn)程共享的
 if (!IsNT() && ((DWORD)hModule >= 0x80000000))
 {
  _ASSERT(FALSE);
  SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);
  return FALSE;
 }
     // 清零
 if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC));

 // 調(diào)用GetNamedImportDescriptor()函數(shù),來得到hModule -- 即我們需要
 // 截獲的函數(shù)所在的DLL模塊的引入描述符(import descriptor)
 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);
 if (pImportDesc == NULL)
 return FALSE; // 若為空,則模塊未被當(dāng)前進(jìn)程所引入

 //  從DLL模塊中得到原始的THUNK信息,因?yàn)閜ImportDesc->FirstThunk數(shù)組中的原始信息已經(jīng)
 //  在應(yīng)用程序引入該DLL時覆蓋上了所有的引入信息,所以我們需要通過取得pImportDesc->OriginalFirstThunk
 //  指針來訪問引入函數(shù)名等信息
 PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule,
                                               pImportDesc->OriginalFirstThunk);

 //  從pImportDesc->FirstThunk得到IMAGE_THUNK_DATA數(shù)組的指針,由于這里在DLL被引入時已經(jīng)填充了
 //  所有的引入信息,所以真正的截獲實(shí)際上正是在這里進(jìn)行的
 PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc->FirstThunk);

 //  窮舉IMAGE_THUNK_DATA數(shù)組,尋找我們需要截獲的函數(shù),這是最關(guān)鍵的部分!
 while (pOrigThunk->u1.Function)
 {
  // 只尋找那些按函數(shù)名而不是序號引入的函數(shù)
  if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))
  {
   // 得到引入函數(shù)的函數(shù)名
   PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,
               pOrigThunk->u1.AddressOfData);

   // 如果函數(shù)名以NULL開始,跳過,繼續(xù)下一個函數(shù)  
   if ('' == pByName->Name[0])
    continue;

   // bDoHook用來檢查是否截獲成功
   BOOL bDoHook = FALSE;

   // 檢查是否當(dāng)前函數(shù)是我們需要截獲的函數(shù)
   if ((paHookFunc.szFunc[0] == pByName->Name[0]) &&
    (strcmpi(paHookFunc.szFunc, (char*)pByName->Name) == 0))
   {
    // 找到了!
    if (paHookFunc.pProc)
    bDoHook = TRUE;
   }
   if (bDoHook)
   {
    // 我們已經(jīng)找到了所要截獲的函數(shù),那么就開始動手吧
    // 首先要做的是改變這一塊虛擬內(nèi)存的內(nèi)存保護(hù)狀態(tài),讓我們可以自由存取
    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));

    // 保存我們所要截獲的函數(shù)的正確跳轉(zhuǎn)地址
    if (paOrigFuncs)
      paOrigFuncs = (PROC)pRealThunk->u1.Function;

    // 將IMAGE_THUNK_DATA數(shù)組中的函數(shù)跳轉(zhuǎn)地址改寫為我們自己的函數(shù)地址!
    // 以后所有進(jìn)程對這個系統(tǒng)函數(shù)的所有調(diào)用都將成為對我們自己編寫的函數(shù)的調(diào)用
    pRealThunk->u1.Function = (PDWORD)paHookFunc.pProc;

    // 操作完畢!將這一塊虛擬內(nèi)存改回原來的保護(hù)狀態(tài)
    DWORD dwOldProtect;
    _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
                        mbi_thunk.Protect, &dwOldProtect));
    SetLastError(ERROR_SUCCESS);
    return TRUE;
   }
  }
  // 訪問IMAGE_THUNK_DATA數(shù)組中的下一個元素
  pOrigThunk++;
  pRealThunk++;
 }
 return TRUE;
}

// GetNamedImportDescriptor函數(shù)的實(shí)現(xiàn)
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)
{
 // 檢測參數(shù)
 _ASSERT(szImportModule);
 _ASSERT(hModule);
 if ((szImportModule == NULL) || (hModule == NULL))
 {
  _ASSERT(FALSE);
  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
  return NULL;
 }

 // 得到Dos文件頭
 PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;

 // 檢測是否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數(shù)組尋找我們需要截獲的函數(shù)所在的模塊
 while (pImportDesc->Name)
 {
  PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc->Name);
  if (stricmp(szCurrMod, szImportModule) == 0)
      break; // 找到!中斷循環(huán)
  // 下一個元素
  pImportDesc++;
 }

 // 如果沒有找到,說明我們尋找的模塊沒有被當(dāng)前的進(jìn)程所引入!
 if (pImportDesc->Name == NULL)
  return NULL;

 // 返回函數(shù)所找到的模塊描述符(import descriptor)
 return pImportDesc;
}

// IsNT()函數(shù)的實(shí)現(xiàn)
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 //////////////////////////////////////////////////////////////////////

   不知道在這篇文章問世之前,,有多少朋友嘗試過去實(shí)現(xiàn)“鼠標(biāo)屏幕取詞”這項(xiàng)充滿了挑戰(zhàn)的技術(shù),,也只有嘗試過的朋友才能體會到其間的不易,尤其在探索API函數(shù)的截獲時,,手頭的幾篇資料沒有一篇是涉及到關(guān)鍵代碼的,,重要的地方都是一筆代過,MSDN更是顯得蒼白而無力,,也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA,,微軟還隱藏了多少秘密,好在硬著頭皮還是把它給攻克了,,希望這篇文章對大家能有所幫助,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多