在理想的軟體架構中,進程間通訊(IPC)機制允許一個進程禮貌地請求另一個進程關閉。然而,現實往往不那麼美好:當你無法控制目標應用程式的原始碼時,強制關閉就成為唯一選項。雖然沒有任何方法能 100% 保證「乾淨」地終止 Win32 應用程式,但遵循正確的步驟能最大程度確保資源被妥善釋放。
Win32 系統保證進程終止時會清理其擁有的核心資源(如記憶體、控制代碼等)。但這不代表進程有機會執行使用者層面的收尾工作:將緩衝區資料寫入磁碟、完成網路通訊的四次揮手、或是讓載入的 DLL 執行 PROCESS_DETACH 程式碼。這正是為什麼在 Windows 95 和 Windows NT 環境下,除非萬不得已,應盡量避免直接終止應用程式。
若必須終止進程,請嚴格遵循以下步驟:
WM_CLOSE 訊息:向目標進程擁有的所有頂層視窗發送 WM_CLOSE。大多數設計良好的 Windows 應用程式會響應此訊息並啟動關閉流程。EnumWindows() 列舉視窗,在回呼函式中透過 GetWindowThreadProcessId() 比對進程 ID。確認匹配後,使用 PostMessage() 或 SendMessageTimeout() 發送 WM_CLOSE。WaitForSingleObject() 等待進程控制代碼訊號。**務必設定超時值**,因為 WM_CLOSE 可能被使用者取消(例如彈出「是否儲存」對話框)。超時時間需足夠長,以便使用者回應。WAIT_OBJECT_0:應用程式已乾淨關閉。WAIT_TIMEOUT:必須呼叫 TerminateProcess() 強制終止。GetLastError() 診斷錯誤原因。此流程無需 IPC 或使用者干預,即可最大程度實現乾淨關閉。
Windows NT 上的 16 位應用程式運行在虛擬 DOS 機器(VDM)中,對應單一 Win32 進程 NTVDM.exe。所有 16 位任務共享此進程 ID,無獨立進程控制代碼,僅有 16 位任務控制代碼與 32 位執行緒 ID。
直接對 NTVDM 進程套用上述 32 位終止流程。缺點是會連帶關閉該 VDM 中**所有** 16 位應用程式。
EnumWindows() 找出屬於目標 NTVDM 進程 ID **且**執行緒 ID 匹配的頂層視窗,發送 WM_CLOSE。Sleep() 適當時間讓其嘗試乾淨關閉。VDMDBG.DLL 中的 VDMTerminateTaskWOW(),傳入 VDM 進程 ID 與 16 位任務控制代碼強制終止。警告:16 位 Windows 及 WOWExec 對被終止任務的資源清理極不徹底。若追求最乾淨的終止,終止整個 VDM 進程反而更佳。若預期未來需終止某 16 位應用,啟動時應加上 CREATE_SEPARATE_WOW_VDM 旗標使其獨立運行。
以下提供兩個核心函式 TerminateApp()(32 位/Win95 16 位)與 Terminate16App()(NT 16 位),採動態連結 DLL 函式確保跨平台相容性。
TermApp.h#include <windows.h>
#define TA_FAILED 0
#define TA_SUCCESS_CLEAN 1
#define TA_SUCCESS_KILL 2
#define TA_SUCCESS_16 3
DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout );
DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread, WORD w16Task, DWORD dwTimeout );
TermApp.cpp 關鍵邏輯// 32 位進程終止
DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )
{
HANDLE hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, dwPID);
if (!hProc) return TA_FAILED;
EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM)dwPID); // 發送 WM_CLOSE
DWORD dwRet;
if (WaitForSingleObject(hProc, dwTimeout) != WAIT_OBJECT_0)
dwRet = TerminateProcess(hProc, 0) ? TA_SUCCESS_KILL : TA_FAILED;
else
dwRet = TA_SUCCESS_CLEAN;
CloseHandle(hProc);
return dwRet;
}
// 回呼:向匹配 PID 的視窗發送 WM_CLOSE
BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam )
{
DWORD dwID;
GetWindowThreadProcessId(hwnd, &dwID);
if (dwID == (DWORD)lParam)
PostMessage(hwnd, WM_CLOSE, 0, 0);
return TRUE;
}
// NT 16 位任務終止
DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread, WORD w16Task, DWORD dwTimeout )
{
HINSTANCE hInstLib = LoadLibraryA("VDMDBG.DLL");
if (!hInstLib) return TA_FAILED;
BOOL (WINAPI *lpfVDMTerminateTaskWOW)(DWORD, WORD) =
(BOOL (WINAPI *)(DWORD, WORD))GetProcAddress(hInstLib, "VDMTerminateTaskWOW");
if (!lpfVDMTerminateTaskWOW) { FreeLibrary(hInstLib); return TA_FAILED; }
TERMINFO info = { dwPID, dwThread };
EnumWindows((WNDENUMPROC)Terminate16AppEnum, (LPARAM)&info); // 發送 WM_CLOSE
Sleep(dwTimeout); // 等待乾淨關閉
lpfVDMTerminateTaskWOW(dwPID, w16Task); // 強制終止
FreeLibrary(hInstLib);
return TA_SUCCESS_16;
}
// 回呼:匹配 PID 與 Thread ID
BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam )
{
TERMINFO *termInfo = (TERMINFO *)lParam;
DWORD dwID, dwThread = GetWindowThreadProcessId(hwnd, &dwID);
if (dwID == termInfo->dwID && termInfo->dwThread == dwThread)
PostMessage(hwnd, WM_CLOSE, 0, 0);
return TRUE;
}
此實作涵蓋從視窗列舉、訊息發送、同步等待到最終強制終止的完整生命週期,是處理 Win32 應用程式終止的經典參考方案。