備份檔案時發現檔案被獨佔鎖定,需先關閉持有進程。暴力 TerminateProcess 會導致資料遺失、資源洩漏。本文建立「禮貌關閉 → 等待 → 強制結束」三階段標準流程,並封裝為可重用 CProcessKiller 類別,支援多進程批次關閉、命令列工具 kp.exe 替代 tskill。
WM_QUERYENDSESSION (模擬登出/關機),讓應用程式彈出「儲存變更?」對話框。使用 SendMessageTimeout + SMTO_NOTIMEOUTIFNOTHUNG 避免卡死。WM_CLOSE 進入標準關閉流程 (儲存設定、釋放資源、PostQuitMessage)。OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE) + WaitForSingleObject(hProcess, 5000)。返回 WAIT_OBJECT_0 表示乾淨退出。TerminateProcess(hProcess, 0) (最後手段)。CProcessKiller
class CProcessKiller {
struct ToolhelpSnapshot { /* 封裝 CreateToolhelp32Snapshot 列舉進程/模組/執行緒 */ };
public:
// 依模組名 (不含/含 .exe) 找第一個符合的 PID
DWORD FindProcess(LPCTSTR pszModule, BOOL bAddExe = TRUE) {
ToolhelpSnapshot snap(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = {sizeof(pe)};
for (BOOL ok = Process32First(snap, &pe); ok; ok = Process32Next(snap, &pe)) {
if (_tcsicmp(pe.szExeFile, pszModule) == 0 ||
(bAddExe && _tcsicmp(pe.szExeFile, (CString(pszModule) + _T(".exe")).GetString()) == 0))
return pe.th32ProcessID;
}
return 0;
}
// 列舉進程所有主視窗 (可見、無擁有者、WS_EX_APPWINDOW)
std::vector EnumMainWindows(DWORD pid) {
std::vector result;
ToolhelpSnapshot snap(TH32CS_SNAPTHREAD);
THREADENTRY32 te = {sizeof(te)};
for (BOOL ok = Thread32First(snap, &te); ok; ok = Thread32Next(snap, &te)) {
if (te.th32OwnerProcessID == pid) {
EnumThreadWindows(te.th32ThreadID, [](HWND hwnd, LPARAM lp) {
auto& vec = *reinterpret_cast*>(lp);
if (IsMainWindow(hwnd)) vec.push_back(hwnd);
return TRUE;
}, reinterpret_cast(&result));
}
}
return result;
}
// 核心關閉邏輯
BOOL KillProcess(DWORD pid, BOOL bForce = FALSE) {
auto windows = EnumMainWindows(pid);
for (HWND hwnd : windows) {
DWORD agreed = FALSE;
SendMessageTimeout(hwnd, WM_QUERYENDSESSION, 0, 0,
SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 100, &agreed);
if (!agreed) return FALSE; // 使用者取消
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
HANDLE hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pid);
if (!hProc) return FALSE;
DWORD wait = WaitForSingleObject(hProc, 5000);
if (wait != WAIT_OBJECT_0) {
if (bForce) TerminateProcess(hProc, 0);
else { CloseHandle(hProc); return FALSE; }
}
CloseHandle(hProc);
return TRUE;
}
private:
static BOOL IsMainWindow(HWND hwnd) {
return IsWindowVisible(hwnd) && GetWindow(hwnd, GW_OWNER) == nullptr &&
(GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_APPWINDOW);
}
};
kp.exe (替代 tskill)
int _tmain(int argc, TCHAR* argv[]) {
CProcessKiller killer;
bool bDisplayOnly = false, bQuiet = false, bForce = false;
std::vector targets;
// 解析參數: -n 只顯示PID, -q 靜默, -z 強制, 其餘為模組名
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == _T('-') || argv[i][0] == _T('/')) {
for (int j = 1; argv[i][j]; ++j) {
switch (_totlower(argv[i][j])) {
case _T('n'): bDisplayOnly = true; break;
case _T('q'): bQuiet = true; break;
case _T('z'): bForce = true; break;
case _T('?'): Usage(); return 0;
}
}
} else targets.push_back(argv[i]);
}
for (auto& mod : targets) {
DWORD pid = killer.FindProcess(mod);
if (pid) {
if (bDisplayOnly) _tprintf(_T("PID: %u (0x%08X)\n"), pid, pid);
else if (!killer.KillProcess(pid, bForce) && !bQuiet)
_tprintf(_T("Failed to kill %s (PID %u)\n"), mod, pid);
} else if (!bQuiet) {
_tprintf(_T("Process not found: %s\n"), mod);
}
}
return 0;
}
kp.exe notepad.exe # 禮貌關閉所有記事本 kp.exe -z chrome.exe # 強制殺死 Chrome (含所有分頁進程) kp.exe -n outlook.exe # 只顯示 Outlook PID kp.exe -q -z winword.exe powerpnt.exe # 靜默強制關閉 Word 和 PowerPoint
| 議題 | 處理策略 |
|---|---|
| 關鍵系統進程 (csrss, winlogon, services) | 白名單過濾,拒絕關閉;FindProcess 排除 PID < 4 或關鍵名稱 |
| 多使用者 Session 隔離 | OpenProcess 需 PROCESS_QUERY_LIMITED_INFORMATION + SeDebugPrivilege 跨 Session |
| Console 應用程式無主視窗 | EnumMainWindows 為空 → 直接 WM_CLOSE 無效 → 改用 GenerateConsoleCtrlEvent(CTRL_CLOSE_EVENT, pid) 或 AttachConsole + SetConsoleCtrlHandler |
| 掛起進程 (Not Responding) | SMTO_ABORTIFHUNG 讓 SendMessageTimeout 立即返回 → 直接進入強制終結 |
| 子進程殘留 | 遞迴列舉子進程 (Toolhelp 父 PID 比對) 先關閉子進程 |
| 工具 | 禮貌關閉 | 等待退出 | 強制選項 | 跨 Session | 依賴 |
|---|---|---|---|---|---|
| tskill.exe | ✗ | ✗ | ✓ (/A) | ✓ | 系統內建 |
| taskkill.exe | ✓ (預設) | ✓ (/T 等待) | ✓ (/F) | ✓ | 系統內建 |
| 本工具 kp.exe | ✓ (WM_QUERYENDSESSION) | ✓ (5秒可配置) | ✓ (-z) | 需特權 | 單一 EXE, 可嵌入 |
「優雅關閉」不僅是禮貌,更是資料完整性的保障。CProcessKiller 將「列舉主視窗 → WM_QUERYENDSESSION → WM_CLOSE → Wait → Terminate」標準化封裝,可直接整合至備份軟體、安裝程式、自動化測試框架、系統管理腳本。完整原始碼 (含 Unicode、錯誤代碼對照、日誌、單元測試) 請參考文章附件。