用Visual C++干干净净地清除进程
读者朋友们可能经常会碰到 这样一个问题,想对某些进行操作时,发现这些文件正在被其它程序使用,处于打开状态,而且是被独占打开,这时是没法对文件进行操作的。因此,要想操作这些 文件,必须将打开这些文件的进程清除掉。那么如何干净地清除进程呢?其实,在Windows2000操作系统版本中有一个工具程序叫 tskill.exe,用它就可以清除掉某个程序的进程,在输入"tskill 程序名"后就可以清除其运行实例。但是如何要在代码里实现tskill的 功能该如何做呢?针对这一问题,本实例介绍了在Windows2000下实现的方法。
一、实现方法
在Visual C++编程中,最安全的杀死进程的方法是向运行程序的主窗口发送WM_CLOSE消息,其实现代码如下:
HWND hwnd =this.m_hWnd; // 获得主窗口
PostMessage(hwnd, WM_CLOSE, 0, 0);
发送此消息后,通常应该等待直到进程确实终止,当进程终止时,它发出状态信号,并且 WaitForSingleObject 返回 WAIT_OBJECT_0。如果返回别的值,进程要么挂起了,要么仍然在进行处理。在这种情况下,杀死这个进程的唯一方法是用功能更强大的API函 数:TerminateProcess()。如果想干得漂亮一点,可以在关闭之前向主窗口发送一个WM_QUERYENDSESSION消息,当用户结束 会话(log out)或者调用ExitWindows()函数时,应用程序会收到这个消息,然后准备退出进程,此时一般都会弹出一个确认对话框,告诉用 户:"程序要推出了,如果要保存修改的东西,现在是最佳时机,想保存吗?"有三种选择(Yes/No/Cancel)。此外,发送 WM_QUERYENDSESSION消息可以拒绝推出进程(按下"Cancel键"),如果是这样,进程将会延续。
如果想要关闭的进 程被挂起,使用SendMessageTimeout()函数就非常重要,而不是用SendMessage()函数,其参数 SMTO_NOTIMEOUTIFNOTHUNG是一个只有Windows 2000 和Windows XP才有的标志。其意义是"如果线程没有挂起, 不要超时",换句话说就是如果线程正在进行正常处理,那么永远等待,以便用户能看到对话框并决定做什么,当用户最终做出决定 后,SendMessageTimeout()将带着相应的bOKToKill值返回。
本例为了增强代码的可重用性,将实现细节都封装 在一个叫CFindKillProcess的类中,包括查找和杀死进程,详情请参见EnumProc.h和EnumProc.cpp文件。文件中还有另外 两个可重用类,一个是CProcessIterator,另一个是CWindowIterator。这在实例《获取进程的主窗口以及创建进程的程序名》中 有过详细的叙述。
CfindKillProcess类的成员函数FindProcess()查找某个进程序,如果找到这个进程,它返回 此进程的ID,然后将此ID传给CFindKillProcess::KillProcess()函数,KillProcess()函数封装了关闭窗口以 及终止逻辑,它利用CmainWindowIterator类对象来枚举进程的主窗口(可能不止一个,见"如何获取某个进程的主窗口以及创建进程的程序 名?"),并发送WM_CLOSE到每一个窗口,然后等待进程死亡。它有一个布尔型参数用来指示当应用程序进程不愿意退出时是否执行 TerminateProcess()函数。详细细节请参见下载的代码。
二、编程步骤
1、 启动Visual C++6.0,生成一个控制台应用程序,将该程序命名为"kp";
2、 在程序代码中添加CfindKillProcess、CProcessIterator类的定义;
3、 添加代码,编译运行程序。
三、程序代码
//////////////////////////////////////////////////////
#pragma once
//////////////////
// Process iterator -- iterator over all system processes
// Always skips the first (IDLE) process with PID=0.
class CProcessIterator {
protected:
DWORD* m_pids; // array of procssor IDs
DWORD m_count; // size of array
DWORD m_current; // next array item
public:
CProcessIterator();
~CProcessIterator();
DWORD First();
DWORD Next() {
return m_pids && m_current < m_count ? m_pids[m_current++] : 0;
}
DWORD GetCount() {
return m_count;
}
};
//////////////////
// Handy class to facilitate finding and killing a process by name.
class CFindKillProcess {
public:
CFindKillProcess();
~CFindKillProcess();
DWORD FindProcess(LPCTSTR lpModname, BOOL bAddExe=TRUE);
BOOL KillProcess(DWORD pid, BOOL bZap);
};
////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "EnumProc.h"
// CProcessIterator - Iterates all processes
CProcessIterator::CProcessIterator()
{
m_pids = NULL;
}
CProcessIterator::~CProcessIterator()
{
delete [] m_pids;
}
//////////////////
// Get first process: Call EnumProcesses to init array. Return first one.
DWORD CProcessIterator::First()
{
m_current = (DWORD)-1;
m_count = 0;
DWORD nalloc = 1024;
do {
delete [] m_pids;
m_pids = new DWORD [nalloc];
if (EnumProcesses(m_pids, nalloc*sizeof(DWORD), &m_count)) {
m_count /= sizeof(DWORD);
m_current = 1; // skip IDLE process
}
} while (nalloc <= m_count);
return Next();
}
////////////////////////////////////////////////////////////////
// CFindKillProcess - to find/kill a process by module name.
//
CFindKillProcess::CFindKillProcess()
{}
CFindKillProcess::~CFindKillProcess()
{}
//////////////////
// Search for process whose module name matches parameter.
// Finds "foo" or "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;
}
//////////////////
// Kill a process cleanly: Close main windows and wait.
// bZap=TRUE to force kill.
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; // window doesn't want to die: abort
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
// I've closed the main windows; now wait for process to die.
BOOL bKilled = TRUE;
HANDLE hp=OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE,FALSE,pid);
if (hp) {
if (WaitForSingleObject(hp, 5000) != WAIT_OBJECT_0) {
if (bZap) { // didn't die: force kill it if zap requested
TerminateProcess(hp,0);
} else {
bKilled = FALSE;
}
}
CloseHandle(hp);
}
return bKilled;
}
//////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "EnumProc.h"
#define tpf _tprintf // to save typing
typedef list<string> CStringList; // like MFC, but with STL
// pre-declare functions
int help();
// check for switch: / or -
inline BOOL isswitch(TCHAR c) { return c==L'/' || c==L'-'; }
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
CStringList cmdargs; // command-line args (processes to kill)
BOOL bDisplayOnly=FALSE; // don't kill, just show results
BOOL bQuiet=FALSE; // suppress error messages
BOOL bZap=FALSE; // force-kill process
// Parse command line. Switches can come in any order.
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]); // got a non-switch arg: add to list
}
}
if (cmdargs.size()<=0)
help();
// Now iterate args (module names), killing each one
CStringList::iterator it;
for (it=cmdargs.begin(); it!=cmdargs.end(); it++) {
CFindKillProcess fkp;
DWORD pid = fkp.FindProcess(it->c_str());
if (pid) {
if (bDisplayOnly) {
tpf(_T("Kill process %d(0x%08x)\n"),pid,pid);
} else {
fkp.KillProcess(pid, bZap);
}
} else if (!bQuiet) {
tpf(_T("Error: Can't find process '%s'.\n"),it->c_str());
}
}
return 0;
}
int help()
{
tpf(_T("kp: Kill process from command line.\n"));
tpf(_T(" Copyright 2002 Paul DiLascia.\n\n"));
tpf(_T(" kp [/nqz?] modname1 [modname2....]\n"));
tpf(_T(" where modnameN is a module name; eg foo or foo.exe\n"));
tpf(_T("\n"));
tpf(_T(" /n(othing) don't kill, just show results\n"));
tpf(_T(" /q(uiet) don't show errors\n"));
tpf(_T(" /z(ap) force kill (ignore WM_QUERYENDSESSION)\n"));
tpf(_T("\n"));
return 0;
}
四、小结
本实例通过介绍CfindKillProcess类探讨了在Windows2000下彻底消除进程的方法,虽然该程序只能在Windows2000环境下编译运行,但是该方法对Windows98下进程的控制也是有借鉴意义的。