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