如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進(jìn)程
編譯:NorthTibet
下載源代碼
摘要
我們在編寫程序時(shí),常常遇到的一件事情就是要準(zhǔn)確列出系統(tǒng)中所有正在運(yùn)行的程序或者進(jìn)程。Windows 任務(wù)管理器就是這樣的一個(gè)程序。它既能列出運(yùn)行的桌面應(yīng)用程序,又能列出系統(tǒng)中所有運(yùn)行的進(jìn)程。那么,我們在程序中如何實(shí)現(xiàn)這樣的任務(wù)呢?本文下面將詳細(xì)討論這個(gè)問題。
枚舉頂層(top-level)窗口
枚舉桌面頂層窗口相對(duì)于枚舉進(jìn)程來說可能要容易一些。枚舉桌面頂層窗口的方法是用 EnumWindows() 函數(shù)。不要用 GetWindow()來創(chuàng)建窗口列表,因?yàn)榇翱谥g復(fù)雜的父子及同胞關(guān)系(Z-Order)容易造成混亂而使得枚舉結(jié)果不準(zhǔn)確。
EnumWindows()有兩個(gè)參數(shù),一個(gè)是指向回調(diào)函數(shù)的指針,一個(gè)是用戶定義的 LPARAM 值, 針對(duì)每個(gè)桌面窗口(或者頂層窗口)它調(diào)用回調(diào)函數(shù)一次。然后回調(diào)函數(shù)用該窗口句柄做一些處理,比如將它添加到列表中。這個(gè)方法保證枚舉結(jié)果不會(huì)被窗口復(fù)雜的層次關(guān)系搞亂,因此,一旦有了窗口句柄,我們就可以通過 GetWindowText() 得到窗口標(biāo)題。
枚舉進(jìn)程
建立系統(tǒng)進(jìn)程列表比枚舉窗口稍微復(fù)雜一些。這主要是因?yàn)樗玫?API 函數(shù)對(duì)于不同的 Win32 操作系統(tǒng)有依賴性。在 Windows 9x、Windows Me、Windows 2000 Professional 以及 Windows XP 中,我們可以用 ToolHelp32 庫中的 APIs 函數(shù)。但是在 Windows NT 里,我們必須用 PSAPI 庫中的 APIs 函數(shù), PSAPI 庫是 SDK 的一部分。本文我們將討論上述所有平臺(tái)中的實(shí)現(xiàn)。附帶的例子程序?qū)?duì)上述庫中的 APIs 進(jìn)行包裝,以便包裝后的函數(shù)能支持所有 Win32 操作系統(tǒng)。
使用 ToolHelp32 庫枚舉進(jìn)程
ToolHelp32 庫函數(shù)在 KERNEL32.dll 中,它們都是標(biāo)準(zhǔn)的 API 函數(shù)。但是 Windows NT 4.0 不提供這些函。
ToolHelp32 庫中有各種各樣的函數(shù)可以用來枚舉系統(tǒng)中的進(jìn)程、線程以及獲取內(nèi)存和模塊信息。其中枚舉進(jìn)程 只需用如下三個(gè)的函數(shù):CreateToolhelp32Snapshot()、Process32First()和 Process32Next()。
使用 ToolHelp32 函數(shù)的第一步是用 CreateToolhelp32Snapshot() 函數(shù)創(chuàng)建系統(tǒng)信息“快照”。這個(gè)函數(shù)可以讓你選擇存儲(chǔ)在快照中的信息類型。如果你只是對(duì)進(jìn)程信息感興趣,那么只要包含 TH32CS_SNAPPROCESS 標(biāo)志即可。 CreateToolhelp32Snapshot() 函數(shù)返回一個(gè) HANDLE,完成調(diào)用之后,必須將此 HANDLE 傳給 CloseHandle()。
接下來是調(diào)用一次 Process32First 函數(shù),從快照中獲取進(jìn)程列表,然后重復(fù)調(diào)用 Process32Next,直到函數(shù)返回 FALSE 為止。這樣將遍歷快照中進(jìn)程列表。這兩個(gè)函數(shù)都帶兩個(gè)參數(shù),它們分別是快照句柄和一個(gè) PROCESSENTRY32 結(jié)構(gòu)。
調(diào)用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中將包含系統(tǒng)中某個(gè)進(jìn)程的關(guān)鍵信息。其中進(jìn)程 ID 就存儲(chǔ)在此結(jié)構(gòu)的 th32ProcessID。此 ID 可以被傳給 OpenProcess() API 以獲得該進(jìn)程的句柄。對(duì)應(yīng)的可執(zhí)行文件名及其存放路徑存放在 szExeFile 結(jié)構(gòu)成員中。在該結(jié)構(gòu)中還可以找到其它一些有用的信息。
注意:在調(diào)用 Process32First() 之前,一定要記住將 PROCESSENTRY32 結(jié)構(gòu)的 dwSize 成員設(shè)置成 sizeof(PROCESSENTRY32)。
使用 PSAPI 庫枚舉進(jìn)程
在 Windows NT 中,創(chuàng)建進(jìn)程列表使用 PSAPI 函數(shù),這些函數(shù)在 PSAPI.DLL 中。這個(gè)文件是隨 Platform SDK 一起分發(fā)的,最新版本的 Platform SDK 可以從這里下載:
使用這個(gè)庫所需的 PSAPI.h 和 PSAPI.lib 文件也在該 Platform SDK 中。
為了使用 PSAPI 庫中的函數(shù),需將 PSAPI.lib 添加到代碼項(xiàng)目中,同時(shí)在所有調(diào)用 PSAPI API 的模塊中包含 PSAPI.h 文件。記住一定要隨可執(zhí)行文件一起分發(fā) PSAPI.DLL,因?yàn)樗浑S Windows NT 一起分發(fā)。你可以點(diǎn)擊這里單獨(dú)下載 PSAPI.DLL 的可分發(fā)版本(不用完全下載 Platform SDK)。
與 ToolHelp32 一樣,PSAPI 庫也包含各種各樣有用的函數(shù)。由于篇幅所限,本文只討論與枚舉進(jìn)程有關(guān)函數(shù):EnumProcesses()、 EnumProcessModules()、GetModuleFileNameEx()和 GetModuleBaseName()。
創(chuàng)建進(jìn)程列表的第一步是調(diào)用 EnumProcesses()。該函數(shù)的聲明如下:
BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );
EnumProcesses()帶三個(gè)參數(shù),DWORD 類型的數(shù)組指針 lpidProcess;該數(shù)組的大小尺寸 cb;以及一個(gè)指向 DWORD 的指針 cbNeeded,它接收返回?cái)?shù)據(jù)的長度。DWORD 數(shù)組用于保存當(dāng)前運(yùn)行的進(jìn)程 IDs。cbNeeded 返回?cái)?shù)組所用的內(nèi)存大小。下面算式可以得出返回了多少進(jìn)程:nReturned = cbNeeded / sizeof(DWORD)。
注意:雖然文檔將返回的 DWORD 命名為“cbNeeded”,實(shí)際上是沒有辦法知道到底要傳多大的數(shù)組的。EnumProcesses()根本不會(huì)在 cbNeeded 中返回一個(gè)大于 cb 參數(shù)傳遞的數(shù)組值。結(jié)果,唯一確保 EnumProcesses()函數(shù)成功的方法是分配一個(gè) DWORD 數(shù)組,并且,如果返回的 cbNeeded 等于 cb,分配一個(gè)較大的數(shù)組,并不停地嘗試直到 cbNeeded 小于 cb
現(xiàn)在,你獲得了一個(gè)數(shù)組,其元素保存著系統(tǒng)中每個(gè)進(jìn)程的ID。如果你要想獲取進(jìn)程名,那么你必須首先獲取一個(gè)句柄。要想從進(jìn)程 ID 得到句柄,就得調(diào)用 OpenProcess()。
一旦有了句柄,則需要得到該進(jìn)程的第一個(gè)模塊。為此調(diào)用 EnumProcessModules() API:
EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );
調(diào)用之后,hModule 變量中保存的將是進(jìn)程中的第一個(gè)模塊。記住進(jìn)程其實(shí)沒有名字,但進(jìn)程的第一個(gè)模塊既是該進(jìn)程的可執(zhí)行模塊。現(xiàn)在你可以用 hModule 中返回的模塊句柄調(diào)用 GetModuleFileNameEx() 或 GetModuleBaseName() API 函數(shù)獲取全路徑名,或者僅僅是進(jìn)程可執(zhí)行模塊名。兩個(gè)函數(shù)均帶四個(gè)參數(shù):進(jìn)程句柄,模塊句柄,返回名字的緩沖指針以及緩沖大小尺寸。
用 EnumProcesses() API 返回的每一個(gè)進(jìn)程 ID 重復(fù)這個(gè)調(diào)用過程,你便可以創(chuàng)建 Windows NT 的進(jìn)程列表。
16位進(jìn)程的處理方法 在 Windows 95,Windows 98 和 Windows ME 中,ToolHelp32 對(duì)待16位程序一視同仁,它們與 Win32 程序一樣有自己的進(jìn)程IDs。但是在 Windows NT,Windows 2000 或 Windows XP 中情況并不是這樣。在這些操作系統(tǒng)中,16位程序運(yùn)行在所謂的 VDM 當(dāng)中(也就是DOS機(jī))。
為了在 Windows NT,Windows 2000 和 Windows XP 中枚舉16位程序,你必須使用一個(gè)名為 VDMEnumTaskWOWEx()的函數(shù)。在源代碼模塊中必須包含 VDMDBG.h,并且 VDMDBG.lib 文件必須與項(xiàng)目鏈接。這兩個(gè)文件都在 Platform SDK 中。該函數(shù)的聲明如下:
INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,LPARAM lparam );
此處 dwProcessId 是 NTVDM 中擬枚舉的16位任務(wù)進(jìn)程標(biāo)示符。參數(shù) fp 是回調(diào)枚舉函數(shù)的指針。參數(shù) lparam 是用戶定義的值,它被傳遞到枚舉函數(shù)。枚舉函數(shù)應(yīng)該被定義成如下這樣:
BOOL WINAPI Enum16( DWORD dwThreadId,
WORD hMod16,
WORD hTask16,
PSZ pszModName,
PSZ pszFileName,
LPARAM lpUserDefined );
該函數(shù)針對(duì)每個(gè)運(yùn)行在 NTVDM 進(jìn)程中的16位任務(wù)調(diào)用一次,NTVDM 進(jìn)程ID將被傳入 VDMEnumTaskWOWEx()。如果想繼續(xù)枚舉則返回 FALSE,終止枚舉則返回 TRUE。注意這是與 EnumWindows()相對(duì)的。
關(guān)于代碼 本文附帶的代碼例子將 PSAPI 和 ToolHelp32 封裝到一個(gè)名為 EnumProcs() 的函數(shù)中。該函數(shù)的工作原理類似 EnumWindows(),有一個(gè)指向回調(diào)函數(shù)的指針,并要對(duì)該函數(shù)進(jìn)行重復(fù)調(diào)用,針對(duì)系統(tǒng)中的每個(gè)進(jìn)程調(diào)用一次。另一個(gè)參數(shù)是用戶定義的 lParam。下面是該函數(shù)的聲明:
BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );
使用該函數(shù)時(shí),要象下面這樣聲明回調(diào)函數(shù):
BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );
參數(shù) dw 包含 ID,“w16”是16位任務(wù)的任務(wù)號(hào),如果為32位進(jìn)程則為0(在 Windows 95 中總是0),參數(shù)lpstr 指向文件名,lParam 是用戶定義的,要被傳入 EnumProcs()。
EnumProcs() 函數(shù)通過顯示鏈接使用 ToolHelp32 和 PSAPI,而非通常所用的隱式鏈接。之所以要這樣做,主要是為了讓代碼能夠在二進(jìn)制一級(jí)兼容,從可以在所有 Win32 操作系統(tǒng)平臺(tái)上運(yùn)行。