在 Windows 2000 及以上作業系統中,透過工作管理器或 Windows XP 的 Tasklist 命令,我們可以輕鬆查看所有正在執行的進程。但這些工具背後的實現原理是什麼?秉持著侯捷大師在《深入淺出 MFC》中提倡的「知其然更要知其所以然」精神,本文將帶您親手實作四種枚舉系統進程的經典方案,所有程式碼均在 Windows XP SP1 + VC6.0 SP6 環境下編譯通過。
這是最廣為人知的方法,核心在於三個 API 函數:CreateToolhelp32Snapshot()、Process32First() 與 Process32Next()。
HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
BOOL WINAPI Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 建立系統進程快照句柄。Process32First() 取得第一筆進程資訊。while 迴圈中反覆呼叫 Process32Next() 遍歷所有進程,並可輔以 CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ...) 取得模組完整路徑。筆者曾反編譯 Sysinternals 的 Pslist.exe,發現其核心邏輯正是基於此法。MSDN 亦提供功能更完善的範例程式碼供參考。
雖然 MSDN 提供了 EnumProcesses 與 EnumProcessModules 的範例,但 VC6.0 自帶 SDK 卻缺少 psapi.h 與對應的導入庫。解決之道在於動態載入 Psapi.dll 並手動獲取函數入口位址。
typedef 定義函數指標,並指定 _stdcall 呼叫慣例。LoadLibrary 載入 Psapi.dll,再用 GetProcAddress 取得 EnumProcesses、EnumProcessModules、GetModuleFileNameEx 等函數位址。WMI 提供了統一的系統管理介面,透過 IWbemLocator、IWbemServices 等 COM 介面,可查詢 Win32_Process 類別,獲得豐富的進程屬性(命令列、啟動時間、擁有者等)。雖然程式碼較繁瑣,但功能最強大,適合管理工具開發。
直接呼叫未文件化的 NtQuerySystemInformation(SystemProcessInformation, ...) 可一次性取得所有進程資訊,效能最高,但屬非公開 API,版本相容性風險較大,建議僅用於特殊需求場景。
| 方法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| ToolHelp32 | API 穩定、文件完整、易上手 | 需建立快照,效能稍遜 | 一般應用程式、診斷工具 |
| PSAPI 動態載入 | 不依賴 SDK 版本、可獲取模組詳細資訊 | 程式碼較繁瑣 | 需相容舊版 VC 環境的專案 |
| WMI | 資訊最豐富、支援遠端查詢 | 啟動較慢、COM 程式碼複雜 | 企業級管理代理、資產掃描 |
| NtQuerySystemInformation | 效能極佳、一次取得所有資料 | 非公開 API、有相容風險 | 高頻輪詢、核心模組開發 |
實務上建議優先使用 ToolHelp32 或 PSAPI;若需豐富中繼資料則選 WMI;極致效能場景才考慮 Native API。掌握多種手法,才能在不同專案約束下游刃有餘。