在 Windows 系統程式開發中,獲取當前系統運行的進程資訊是基礎且關鍵的需求。本文將詳細介紹兩種主流的 C 語言實現方案:基於 Toolhelp32 API 的快照遍歷法,以及基於 PSAPI 的 EnumProcesses 介面。文中包含完整、可編譯的程式碼範例,並解析關鍵步驟與常見坑點。
CreateToolhelp32Snapshot 能將當前系統所有進程的狀態「拍照」成一個快照,隨後配合 Process32First/Process32Next 即可逐一枚舉。此法不需額外連結庫,標頭檔 <tlhelp32.h> 即可使用,適合輕量級工具。
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
int main() {
PROCESSENTRY32 pe32 = {0};
pe32.dwSize = sizeof(pe32);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) {
printf("CreateToolhelp32Snapshot 失敗,錯誤碼:%lu\n", GetLastError());
return 1;
}
printf("%-20s %10s\n", "進程名", "PID");
printf("==============================\n");
BOOL bMore = Process32First(hSnap, &pe32);
int count = 0;
while (bMore) {
printf("%-20s %10u\n", pe32.szExeFile, pe32.th32ProcessID);
++count;
bMore = Process32Next(hSnap, &pe32);
}
printf("\n共枚舉到 %d 個進程。\n", count);
CloseHandle(hSnap);
return 0;
}
pe32.dwSize = sizeof(pe32) 必須在呼叫 Process32First 前完成,否則 API 會拒絕服務。INVALID_HANDLE_VALUE 並呼叫 GetLastError() 取得詳細錯誤碼。CloseHandle(hSnap),避免句柄洩漏。EnumProcesses 直接回傳所有進程 ID 陣列,再以 OpenProcess 取得句柄,配合 EnumProcessModules/GetModuleFileNameEx 可獲取完整路徑。此法需連結 psapi.lib(或透過 #pragma comment(lib, "psapi.lib"))。
#include <windows.h>
#include <psapi.h>
#include <stdio.h>
#pragma comment(lib, "psapi.lib")
BOOL EnableDebugPrivilege() {
HANDLE hToken;
TOKEN_PRIVILEGES tp = {1, {{0}, SE_PRIVILEGE_ENABLED}};
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
CloseHandle(hToken);
return ok;
}
int main() {
EnableDebugPrivilege(); // 取得查詢系統進程所需權限
DWORD pids[1024], cbNeeded, count;
if (!EnumProcesses(pids, sizeof(pids), &cbNeeded)) {
printf("EnumProcesses 失敗:%lu\n", GetLastError());
return 1;
}
count = cbNeeded / sizeof(DWORD);
for (DWORD i = 0; i < count; ++i) {
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pids[i]);
if (!hProc) {
printf("PID %5u : 無法開啟(權限不足)\n", pids[i]);
continue;
}
HMODULE hMod;
DWORD cbMod;
char path[MAX_PATH] = {0};
if (EnumProcessModules(hProc, &hMod, sizeof(hMod), &cbMod))
GetModuleFileNameExA(hProc, hMod, path, MAX_PATH);
printf("PID %5u : %s\n", pids[i], path[0] ? path : "(無法取得路徑)");
CloseHandle(hProc);
}
return 0;
}
系統進程(如 csrss.exe、services.exe)預設拒絕一般權限的 OpenProcess。範例中的 EnableDebugPrivilege() 會為當前執行緒加上 SE_DEBUG_NAME 特權,讓後續呼叫能穿透大多數防護。實務上建議僅在必要時啟用,用畢即以 AdjustTokenPrivileges 關閉。
若需列出某進程載入的所有模組,可使用 CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid) 搭配 Module32First/Module32Next。以下為簡化版封裝函式:
int ListModules(DWORD pid) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
if (hSnap == INVALID_HANDLE_VALUE) return 1;
MODULEENTRY32 me = {0};
me.dwSize = sizeof(me);
if (Module32First(hSnap, &me)) {
do {
printf("%-30s %s\n", me.szModule, me.szExePath);
} while (Module32Next(hSnap, &me));
}
CloseHandle(hSnap);
return 0;
}
| API | 適用場景 | 特性 |
|---|---|---|
ExitProcess | 當前進程正常退出 | 會執行 DLL 的 DLL_PROCESS_DETACH、釋放資源、刷新 I/O 緩衝區 |
TerminateProcess | 強制結束任意進程(需權限) | 立即終止,**不**執行清理程式碼,可能導致資料遺失或資源洩漏 |
編寫管理工具時,優先嘗試發送 WM_CLOSE 或呼叫 ExitProcess(若為自身);唯有進程無回應時才退而求其次使用 TerminateProcess。
掌握這兩套機制,即可在 C/C++ 中靈活實作進程監控、診斷與管理功能,為後續開發系統級工具打下堅實基礎。