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

無視窗句柄下按進程名稱終止程式完整實現 (Toolhelp32 API)

     編輯:VC 教學

前言

當需終止某程式但無法獲取其視窗句柄 (如無 UI 後台程式、掛起視窗、權限不足無法 FindWindow) 時,可透過 Toolhelp32 快照遍歷系統進程,匹配可執行檔名稱,再枚舉其主視窗發送 WM_QUIT 或強制 TerminateProcess。本文提供完整、健壯的 C++ 實現。

核心 API 概覽

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);
}

關鍵細節解析

1. 檔名提取邏輯

LPCSTR pszFileName = strrchr(pe32.szExeFile, '\\');
pszFileName = pszFileName ? pszFileName + 1 : pe32.szExeFile;

PROCESSENTRY32.szExeFile 在某些系統版本可能包含完整路徑,某些僅含檔名。上述代碼兼容兩種情況。

2. 優雅退出 vs 強制終止

  • PostMessage(WM_QUIT):發送退出訊息到視窗線程,觸發 GetMessage 返回 0,程式可執行清理代碼 (關閉檔案、釋放資源、存檔)。首選方案。
  • TerminateProcess:立即暴力終止進程,不執行任何清理 (DLL_PROCESS_DETACH 不調用、檔案緩衝區不刷新、關鍵區不釋放)。僅在進程無響應時作為最後手段。

3. 權限考量

  • 枚舉進程/視窗:標準用戶權限即可
  • OpenProcess(PROCESS_TERMINATE):需對目標進程有終止權限 (通常需管理員或同用戶進程)
  • 系統進程 (csrss.exe, services.exe 等) 無法終止,會返回 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;
}

常見問題與避坑指南

Q: 為什麼 KillProgram 找不到明明在運行的進程?
A: 1) 權限不足:以管理員權限運行;2) 進程名稱大小寫:使用 _stricmp 不區分大小寫;3) 32/64 位元不匹配:32 位程式需啟用 Wow64DisableWow64FsRedirection 才能枚舉 64 位進程,或編譯 64 位版本。
Q: WM_QUIT vs WM_CLOSE 哪個更好?
A: WM_CLOSE 讓視窗處理關閉邏輯 (可攔截詢問用戶);WM_QUIT 直接退出訊息迴圈。建議先 WM_CLOSE,超時後 TerminateProcess。
Q: 如何只關閉特定用戶的進程實例?
A: OpenProcess 後用 GetTokenInformation(TokenUser) 比對用戶 SID,或用 WMI Win32_Process.GetOwner。

現代替代方案

  • WMI:SELECT * FROM Win32_Process WHERE Name = 'notepad.exe' → Terminate(),支援遠端、查詢擁有者、命令列參數
  • PowerShell:Stop-Process -Name notepad -Force
  • Taskkill.exe:taskkill /IM notepad.exe /F (系統內建)

但原生 Toolhelp32 方案無依賴、啟動快、適合嵌入自身程式中做自我保護/互斥邏輯。

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