在編寫工具程序以及系統管理程序的時候。常常需要獲取某個進程的主窗口以及創建此進程的程序名。獲取主窗口的目的是向窗口發送各種消息。獲取啟動進程的程序名可以控制對進程的操作。但是有些進程往往有多個主窗口。你要的是哪一個主窗口呢?如果你用過Outlook程序,你就會發現它有多個主窗口,一個窗口列出收件箱和其它文件夾。如果你打開e-mail,便會有另外一個窗口顯示信息。它們都是沒有父窗口(或者說宿主窗口)的主窗口。運行一下Spy程序,你甚至會發現它們的窗口類名都相同:rctrl_renwnd32。資源管理器(Explorer.exe)也有不止一個主窗口。如圖一所示,資源管理器有兩個主窗口。一般來講,想要獲取主窗口,憑窗口的式樣或類名,你沒有什麼辦法知道哪一個窗口是真正意義上的主窗口。
首先我們討論如何從多個窗口中獲取主窗口?其實很容易。利用兩個API函數便可以實現。這兩個API是 EnumWindows 和 GetWindowThreadProcessId。如果你對這兩個函數不熟悉,不要怕,本文提供了一個C++類來對這兩個API進行封裝。這個類叫 CMainWindowIterator,用它可以枚舉某個進程(已知進程ID)的所有主窗口。這正是我們想要的東西。其使用方法如下:
DWORD pid = // 已知某個進程的ID
CMainWindowIterator itw(pid);
for (HWND hwnd = itw.First(); hwnd; hwnd=itw.Next()) {
// do something
}
就這麼簡單,CMainWindowIterator派生於一個更通用的類:CWindowIterator,CWindowIterator負責將::EnumWindows函數打包以隱藏回調細節。它有一個虛擬函數OnWindow,你可以在派生類中重寫這個函數,從而可以用任何方式來枚舉窗口。CMainWindowIterator就是重寫了OnWindow函數,讓它只獲取屬於某個給定進程的主窗口:
// (在構造函數中設置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)
//////////////////////////////////////////////////////
// 這個類主要是封裝::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:
DWORD count;
HMODULE hm[1];
EnumProcessModules(hProcess, hm, 1, &count);
然後調用GetModuleFileNameEx。
實際上從前面的圖一中可以看到,在lp.exe程序中我們已經實現了羅列進程及其對應的模塊名。程序的實現細節中還用到了PSAPI輸出的API函數EnumProcesses來枚舉所有運行進程,為了對具體的細節進行封裝,我如法炮制編寫了與CWindowIterator 和CMainWindowIterator類似的兩個C++類:CProcessIterator 和 CProcessModuleIterator ,它們分別對EnumProcesses 和EnumProcessModules API函數進行了封裝。有了這兩個打包類,一切都變得如此簡單。
CProcessIterator itp;
for (DWORD pid=itp.First(); pid; pid=itp.Next()) {
// 處理每一個進程
}
下面是獲取創建進程的EXE文件名的方法:
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的定義:
////////////////////////////////////////////////////////////////////////////////////////
// 進程列舉類 -- 列舉出系統中的所有進程,但總是跳過第一個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;
}
};
本文配套源碼