在編寫工具程式以及系統管理程式的時候。常常需要擷取某個程序的主視窗以及建立此程序的程式名。擷取主視窗的目的是向視窗發送各種消息。擷取啟動程序的程式名可以控制對程序的操作。但是有些程序往往有多個主視窗。你要的是哪一個主視窗呢?如果你用過Outlook程式,你就會發現它有多個主視窗,一個視窗列出收件箱和其它檔案夾。如果你打開e-mail,便會有另外一個視窗顯示資訊。它們都是沒有父視窗(或者說宿主視窗)的主視窗。運作一下Spy程式,你甚至會發現它們的視窗類名都相同:rctrl_renwnd32。資料總管(Explorer.exe)也有不止一個主視窗。如圖一所示,資料總管有兩個主視窗。一般來講,想要擷取主視窗,憑視窗的式樣或類名,你沒有什麼辦法知道哪一個視窗是真正意義上的主視窗。
首先我們讨論如何從多個視窗中擷取主視窗?其實很容易。利用兩個API函數便可以實作。這兩個API是 EnumWindows 和 GetWindowThreadProcessId。如果你對這兩個函數不熟悉,不要怕,本文提供了一個C++類來對這兩個API進行封裝。這個類叫 CMainWindowIterator,用它可以枚舉某個程序(已知程序ID)的所有主視窗。這正是我們想要的東西。其使用方法如下:
1 2 3 4 5 | DWORD pid = // 已知某個程序的ID CMainWindowIterator itw(pid); for (HWND hwnd = itw.First(); hwnd; hwnd=itw.Next()) { // do something } |
就這麼簡單,CMainWindowIterator派生于一個更通用的類:CWindowIterator,CWindowIterator負責将::EnumWindows函數打包以隐藏回調細節。它有一個虛拟函數OnWindow,你可以在派生類中重寫這個函數,進而可以用任何方式來枚舉視窗。CMainWindowIterator就是重寫了OnWindow函數,讓它隻擷取屬于某個給定程序的主視窗:
1 2 3 4 5 6 7 8 9 10 11 | // (在構造函數中設定m_pid) BOOL CMainWindowIterator::OnWindow(HWND hwnd) { if ((GetWindowLong(hwnd,GWL_STYLE) & WS_VISIBLE)) { DWORD pidwin; GetWindowThreadProcessId(hwnd, &pidwin); if (pidwin==m_pid) return TRUE; } return FALSE; } |
這兩個類的定義如下:(對應的檔案是 EnumProc.h 和 EnumProc.cpp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // // 這個類主要是封裝::EnumWindows,列舉頂層視窗 // class CWindowIterator { protected: HWND* m_hwnds; // 隸屬于某個程序PID視窗句柄數組 DWORD m_nAlloc; // 數組大小 DWORD m_count; // 找到的視窗句柄數 DWORD m_current; // 目前的視窗句柄 static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lp); // 虛拟枚舉函數 virtual BOOL OnEnumProc(HWND hwnd); // 在派生中改寫此函數來過濾不同種類的視窗 virtual BOOL OnWindow(HWND hwnd) { return TRUE; } public: CWindowIterator(DWORD nAlloc=1024); ~CWindowIterator(); DWORD GetCount() { return m_count; } HWND First(); HWND Next() { return m_hwnds && m_current <m_count ? m_hwnds[m_current++] : NULL; } }; // 列舉某個程序的頂層視窗 // class CMainWindowIterator : public CWindowIterator { protected: DWORD m_pid; // 程序ID virtual BOOL OnWindow(HWND hwnd); public: CMainWindowIterator(DWORD pid, DWORD nAlloc=1024); ~CMainWindowIterator(); }; |
圖一是用上述C++類編寫的一個控制台程式lp.exe的輸出畫面。最後兩欄分别是對應程序的“視窗句柄”和“類名/視窗标題”。其指令行開關“/ct”表示列出視窗類名(c)和視窗标題(t)。
圖一
一般來講,如果隸屬于某個程序的視窗沒有可見的父視窗,那麼這個視窗就可以認為是此程序的主視窗。對WS_VISIBLE的檢查很重要,因為有些應用建立多個不可見的頂層視窗。關于CMainWindowIterator類的使用細節請參見本文的例子源代碼。
接下來我們來讨論如何擷取建立程序的程式檔案名。有人用各種方法嘗試過,比如:GetModuleFileName,GetModuleInstance 和 GetModuleHandle,好像都不行。為什麼呢?其實,方法是沒錯,但調用這些函數得到的隻是目前正在運作的這個程序已經加載的子產品名(modules),不能用于擷取其它程序所加載的子產品。是以,必須想别的辦法,首先要考慮兩種情況,一種是如果你寫的程式在Windows NT,Windows 2000,Windows XP環境運作,則可以使用PSAPI,這是一個Windows作業系統中比較新的DLL,利用其中輸出的API函數可以擷取程序和子產品的詳細資訊。另一種是如果你寫的程式在Windows 9x或者Windows Me中運作,則必須借助于ToolHelp,限于本文的篇幅,我在這裡不介紹如何使用ToolHelp,如果你感興趣的話可以參考,MSDN的技術支援文章Q175030,題目為“如何在Win32中枚舉應用程式”。
PSAPI中有一個函數是GetModuleFileNameEx。它通過某個程序和子產品句柄作為參數來獲得子產品名。那麼對于某個程序來說,你怎麼知道哪個子產品是啟動程序的執行檔案呢呢?PSAPI中的另一個函數EnumProcessModules将某個程序中所有子產品的子產品句柄填充到一個數組中。這個數組的第一個元素便是主子產品的句柄,是以你用下面的代碼來得到第一個HMODULE:
1 2 3 | DWORD count; HMODULE hm[1]; EnumProcessModules(hProcess, hm, 1, &count); |
然後調用GetModuleFileNameEx。
實際上從前面的圖一中可以看到,在lp.exe程式中我們已經實作了羅列程序及其對應的子產品名。程式的實作細節中還用到了PSAPI輸出的API函數EnumProcesses來枚舉所有運作程序,為了對具體的細節進行封裝,我如法炮制編寫了與CWindowIterator 和CMainWindowIterator類似的兩個C++類:CProcessIterator 和 CProcessModuleIterator ,它們分别對EnumProcesses 和EnumProcessModules API函數進行了封裝。有了這兩個打包類,一切都變得如此簡單。
1 2 3 4 | CProcessIterator itp; for (DWORD pid=itp.First(); pid; pid=itp.Next()) { // 處理每一個程序 } |
下面是擷取建立程序的EXE檔案名的方法:
1 2 3 4 | CProcessModuleIterator itm(pid); HMODULE hModule = itm.First(); // .EXE TCHAR modname[_MAX_PATH]; GetModuleBaseName(itm.GetProcessHandle(), hModule, modname, _MAX_PATH); |
因為lp顯示出來的并不是一個含全路徑的子產品檔案名,是以我用另外一個PSAPI函數GetModuleBaseName來代替GetModuleFileNameEx進而擷取全路徑名。此外,由于CProcessModuleIterator自己會打開程序枚舉子產品,是以不必調用OpenProcess。用CProcessModuleIterator::GetProcessHandle可以得到已打開程序的句柄。lp程式還用CMainWindowIterator來顯示每個特定程序的所有主視窗。下面是CProcessIterator 和 CProcessModuleIterator的定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 程序列舉類 -- 列舉出系統中的所有程序,但總是跳過第一個PID=0的程序,即空閑程序(IDLE) // class CProcessIterator { protected: DWORD* m_pids; // 包含程序IDs的數祖 DWORD m_count; // 數組大小 DWORD m_current; // 目前數組項 public: CProcessIterator(); ~CProcessIterator(); DWORD GetCount() { return m_count; } DWORD First(); DWORD Next() { return m_pids && m_current <m_count ? m_pids[m_current++] : 0; } }; / // 列舉某個程序的子產品,第一個子產品就是建立此程序的主exe程式 // class CProcessModuleIterator { protected: HANDLE m_hProcess; // 程序句柄 HMODULE* m_hModules; // 子產品句柄數組 DWORD m_count; // 數組大小 DWORD m_current; // 目前子產品的句柄 public: CProcessModuleIterator(DWORD pid); ~CProcessModuleIterator(); HANDLE GetProcessHandle() { return m_hProcess; } DWORD GetCount() { return m_count; } HMODULE First(); HMODULE Next() { return m_hProcess && m_current < m_count ? m_hModules[m_current++] : 0; } }; |
參考:http://tech.ddvip.com/2006-04/11444325604256_2.html
http://www.myexception.cn/vc-mfc/165196.html