作者:濟南 宋悅
轉載請與作者聯系
一、開發背景:
我想大家都有過忙手忙腳最小化窗口(或關閉窗口)的經歷吧!原因很簡單——不想讓突如其來的老板、老媽、老婆看到我們電腦屏幕上正在顯示的游戲、日記、MM:-)
等屬於個人隱私的東東。
如果能做一個程序在後台運行,當我們發出一個特殊的輸入事件(我選擇了鼠標左、右鍵同時按下)時,該程序就迅速隱藏正在顯示的窗口,免去人工瞄准並按下每個窗口右上方的那個小得可憐的的最小化按扭之苦了。當危險解除再利用這個特殊事件使隱藏的窗口恢復。
這對於像我這樣小腦不太發達、心理素質又不過硬而又經常在老板的眼皮底下“懸崖騎馬”的同志們來說是絕對有實戰意義的。於是我做了這個“魔高一丈”以實現上述功能!
二、程序原理:
首先,我們得能截獲鼠標左、右鍵同時按下去這個事件——這並不難——設一個標志變量當鼠標發出WM_LBUTTONDOWN並且又有WM_RBUTTONDOWN消息發出時把它置“1”罷了。
而我要說明的是,這個“同時按下”只是一種宏觀上的概念,鼠標是不會同時發出兩個消息的。其次就是解決不管鼠標位於任何窗口之上都能在程序裡截獲(或者稱為監聽更准確)到鼠標發出的消息並加以過濾的問題了,這是很關鍵的。我用了鉤子船長的那只鉤子(Hook),而且是全局的鼠標鉤子,它給了我們跟操作系統溝通的一個機會。
許多比較有神秘感的程序(比如金山詞霸的鼠標取詞)都是用它實現的,稍後我將詳細解釋。最後就是剩下能得到可見的窗口的句柄(HANDLE)並根據其句柄顯示、隱藏窗口的問題了,這也沒什麼難的有現成的API函數——EnumWindows和ShowWindow。你可以先運行一下我的程序(那個大五星,需要把它跟那個Mousehook.dll文件放在一個文件夾下)。當鼠標左右鍵一起按下時所有的窗口都隱藏了;再一次同時按下左右鍵又可恢復隱藏窗口;單擊任務欄右下角(托盤)的圖標可隱藏或顯示本程序窗口。
三、開發步驟:
第0步、選用VC 6.0集成開發環境。
第1步、由於建立全局鉤子必須把鉤子函數放在DLL裡面,所以我們選擇MFC AppWizard(DLL)創建一個新的項目,命名為“Mousehook”,再選擇選擇MFC
Extension DLL類型(為了方便嘛!)。
為什麼必須把全局鉤子函數放在DLL裡呢?這是因為系統會動態地調用你所添加的全局鼠標鉤子,所有窗口消息數都會由於你添加了鼠標鉤子而引起系統處理(何為處理?調用鉤子函數也。)這必然需要操作系統能夠從一個東東裡動態地加入這段處理程序,而這個東東非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);//系統回調的鉤子函數
VOID UseForExit();//退出程序時恢復所有隱藏窗口
};
這裡我想特別地提一下EnumWindowsProc函數前的CALLBACK跟static,對於CALLBACK我想給大家一個特別江湖的解釋其就是:凡是由你設計而卻由Windows系統調用的函數,統稱callback函數。這些函數都有一定的類型,以配合Windows的調用操作。——引用台灣侯師傅的話。他還說,某些Windows
API函數會要求以callback函數(的函數地址)作為其參數之一。
我們這裡用到的又比如 SetWindowsHookEx( int idHook, HOOK_PROC lpfn, HINSTANCE
hMod,DWORD dwThreadID)的第二個參數。這種API通常會在進行某種行為之後或滿足某種狀態的情況下調用其參數中的callback函數。
又由於系統在調用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; //用以在循環中序列化窗口數組的變量。
BOOL Condition1=0; //用以記錄左鍵按下或釋放的標志變量。
BOOL Condition2=0; //用以記錄右鍵按下或釋放的標志變量。
BOOL HideOrVisitableFlag=0; //用以標識當再次有左、右鍵同時按下的情況發生時是隱藏還是顯示窗口。
BOOL Check1=0; //用來表示控件Check1狀態的標志變量。
BOOL Check2=0; //用來表示控件Check2狀態的標志變量。
BOOL Check3=0; //用來表示控件Check3狀態的標志變量。
#pragma data_seg() //與#pragma data_seg("mydata") 首尾呼應表示該數據段的結束。
加入上述數據段以後還應在項目裡插入一個“Mousehook.def”文件,用:"SECTIONS mydata READ WRITE
SHARED"將mydata數據段設置為一個可讀寫的共享段。在程序裡加入預編譯指令,或在開發環境的項目設置裡也可以達到設置數據段屬性的目的,我就不一一贅述了。
我前面講過,系統通過調用放在DLL中的鉤子回調函數來實現全局鉤(鉤取所有窗口的鼠標消息),操作系統對DLL的操作僅僅是把DLL映射到需要它的進程的虛擬地址空間裡去。也就是說,DLL函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有。
“DLL在WIN32中什麼都不擁有”——這句話很重要。比如我們在DLL裡建立了一個變量a,而我們的這個DLL文件又被兩個進程所調用,這兩個進程的中都用到了a可這絕對是兩個不同存儲單元中存儲的兩個a,它們之間沒有絲毫的聯系。給其中一個賦值也絕對不會影響到另一個。
而對於本程序的一些數據是需要在不同的進程中保持唯一的(也可以說是一致),比方說: HWND GlobalWndHandle[100]它是用來保存程序做了隱藏的窗口之句柄的數組。當程序運行,我在任意窗口A中同時按下了鼠標左、右鍵,由於設置了鼠標鉤子,系統會調用DLL中的鉤子處理函數截獲消息並加以處理,即把目前的可見窗口隱藏並把窗口句柄保存到GlobalWndHandle[100]數組中以備將來顯示之用。
如果不把GlobalWndHandle[100]放到一個共享的數據段裡,系統就會在目前我們截獲鼠標消息的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控件的部分代碼都比較傳統也沒什麼好說明的。最後,編譯成exe文件以後還須把Mousehook.dll文件拷貝到同exe相同的目錄下才能正確運行exe。
臨了,希望大家能對我上文中含糊、混沌的地方提出批評指正,也歡迎大家來信([email protected])切磋。