如何“干凈地”終止 Win32 中的應(yīng)用程序
編譯:Northtibet
摘要
32 位進程(和 Windows 95 下的 16 位進程)
16 位問題(在 Windows NT 下)
示例代碼
摘要
在理想環(huán)境中,某一進程可能會通過某種形式的進程間通信要求另一進程關(guān)閉。不過,如果你對希望其關(guān)閉的應(yīng)用程序沒有源代碼級控制權(quán),可能就沒有辦法做這樣的選擇。盡管沒有哪種方法能保證“干凈地”關(guān)閉 Win32 中的應(yīng)用程序,但你可以采取一些步驟來確保應(yīng)用程序使用最佳方法清除資源。
32 位進程(和 Windows 95 下的 16 位進程)
在 Win32 下,操作系統(tǒng)可保證在進程關(guān)閉時清除進程所擁有的資源。但是,這并不意味著進程本身將有機會對磁盤執(zhí)行任何最后的信息刷新或通過遠程連接執(zhí)行任何最后的通信,也不意味著進程的 DLL 將有機會執(zhí)行其 PROCESS_DETACH 代碼。這就是通常最好避免在 Windows 95 和 Windows NT 下終止應(yīng)用程序的原因。
如果你必須關(guān)閉進程,請按照下列步驟操作:
- 向你打算關(guān)閉的進程所擁有的所有頂級窗口發(fā)送一條 WM_CLOSE 消息。許多 Windows 應(yīng)用程序會通過關(guān)閉它自身來響應(yīng)此消息。
注意:控制臺應(yīng)用程序?qū)?WM_CLOSE 的響應(yīng)取決于它是否安裝了控制處理程序。
使用 EnumWindows() 找到目標窗口的句柄。在回調(diào)函數(shù)中,檢查該窗口的進程 ID 是否與要關(guān)閉的進程相匹配。你可以通過調(diào)用 GetWindowThreadProcessId() 來執(zhí)行此操作。確定匹配項后,使用 PostMessage() 或 SendMessageTimeout() 向該窗口發(fā)送 WM_CLOSE 消息。
- 使用 WaitForSingleObject() 等待進程的句柄。確保你使用超時值等待,因為在很多情況下 WM_CLOSE 不會關(guān)閉應(yīng)用程序。記住,應(yīng)使超時值足夠長(通過 WaitForSingleObject() 或 SendMessageTimeout()),以便用戶可以響應(yīng)為了 處理 WM_CLOSE 消息而創(chuàng)建的任何對話框。
- 如果返回值為 WAIT_OBJECT_0,則應(yīng)用程序已干凈地將其自身關(guān)閉。如果返回值為 WAIT_TIMEOUT,則必須使用 TerminateProcess() 關(guān)閉應(yīng)用程序。
注意:如果從 WaitForSingleObject() 得到的返回值不是 WAIT_OBJECT_0 或 WAIT_TIMEOUT,則應(yīng)使用 GetLastError() 找出原因。
通過執(zhí)行上述這些步驟,你便完全有可能干凈地關(guān)閉應(yīng)用程序(無需 IPC 或用戶干預(yù))。
16 位問題(在 Windows NT 下) 上述步驟適用于 Windows 95 下的 16 位應(yīng)用程序,而 Windows NT 下的 16 位應(yīng)用程序與 Windows 95 下的 16 位應(yīng)用程序的工作方式差別非常大。
在 Windows NT 下,所有 16 位應(yīng)用程序都在虛擬 DOS 機 (VDM) 中運行。此 VDM 是作為 Windows NT 下的一個 Win32 進程 (NTVDM) 運行的。NTVDM 進程具有進程 ID。你可以通過 OpenProcess() 獲取該進程的句柄,就像處理其它任何 Win32 進程一樣。不過,在 VDM 中運行的 16 位應(yīng)用程序都沒有進程 ID,因此你無法從 OpenProcess() 獲取進程句柄。VDM 中的每個 16 位應(yīng)用程序都有一個 16 位任務(wù)句柄和一個 32 位執(zhí)行線程。可通過調(diào)用函數(shù) VDMEnumTaskWOWEx() 找到該任務(wù)句柄和線程 ID。有關(guān)這方面的其它信息,請參見:“
如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進程”。
關(guān)閉 Windows NT 下的 16 位應(yīng)用程序的首選和最直接的方法是關(guān)閉整個 NTVDM 進程。你可以通過執(zhí)行前面所描述的步驟來完成此操作。你只需知道 NTVDM 的進程 ID 即可,參考“
如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進程”所講的方法來查找 NTVDM 的進程 ID。此方法的缺點是它會關(guān)閉在該 VDM 中運行的所有 16 位應(yīng)用程序。如果這不是你想要的結(jié)果,則需要采取其它方法。
如果你希望關(guān)閉 NTVDM 進程中的單個 16 位應(yīng)用程序,需要按照下列步驟操作:
- 向該進程所擁有的以及與你要關(guān)閉的 16 位任務(wù)具有相同線程 ID 的所有頂級窗口發(fā)送一條 WM_CLOSE 消息。執(zhí)行此操作最有效的方法是使用 EnumWindows()。在回調(diào)函數(shù)中,檢查窗口的進程 ID 和線程 ID 是否與要關(guān)閉的 16 位任務(wù)相匹配。請記住,該進程 ID 將成為在其中運行 16 位應(yīng)用程序的 NTVDM 的進程 ID。
- 盡管你有線程 ID,但無法等待 16 位進程的終止。因此,你必須等待任意時間長度(以允許干凈關(guān)閉),然后嘗試關(guān)閉應(yīng)用程序。如果應(yīng)用程序已關(guān)閉,則此操作無效。如果應(yīng)用程序尚未關(guān)閉,則它將終止應(yīng)用程序。
- 使用稱為 VDMTerminateTaskWOW() 的函數(shù)終止應(yīng)用程序,該函數(shù)可在 Vdmdbg.dll 中找到。它采用 VDM 的進程 ID 和 16 位任務(wù)的任務(wù)編號。
此方法允許你關(guān)閉 Windows NT 下 VDM 中的單個 16 位應(yīng)用程序。不過,16 位 Windows 以及 VDM 中運行的 WOWExec 都不能有效地清除已終止任務(wù)的資源。如果你要尋找最有可能干凈地終止 Windows NT 下的 16 位應(yīng)用程序的方法,應(yīng)考慮終止整個 VDM 進程。注意:如果你要啟動以后可能會終止的 16 位應(yīng)用程序,請將 CREATE_SEPARATE_WOW_VDM 與 CreateProcess() 結(jié)合使用。
示例代碼
下面的示例代碼使用以下兩個函數(shù)實現(xiàn)上述用于 16 位和 32 位應(yīng)用程序的方法:TerminateApp() 和 Terminate16App()。TerminateApp() 采用一個 32 位進程 ID 和一個超時值(以毫秒為單位)。Terminate16App()。這兩個函數(shù)都使用 DLL 函數(shù)的顯式鏈接,以便它們的二進制文件與 Windows NT 和 Windows 95 都兼容。
//******************
// 頭文件 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 );
//*********************
// 實現(xiàn)代碼 TermApp.cpp
//*********************
#include "TermApp.h"
#include <vdmdbg.h>
typedef struct
{
DWORD dwID ;
DWORD dwThread ;
} TERMINFO ;
// 聲明回調(diào)枚舉函數(shù).
BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam ) ;
BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam ) ;
/*----------------------------------------------------------------
DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )
功能:
關(guān)閉 32-位進程(或 Windows 95 下的 16-位進程)
參數(shù):
dwPID
要關(guān)閉之進程的進程 ID.
dwTimeout
進程關(guān)閉前等待的毫秒時間.
返回值:
TA_FAILED —— 如果關(guān)閉失敗.
TA_SUCCESS_CLEAN —— 如果使用 WM_CLOSE 關(guān)閉了進程.
TA_SUCCESS_KILL —— 如果使用 TerminateProcess() 關(guān)閉了進程.
返回值的定義參見頭文件.
----------------------------------------------------------------*/
DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )
{
HANDLE hProc ;
DWORD dwRet ;
// 如果無法用 PROCESS_TERMINATE 權(quán)限打開進程,那么立即放棄。
hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE,dwPID);
if(hProc == NULL)
{
return TA_FAILED ;
}
// TerminateAppEnum() 將 WM_CLOSE 消息發(fā)到所有其進程ID 與你所提供的進程ID 匹配的窗口.
EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM) dwPID) ;
// 等待處理,如果成功,OK。如果超時,則干掉它.
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 ;
}
/*----------------------------------------------------------------
DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,
WORD w16Task, DWORD dwTimeout )
功能:
關(guān)閉 Win16 應(yīng)用程序.
參數(shù):
dwPID
16-位程序運行其中的 NTVDM 進程 ID.
dwThread
16-位程序中執(zhí)行線程的線程 ID.
w16Task
應(yīng)用程序的 16-位任務(wù)句柄.
dwTimeout
任務(wù)關(guān)閉前等待的毫秒時間.
返回值:
如果成功, 返回 TA_SUCCESS_16
如果不成功, 返回 TA_FAILED.
返回值的定義參見該函數(shù)的頭文件.
注意:
你可以通過 VDMEnumTaskWOW() 或 VDMEnumTaskWOWEx() 函數(shù)獲得 Win16 和線程 ID.
----------------------------------------------------------------*/
DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,
WORD w16Task, DWORD dwTimeout )
{
HINSTANCE hInstLib ;
TERMINFO info ;
// 你必須通過外部鏈接調(diào)用函數(shù),以便代碼在所有 Win32 平臺上都兼容。
BOOL (WINAPI *lpfVDMTerminateTaskWOW)(DWORD dwProcessId,WORD htask) ;
hInstLib = LoadLibraryA( "VDMDBG.DLL" ) ;
if( hInstLib == NULL )
return TA_FAILED ;
// 獲得函數(shù)過程地址.
lpfVDMTerminateTaskWOW = (BOOL (WINAPI *)(DWORD, WORD ))
GetProcAddress( hInstLib, "VDMTerminateTaskWOW" ) ;
if( lpfVDMTerminateTaskWOW == NULL )
{
FreeLibrary( hInstLib ) ;
return TA_FAILED ;
}
// 向所有匹配進程 ID 和線程的窗口發(fā)送 WM_CLOSE 消息.
info.dwID = dwPID ;
info.dwThread = dwThread ;
EnumWindows((WNDENUMPROC)Terminate16AppEnum, (LPARAM) &info) ;
// 等待.
Sleep( dwTimeout ) ;
// 然后終止.
lpfVDMTerminateTaskWOW(dwPID, w16Task) ;
FreeLibrary( hInstLib ) ;
return TA_SUCCESS_16 ;
}
BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam )
{
DWORD dwID ;
GetWindowThreadProcessId(hwnd, &dwID) ;
if(dwID == (DWORD)lParam)
{
PostMessage(hwnd, WM_CLOSE, 0, 0) ;
}
return TRUE ;
}
BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam )
{
DWORD dwID ;
DWORD dwThread ;
TERMINFO *termInfo ;
termInfo = (TERMINFO *)lParam ;
dwThread = GetWindowThreadProcessId(hwnd, &dwID) ;
if(dwID == termInfo->dwID && termInfo->dwThread == dwThread )
{
PostMessage(hwnd, WM_CLOSE, 0, 0) ;
}
return TRUE ;
}