當需終止某程式但無法獲取其視窗句柄 (如無 UI 後台程式、掛起視窗、權限不足無法 FindWindow) 時,可透過 Toolhelp32 快照遍歷系統進程,匹配可執行檔名稱,再枚舉其主視窗發送 WM_QUIT 或強制 TerminateProcess。本文提供完整、健壯的 C++ 實現。
| API | 用途 |
|---|---|
CreateToolhelp32Snapshot | 建立系統進程快照 (TH32CS_SNAPPROCESS) |
Process32First / Process32Next | 遍歷快照中的 PROCESSENTRY32 |
EnumWindows | 枚舉頂層視窗,配合回調獲取進程 ID 對應視窗 |
GetWindowThreadProcessId | 獲取視窗所屬進程 ID |
PostMessage(WM_QUIT) | 優雅請求視窗線程退出訊息迴圈 |
OpenProcess / TerminateProcess | 強制終止進程 (最後手段,需 PROCESS_TERMINATE 權限) |
#include
#include
#include
#include
// 視窗枚舉回調:尋找屬於目標進程的視窗並發送 WM_QUIT
BOOL CALLBACK EnumWinProc(HWND hwnd, LPARAM lParam) {
DWORD dwProcessId;
GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwProcessId == (DWORD)lParam) {
// 發送 WM_QUIT 讓視窗線程自然退出
PostMessage(hwnd, WM_QUIT, 0, 0);
return FALSE; // 停止枚舉
}
return TRUE; // 繼續枚舉
}
// 主函數:按 EXE 檔名終止程式 (不含路徑)
void KillProgram(LPCSTR pszExeName) {
if (!pszExeName || !*pszExeName) return;
// 1. 建立進程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
// 2. 遍歷所有進程
if (Process32First(hSnapshot, &pe32)) {
do {
// 僅比對檔名部分 (去除路徑)
LPCSTR pszFileName = strrchr(pe32.szExeFile, '\\');
pszFileName = pszFileName ? pszFileName + 1 : pe32.szExeFile;
// 不區分大小寫比對
if (_stricmp(pszFileName, pszExeName) == 0) {
// 3. 找到匹配進程,枚舉其視窗發送 WM_QUIT
EnumWindows(EnumWinProc, pe32.th32ProcessID);
// 可選:若需強制終止 (視窗無響應時)
// HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
// if (hProc) { TerminateProcess(hProc, 1); CloseHandle(hProc); }
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
LPCSTR pszFileName = strrchr(pe32.szExeFile, '\\');
pszFileName = pszFileName ? pszFileName + 1 : pe32.szExeFile;
PROCESSENTRY32.szExeFile 在某些系統版本可能包含完整路徑,某些僅含檔名。上述代碼兼容兩種情況。
GetMessage 返回 0,程式可執行清理代碼 (關閉檔案、釋放資源、存檔)。首選方案。OpenProcess(PROCESS_TERMINATE):需對目標進程有終止權限 (通常需管理員或同用戶進程)ERROR_ACCESS_DENIED
bool KillProcessByName(LPCTSTR pszExeName, DWORD dwTimeoutMs = 5000, bool bForceKill = true) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) return false;
PROCESSENTRY32 pe = { sizeof(pe) };
bool bFound = false;
if (Process32First(hSnap, &pe)) {
do {
LPCSTR pszName = strrchr(pe.szExeFile, '\\');
pszName = pszName ? pszName + 1 : pe.szExeFile;
if (_stricmp(pszName, pszExeName) == 0) {
bFound = true;
// 嘗試優雅退出
EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
DWORD pid; GetWindowThreadProcessId(hwnd, &pid);
if (pid == (DWORD)lParam) { PostMessage(hwnd, WM_CLOSE, 0, 0); return FALSE; }
return TRUE;
}, pe.th32ProcessID);
// 等待進程自然退出
HANDLE hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
if (hProc) {
DWORD dwWait = WaitForSingleObject(hProc, dwTimeoutMs);
if (dwWait == WAIT_TIMEOUT && bForceKill) {
TerminateProcess(hProc, 2); // 強制終止
}
CloseHandle(hProc);
}
}
} while (Process32Next(hSnap, &pe));
}
CloseHandle(hSnap);
return bFound;
}
_stricmp 不區分大小寫;3) 32/64 位元不匹配:32 位程式需啟用 Wow64DisableWow64FsRedirection 才能枚舉 64 位進程,或編譯 64 位版本。WM_CLOSE 讓視窗處理關閉邏輯 (可攔截詢問用戶);WM_QUIT 直接退出訊息迴圈。建議先 WM_CLOSE,超時後 TerminateProcess。OpenProcess 後用 GetTokenInformation(TokenUser) 比對用戶 SID,或用 WMI Win32_Process.GetOwner。SELECT * FROM Win32_Process WHERE Name = 'notepad.exe' → Terminate(),支援遠端、查詢擁有者、命令列參數Stop-Process -Name notepad -Forcetaskkill /IM notepad.exe /F (系統內建)但原生 Toolhelp32 方案無依賴、啟動快、適合嵌入自身程式中做自我保護/互斥邏輯。