程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 干干淨淨殺死進程

干干淨淨殺死進程

編輯:關於VC++

最近在寫程序時碰到這樣一個問題:我想將文件備份到網絡驅動器上,但是有一些文件正在被其它程序使用,處於打開狀態,而且是被獨占打開,這時是沒法對文件進行備份操作的。因此,要想備份這些文件,必須將打開它們的那些進程kill掉。那麼如何干淨地殺死這些打開文件的進程呢?相信看完本文後,自然會有辦法解決!

其實,在較新的Windows操作系統版本中有一個工具程序叫tskill.exe,用它就可以解決問題。如圖一所示:

圖一 tskill程序

要殺掉某個程序的進程,可以輸入下面的命令便可以殺死其運行實例:

tskill 程序名

但是我想在自己寫的代碼裡實現tskill的功能該如何做呢?最安全的殺死進程的方法是向運行程序的主窗口發送WM_CLOSE消息。

HWND hwnd = // 獲得主窗口
PostMessage(hwnd, WM_CLOSE, 0, 0);
發送此消息後,通常應該等待直到進程確實終止:HANDLE hp = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE,FALSE,pid);
WaitForSingleObject(hp, 5000); // 等待5秒
當進程終止時,它發出狀態信號,並且 WaitForSingleObject 返回WAIT_OBJECT_0。如果返回別的值,進程要麼掛起了,要麼仍然在進行處理。在這種情況下,殺死這個進程的唯一方法是用功能更強大的TerminateProcess:if (WaitForSingleObject(hp, 5000) != WAIT_OBJECT_0)
  TerminateProcess(hp,0);
如果想干得漂亮一點,可以在關閉之前向主窗口發送一個WM_QUERYENDSESSION消息。當用戶結束會話(log out)或者有人調用ExitWindows時,應用程序會收到這個消息。然後准備即將來臨的死亡。此時一般都會彈出一個確認對話框,告訴世人:“我要完蛋了,如果要保存修改的東西,現在是最佳時機,想保存嗎?”有三種選擇(Yes/No/Cancel)。此外,WM_QUERYENDSESSION甚至可以拒絕死亡(按下"Cancel鍵"),如果是這樣,生命將會延續。代碼應該這樣寫:DWORD bOKToKill = FALSE;
SendMessageTimeout(hwnd, WM_QUERYENDSESSION, 0, 0,
  SMTO_ABORTIFHUNG|SMTO_NOTIMEOUTIFNOTHUNG, 100, &bOKToKill);
if (bOKToKill) {
 // 發送WM_CLOSE 並等待
}

如果想要關閉的進程被掛起。使用SendMessageTimeout就非常重要,而不是用SendMessage。SMTO_NOTIMEOUTIFNOTHUNG是一個只有Windows 2000 和Windows XP才有的標志。其意義是“如果線程沒有掛起,不要超時。”換句話說就是:如果線程正在進行正常處理,那麼永遠等待,以便用戶能看到對話框並決定做什麼。當用戶最終做出決定後,SendMessageTimeout將帶著相應的bOKToKill值返回。所有這些的前提是其它的應用程序運行正常並且WM_QUERYENDSESSION也得到正常處理。本文提供了一個小例子程序kp.exe,下面是關鍵代碼:

/////////////////////////////////////////////
kp.exe程序代碼

////////////////////////////////////////////
#include "stdafx.h"
#include "EnumProc.h"
typedef list<string> CStringList;     // 使用STL,與MFC的類似
inline BOOL isswitch(TCHAR c) { return c==L''''''''/'''''''' || c==L''''''''-''''''''; }
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
  CStringList cmdargs;       // 命令行參數 (要殺死的進程,可以多個)
  BOOL bDisplayOnly=FALSE;   // 不殺,只是顯示
  BOOL bQuiet=FALSE;      // 禁止出錯信息
  BOOL bZap=FALSE;      // 強行殺死
  // 解析命令行開關,開關順序可以任意
  for (int i=1; i<argc; i++) {
    if (isswitch(argv[i][0])) {
      for (UINT j=1; j<strlen(argv[i]); j++) {
        switch(tolower(argv[i][j])) {
        case ''''''''?'''''''':  help();  return 0;
        case ''''''''n'''''''':  bDisplayOnly=TRUE; break;
        case ''''''''q'''''''':  bQuiet=TRUE;    break;
        case ''''''''z'''''''':  bZap=TRUE;     break;
        default:
          return help();
        }
      }
    } else {
      cmdargs.push_back(argv[i]);   // 如果是非開關參數,添加到列表
    }
  }
  if (cmdargs.size()<=0)
    help();
  // 遍歷參數(模塊名),一個一個干掉
  CStringList::iterator it;
  for (it=cmdargs.begin(); it!=cmdargs.end(); it++) {
    CFindKillProcess fkp;
    DWORD pid = fkp.FindProcess(it->c_str());
    if (pid) {
      if (bDisplayOnly) {
        _tprintf(_T("Kill process %d(0x%08x)\n"),pid,pid);
      } else {
        fkp.KillProcess(pid, bZap);
      }
    } else if (!bQuiet) {
      _tprintf(_T("錯誤: 找不到進程 ''''''''%s''''''''.\n"),it->c_str());
    }
  }
  return 0;
}

