Win32全域性鉤子在VC5中的實現 (轉)

worldblog發表於2007-12-04
Win32全域性鉤子在VC5中的實現 (轉)[@more@]

全域性鉤子在VC5中的實現

  ·是建立在事件的機制上的,說穿了就是整個系統都是透過訊息的傳遞來實現的。而鉤子是Windows系統中非常重要的系統介面,用它可以截獲並處理送給其他應用的訊息,來完成普通應用程式難以實現的功能。鉤子的種類很多,每種鉤子可以截獲並處理相應的訊息,如鍵盤鉤子可以截獲鍵盤訊息,外殼鉤子可以擷取、啟動和關閉應用程式的訊息等。本文在VC5環境下實現了一個簡單的滑鼠鉤子程式,並對Win32全域性鉤子的執行機制、Win32 DLL的特點、VC5環境下的MFC DLL以及共享資料等相關知識進行了簡單的闡述。

  一.Win32全域性鉤子的執行機制

  鉤子實際上是一個處理訊息的程式段,透過系統,把它掛入系統。每當特定的訊息發出,在沒有到達目的視窗前,鉤子程式就先捕獲該訊息,亦即鉤子先得到控制權。這時鉤子函式即可以加工處理(改變)該訊息,也可以不作處理而繼續傳遞該訊息,還可以強制結束訊息的傳遞。對每種型別的鉤子由系統來維護一個鉤子鏈,最近的鉤子放在鏈的開始,而最先安裝的鉤子放在最後,也就是後加入的先獲得控制權。要實現Win32的系統鉤子,必須呼叫SDK中的函式SetWindowsHookEx來安裝這個鉤子函式,這個函式的原型是HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,D 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 C

  {

  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 Tenating!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"..MousehookMousehook.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);

   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

   }

  }

  SetIcon(m_hIcon, TRUE);//Set big icon

  SetIcon(m_hIcon, FALSE);//Set small icon

  //TODO: Add extra initialization here

  CWnd * pwnd=GetDlgItem(IDC_EDIT1);

  //取得編輯框的類指標

  m_hook.starthook(pwnd->GetSafeHwnd());

  //取得編輯框的視窗控制程式碼並安裝鉤子

  return TRUE;

  //return TRUE unless you set the focus to a control

  }

  (7)連結DLL庫,即把..MousehookdeMousehook.lib加入到專案設定連結標籤中;

  (8)編譯專案生成可執行檔案;

  (9)把Mousehook.DLL複製到..mousedebug目錄中;

  (10)先執行幾個可執行程式,然後執行Mouse.exe程式,把滑鼠在不同視窗中移動,在Mouse.exe程式視窗中的編輯框內將顯示出滑鼠所在的應用程式主視窗的標題。pcc

  (作者地址:遼寧省鐵嶺縣委機要局 112000 收稿日期:1998.12.14)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988167/,如需轉載,請註明出處,否則將追究法律責任。

相關文章