首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

優雅關閉進程:從 WM_CLOSE 到 TerminateProcess 的完整生命週期管理

     編輯:關於VC++

備份檔案時發現檔案被獨佔鎖定,需先關閉持有進程。暴力 TerminateProcess 會導致資料遺失、資源洩漏。本文建立「禮貌關閉 → 等待 → 強制結束」三階段標準流程,並封裝為可重用 CProcessKiller 類別,支援多進程批次關閉、命令列工具 kp.exe 替代 tskill。

標準關閉流程

  1. 禮貌請求:對每個主視窗發送 WM_QUERYENDSESSION (模擬登出/關機),讓應用程式彈出「儲存變更?」對話框。使用 SendMessageTimeout + SMTO_NOTIMEOUTIFNOTHUNG 避免卡死。
  2. 正常關閉:若同意關閉,發送 WM_CLOSE 進入標準關閉流程 (儲存設定、釋放資源、PostQuitMessage)。
  3. 等待退出:OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE) + WaitForSingleObject(hProcess, 5000)。返回 WAIT_OBJECT_0 表示乾淨退出。
  4. 強制終結:超時或拒絕關閉 → 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、錯誤代碼對照、日誌、單元測試) 請參考文章附件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved