一、開發(fā)背景:
我想大家都有過忙手忙腳最小化窗口(或關閉窗口)的經歷吧!原因很簡單??不想讓突如其來的老板、老媽、老婆看到我們電腦屏幕上正在顯示的游戲、日記、MM:-) 等屬于個人隱私的東東。
如果能做一個程序在后臺運行,當我們發(fā)出一個特殊的輸入事件(我選擇了鼠標左、右鍵同時按下)時,該程序就迅速隱藏正在顯示的窗口,免去人工瞄準并按下每個窗口右上方的那個小得可憐的的最小化按扭之苦了。當危險解除再利用這個特殊事件使隱藏的窗口恢復。
這對于像我這樣小腦不太發(fā)達、心理素質又不過硬而又經常在老板的眼皮底下“懸崖騎馬”的同志們來說是絕對有實戰(zhàn)意義的。于是我做了這個“魔高一丈”以實現(xiàn)上述功能!
二、程序原理:
首先,我們得能截獲鼠標左、右鍵同時按下去這個事件??這并不難??設一個標志變量當鼠標發(fā)出WM_LBUTTONDOWN并且又有WM_RBUTTONDOWN消息發(fā)出時把它置“1”罷了。
而我要說明的是,這個“同時按下”只是一種宏觀上的概念,鼠標是不會同時發(fā)出兩個消息的。其次就是解決不管鼠標位于任何窗口之上都能在程序里截獲(或者稱為監(jiān)聽更準確)到鼠標發(fā)出的消息并加以過濾的問題了,這是很關鍵的。我用了鉤子船長的那只鉤子(Hook),而且是全局的鼠標鉤子,它給了我們跟操作系統(tǒng)溝通的一個機會。
許多比較有神秘感的程序(比如金山詞霸的鼠標取詞)都是用它實現(xiàn)的,稍后我將詳細解釋。最后就是剩下能得到可見的窗口的句柄(HANDLE)并根據其句柄顯示、隱藏窗口的問題了,這也沒什么難的有現(xiàn)成的API函數??EnumWindows和ShowWindow。你可以先運行一下我的程序(那個大五星,需要把它跟那個Mousehook.dll文件放在一個文件夾下)。當鼠標左右鍵一起按下時所有的窗口都隱藏了;再一次同時按下左右鍵又可恢復隱藏窗口;單擊任務欄右下角(托盤)的圖標可隱藏或顯示本程序窗口。
三、開發(fā)步驟:
第0步、選用VC 6.0集成開發(fā)環(huán)境。
第1步、由于建立全局鉤子必須把鉤子函數放在DLL里面,所以我們選擇MFC AppWizard(DLL)創(chuàng)建一個新的項目,命名為“Mousehook”,再選擇選擇MFC Extension DLL類型(為了方便嘛?。?。
為什么必須把全局鉤子函數放在DLL里呢?這是因為系統(tǒng)會動態(tài)地調用你所添加的全局鼠標鉤子,所有窗口消息數都會由于你添加了鼠標鉤子而引起系統(tǒng)處理(何為處理?調用鉤子函數也。)這必然需要操作系統(tǒng)能夠從一個東東里動態(tài)地加入這段處理程序,而這個東東非DLL莫屬。
第2步、在項目中加入Mousehook.h文件用以構造一個鉤子類??CMousehook,具體如下:
class AFX_EXT_CLASS CMousehook:public CObject
{
public:
CMousehook();
~CMousehook();
BOOL starthook();//封裝SetWindowsHookEx( int idHook, HOOK_PROC lpfn, HINSTANCE hMod,DWORD dwThreadID)用來安裝鉤子
BOOL stophook(); //封裝UnhookWindowsHookEx( HHOOK hhk )用來卸載鉤子
VOID SetCheck1(UINT i);//處理對話框的選擇鉤選框1
VOID SetCheck2(UINT i);//處理對話框的選擇鉤選框2
VOID SetCheck3(UINT i);//處理對話框的選擇鉤選框3
static BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);//系統(tǒng)回調的鉤子函數
VOID UseForExit();//退出程序時恢復所有隱藏窗口
};
這里我想特別地提一下EnumWindowsProc函數前的CALLBACK跟static,對于CALLBACK我想給大家一個特別江湖的解釋其就是:凡是由你設計而卻由Windows系統(tǒng)調用的函數,統(tǒng)稱callback函數。這些函數都有一定的類型,以配合Windows的調用操作。??引用臺灣侯師傅的話。他還說,某些Windows API函數會要求以callback函數(的函數地址)作為其參數之一。
我們這里用到的又比如 SetWindowsHookEx( int idHook, HOOK_PROC lpfn, HINSTANCE hMod,DWORD dwThreadID)的第二個參數。這種API通常會在進行某種行為之后或滿足某種狀態(tài)的情況下調用其參數中的callback函數。
又由于系統(tǒng)在調用callback函數的時候并不會借助任何對象去調用該callback函數,所以在用類來封裝callback函數時,需要用static來使callback函數能夠獨立于對象而又屬于類的成員函數。明白了不?(?。康厍蛉硕贾姥?!太傷自尊了!)
第3步、在項目中加入Mousehook.cpp文件在CMousehook里封裝其中加入必要的共享數據以及SetWindowsHookEx、UnhookWindowsHookEx等函數??這些API函數具體的參數的類型跟作用解釋在程序代碼的注釋里有(網上也到處都有,我也是從網上摳下來的。一個聲音高叫著??當然MSDN里也有。),而把它們寫在文章里就不免有騙取稿費之嫌了。我只是想解釋一下為什么需要使用一個共享的數據段,如下:
#pragma data_seg("mydata") //編譯器識別的指令用以在虛擬內存中開辟一個數據段存放該指令下面的數據
HINSTANCE glhInstance=NULL; //DLL實例(或者說模塊)的句柄。
HHOOK glhHook=NULL; //鼠標鉤子的句柄。
HWND GlobalWndHandle[100]={NULL,.....};//用來存放被隱藏的窗口的句柄,以數組的形式保存。
//該數組必須初始化,原因見下文。我以“......”省略。
UINT Global_i=0; //用以在循環(huán)中序列化窗口數組的變量。
BOOL Condition1=0; //用以記錄左鍵按下或釋放的標志變量。
BOOL Condition2=0; //用以記錄右鍵按下或釋放的標志變量。
BOOL HideOrVisitableFlag=0; //用以標識當再次有左、右鍵同時按下的情況發(fā)生時是隱藏還是顯示窗口。
BOOL Check1=0; //用來表示控件Check1狀態(tài)的標志變量。
BOOL Check2=0; //用來表示控件Check2狀態(tài)的標志變量。
BOOL Check3=0; //用來表示控件Check3狀態(tài)的標志變量。
#pragma data_seg() //與#pragma data_seg("mydata") 首尾呼應表示該數據段的結束。
加入上述數據段以后還應在項目里插入一個“Mousehook.def”文件,用:"SECTIONS mydata READ WRITE SHARED"將mydata數據段設置為一個可讀寫的共享段。在程序里加入預編譯指令,或在開發(fā)環(huán)境的項目設置里也可以達到設置數據段屬性的目的,我就不一一贅述了。
我前面講過,系統(tǒng)通過調用放在DLL中的鉤子回調函數來實現(xiàn)全局鉤(鉤取所有窗口的鼠標消息),操作系統(tǒng)對DLL的操作僅僅是把DLL映射到需要它的進程的虛擬地址空間里去。也就是說,DLL函數中的代碼所創(chuàng)建的任何對象(包括變量)都歸調用它的線程或進程所有。
“DLL在WIN32中什么都不擁有”??這句話很重要。比如我們在DLL里建立了一個變量a,而我們的這個DLL文件又被兩個進程所調用,這兩個進程的中都用到了a可這絕對是兩個不同存儲單元中存儲的兩個a,它們之間沒有絲毫的聯(lián)系。給其中一個賦值也絕對不會影響到另一個。
而對于本程序的一些數據是需要在不同的進程中保持唯一的(也可以說是一致),比方說: HWND GlobalWndHandle[100]它是用來保存程序做了隱藏的窗口之句柄的數組。當程序運行,我在任意窗口A中同時按下了鼠標左、右鍵,由于設置了鼠標鉤子,系統(tǒng)會調用DLL中的鉤子處理函數截獲消息并加以處理,即把目前的可見窗口隱藏并把窗口句柄保存到GlobalWndHandle[100]數組中以備將來顯示之用。
如果不把GlobalWndHandle[100]放到一個共享的數據段里,系統(tǒng)就會在目前我們截獲鼠標消息的A窗口的進程的地址空間里開辟HWND GlobalWndHandle[100]來存儲窗口句柄。這樣對于其他進程就不能方便地得到這個進程存入GlobalWndHandle[100]數組的數據了。這時只能將GlobalWndHandle[100]等需要跨進程訪問的變量數據放在一個共享的數據段里了。
另外,需要特別注意??必須給這些變量賦初值(就象我在程序代碼里傻呼呼地寫了100個NULL一樣。你可以不初始化這個數組試驗一下,有助于你理解我上面的話),否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數據段中。
第4步:編譯生成dll文件,并用MFC AppWizard(exe)建立一個基于對話框的項目,在里面添加一個名為“Mousehook.h”的頭文件其內容與dll項目中的“Mousehook.h”文件一致,打開菜單的“Project Settings”對話框在“Link”選項標簽的“Object/library modules”編輯框里填入Mousehook.lib(此文件是與dll一起生成的,當編譯一個隱式調用dll的exe時,lib文件起到提供dll引出函數接口地址的作用,如果此路徑設置不正確程序是無法進行連接的)文件的存放路徑。這樣就可以放心使用dll里定義的CMousehook類的成員了。如下:
1 在HideWindowDlg.h中加入#include "MouseHook.h"并在CHideWindowDlg中定義一個CMousehook類對象hook。
2 在CHideWindowDlg::OnInitDialog()函數中加入hook.starthook()并初始化相關變量,這樣當對話框初始時就會啟動鼠標鉤子。
3 在CHideWindowDlg::~CHideWindowDlg()函數中加入hook.stophook()。用以釋放對話框對象時解除鼠標鉤。
為了不忽略讀者的智力水平我只對主要的代碼進行了說明,其余有關托盤、Check控件的部分代碼都比較傳統(tǒng)也沒什么好說明的。最后,編譯成exe文件以后還須把Mousehook.dll文件拷貝到同exe相同的目錄下才能正確運行exe。