這個程序的功能與tskill類似,用它也可以殺死進程。為了增強代碼的可重用性,將實現細節都封裝在一個叫CFindKillProcess的類中,包括查找和殺死進程,詳情請參見EnumProc.h和EnumProc.cpp文件。文件中還有另外兩個可重用類,一個是CProcessIterator,另一個是CWindowIterator。這在以前的文章中有過詳細描述。見“如何獲取某個進程的主窗口以及創建進程的程序名?”

///////////////////////////////////////////////////////////////
EnumProc.h和EnumProc.cpp
//////////////////////////////////////////////////////////////
EnumProc.h
//////////////////////////////////////////////////////////////////////////////////////////////
// 下面是CFindKillProcess的定義和實現,這是一個小巧工具類,專門用來通過某個進程的名字查找並殺死它。
//
class CFindKillProcess {
public:
  CFindKillProcess();
  ~CFindKillProcess();
  DWORD FindProcess(LPCTSTR lpModname, BOOL bAddExe=TRUE);
  BOOL KillProcess(DWORD pid, BOOL bZap);
};
EnumProc.cpp
#include "stdafx.h"
#include "EnumProc.h"
……
//實現CProcessIterator
…….
//實現CWindowIterator
////////////////////////////////////////////////////////////////
// 實現CFindKillProcess - 用模塊名查找並殺死進程。
//
CFindKillProcess::CFindKillProcess()
{
}
CFindKillProcess::~CFindKillProcess()
{
}
//////////////////
// 搜索與參數匹配的模塊名,模塊名可以是foo或者foo.exe
//
DWORD CFindKillProcess::FindProcess(LPCTSTR modname, BOOL bAddExe)
{
  CProcessIterator itp;
  for (DWORD pid=itp.First(); pid; pid=itp.Next()) {
    TCHAR name[_MAX_PATH];
    CProcessModuleIterator itm(pid);
    HMODULE hModule = itm.First(); // .EXE
    if (hModule) {
      GetModuleBaseName(itm.GetProcessHandle(),
        hModule, name, _MAX_PATH);
      string sModName = modname;
      if (strcmpi(sModName.c_str(),name)==0)
        return pid;
      sModName += ".exe";
      if (bAddExe && strcmpi(sModName.c_str(),name)==0)
        return pid;
    }
  }
  return 0;
}
//////////////////
// 干淨地殺死進程:關閉窗口後等待。
// bZap=TRUE :強行殺死
//
BOOL CFindKillProcess::KillProcess(DWORD pid, BOOL bZap)
{
  CMainWindowIterator itw(pid);
  for (HWND hwnd=itw.First(); hwnd; hwnd=itw.Next()) {
    DWORD bOKToKill = FALSE;
    SendMessageTimeout(hwnd, WM_QUERYENDSESSION, 0, 0,
      SMTO_ABORTIFHUNG|SMTO_NOTIMEOUTIFNOTHUNG, 100, &bOKToKill);
    if (!bOKToKill)
      return FALSE; // 不願意死亡,取消行動
    PostMessage(hwnd, WM_CLOSE, 0, 0);
  }
  // 我已經關閉了全部主窗口,現在等待進程死亡。
  BOOL bKilled = TRUE;
  HANDLE hp=OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE,FALSE,pid);
  if (hp) {
    if (WaitForSingleObject(hp, 5000) != WAIT_OBJECT_0) {
      if (bZap) { // 不願死,那不行,你必須死
        TerminateProcess(hp,0);
      } else {
        bKilled = FALSE;
      }
    }
    CloseHandle(hp);
  }
  return bKilled;
}

CFindKillProcess的使用方法如下:

CFindKillProcess fkp;
DWORD pid = fkp.FindProcess("outlook.exe");
fkp.KillProcess(pid);
FindKillProcess用 CProcessIterator 和 CProcessModuleIterator類來枚舉所有進程,查找第一個與名字匹配的模塊(即啟動進程的exe文件):CProcessIterator itp;
for (DWORD pid=itp.First(); pid; pid=itp.Next()) {
 CProcessModuleIterator itm(pid);
 HMODULE hModule = itm.First(); // .EXE
 if (/* 模塊名 = 正在查找的模塊名*/) {
  return pid;
 }
}

FindProcess查找foo或者foo.exe。如果找到這個進程,它返回此進程的ID,然後你將它傳給CFindKillProcess::KillProcess。KillProcess封裝了關閉窗口以及終止邏輯。它利用CMainWindowIterator來枚舉進程的主窗口(可能不止一個,見“如何獲取某個進程的主窗口以及創建進程的程序名?”),並發送WM_CLOSE到每一個窗口,然後等待進程死亡。它有一個布爾型參數用來指示當應用程序不願意死亡時是否執行TerminateProcess。詳細細節請參見下載的代碼。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved