一、前言
进程是计算机中执行的程序,是向操作系统申请资源的基本单位。我们执行一个程序。那么就会对应地创建一个甚至多个进程,关闭程序时。进程也就结束了。查看进程最经常使用的手段是按下Ctrl+Shift+Delete打开Windows自带的任务管理器,或者使用老牌强力软件“冰刃”。又或者是使用由微软推出的更为强大的Process Monitor,都能基本得到同样的效果。不同的是。强大的进程查看软件能够查看到系统的隐藏进程,而一般的仅仅能查看应用层的进程。而我在这两篇文章中所讨论的就是怎样实现一个简易的进程管理器,通过它能够管理当前的进程,也能够管理进程所载入的DLL。这篇文章主要讨论的就是进程管理方面的编程,下一篇再讨论DLL管理方面的程序编写。
二、界面设计
本程序须要设计两个界面。这篇文章仅仅讨论第一个界面的制作。这里须要一个“List Control”和三个“Button”控件:
图1 主界面的设计
然后设置“List Control”的控件属性。在“Sytles”中的“View”中。选择“Report”,再选中“Single Selection”选项。
然后为其加入一个名为“m_ProcessList”的变量,然后通过编程进行初始化:
void CProcessManageDlg::InitProcessList()
{
//设置“List Control”控件的扩展风格
m_ProcessList.SetExtendedStyle(
m_ProcessList.GetExtendedStyle()
| LVS_EX_GRIDLINES //有网络格
| LVS_EX_FULLROWSELECT); //选中某行使整行高亮(仅仅适用于report风格)
//加入列目
m_ProcessList.InsertColumn(0, _T("序号"));
m_ProcessList.InsertColumn(1, _T("进程名称 "));
m_ProcessList.InsertColumn(2, _T("PID值"));
m_ProcessList.InsertColumn(3, _T("线程数"));
m_ProcessList.InsertColumn(4, _T("父进程ID"));
m_ProcessList.InsertColumn(5, _T("线程优先级"));
//设置列的宽度
m_ProcessList.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);
m_ProcessList.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
m_ProcessList.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);
m_ProcessList.SetColumnWidth(3, LVSCW_AUTOSIZE_USEHEADER);
m_ProcessList.SetColumnWidth(4, LVSCW_AUTOSIZE_USEHEADER);
m_ProcessList.SetColumnWidth(5, LVSCW_AUTOSIZE_USEHEADER);
}
之后在CProcessManageDlg::OnInitDialog()中加入:InitProcessList();
以实现初始化,再在头文件里声明:void InitProcessList();
三、进程的枚举
进程的枚举就是把全部的进程显示出来,而有一些特意隐藏的进程是无法通过常规的枚举方式枚举到的。这里所解说的是应用层的进程枚举。
为实现此功能。这里使用的是CreateToolhelp32Snapshot()。它的作用是对当前系统中的进程进行一个快照。在创建成功后对进程逐个枚举。
枚举进程须要用到Process32First()以及Process32Next()这两个函数。使用这几个函数须要先包括Tlhelp32.h头文件。代码例如以下:
void CProcessManageDlg::ShowProcess()
{
//清空列表
m_ProcessList.DeleteAllItems();
//给系统内全部的进程拍个快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("进程快照创建失败!");
return ;
}
PROCESSENTRY32 Pe32 = { 0 };
//在使用这个结构前。先设置它的大小
Pe32.dwSize = sizeof(PROCESSENTRY32);
//遍历进程快照,轮流显示每一个进程的信息
BOOL bRet = Process32First(hSnap, &Pe32);
int i = 0;
CString str;
while ( bRet )
{
str.Format("%d", i);
m_ProcessList.InsertItem(i, str);
//进程名
m_ProcessList.SetItemText(i, 1, Pe32.szExeFile);
//进程ID
str.Format("%d", Pe32.th32ProcessID);
m_ProcessList.SetItemText(i, 2, str);
//此进程开启的线程计数
str.Format("%d", Pe32.cntThreads);
m_ProcessList.SetItemText(i, 3, str);
//父进程ID
str.Format("%d", Pe32.th32ParentProcessID);
m_ProcessList.SetItemText(i, 4, str);
//线程优先权
str.Format("%d", Pe32.pcPriClassBase);
m_ProcessList.SetItemText(i, 5, str);
i ++;
bRet = Process32Next(hSnap, &Pe32);
}
CloseHandle(hSnap);
}
由于我希望刚打开程序,就行把系统的进程显示出来,因此要在OnInitDialog()中加入:ShowProcess();
最后在头文件里加上:void ShowProcess();
四、结束进程
通常情况下。进程正常结束时,会调用ExitProcess()函数来使自身退出。而假设想要结束指定的进程,则须要使用TerminateProcess()函数。可是对于进程的操作,往往都须要使用其PID值,为了方便起见。这里编写一个获取进程PID值的程序。以方便接下来对于进程的一系列操作。它的原理就是在进程被枚举出来,显示在列表框中以后,返回所选取进程的“PID值”的内容:
int CProcessManageDlg::GetSelectPid()
{
pid = -1;
//获取列表框中所选中的位置
POSITION Pos = m_ProcessList.GetFirstSelectedItemPosition();
int nSelect = -1;
while ( Pos )
{
nSelect = m_ProcessList.GetNextSelectedItem(Pos);
}
//假设在列表框中没有进行选择。则报错
if ( -1 == nSelect )
{
AfxMessageBox("请选择进程!");
return -1;
}
//获取列表框中显示的PID值
char szPid[10] = { 0 };
m_ProcessList.GetItemText(nSelect, 2, szPid, 10);
pid = atoi(szPid);
return pid;
}
这个函数须要在头文件里声明:int GetSelectPid();
然后为“结束进程”button加入代码:void CProcessManageDlg::OnButtonTerminate()
{
// TODO: Add your control notification handler code here
int nPid = GetSelectPid();
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, nPid);
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
ShowProcess();
}
上述代码的原理是先获取进程的权限,然后再进行结束。
五、暂停与恢复进程
有些时候,恶意程序为了保护自身,可能会创建两个或者多个进程。令其“荣辱与共”。
当当中一个进程发现还有一个进程被结束了,那么它就会把那个被结束的进程又一次执行起来。这几个进程相互帮助。所以就非常难把恶意程序的进程彻底结束掉。也就不能删除恶意程序本身。
遇到这样的情况。能够将这几个进程暂停,然后就能够结束掉恶意进程了。
暂停进程通常使用的是SuspendThread()函数,它须要使用线程的句柄。线程的句柄能够通过OpenThread()函数获得,然后利用Thread32First()以及Thread32Next()这两个函数进行枚举。为“暂停进程”button加入代码:
void CProcessManageDlg::OnBtnStop()
{
// TODO: Add your control notification handler code here
int nPid = -1;
nPid = GetSelectPid();
//创建线程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("暂停进程失败!
"); return ; } THREADENTRY32 Te32 = { 0 }; Te32.dwSize = sizeof(THREADENTRY32); BOOL bRet = Thread32First(hSnap, &Te32); while ( bRet ) { //推断线程所属 if ( Te32.th32OwnerProcessID == nPid ) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, Te32.th32ThreadID); SuspendThread(hThread); CloseHandle(hThread); } bRet = Thread32Next(hSnap, &Te32); } }
因为CreateToolhelp32Snapshot()仅仅能创建系统的线程快照。不能创建指定进程中的线程的快照。所以假设想要暂停线程。必须对枚举到的线程进行推断。看其是否为指定进程中的线程。在THREADENTRY32这个结构体中的th32ThreadID标识了当前枚举到的线程的线程ID,而th32OwnerProcessID标识了该线程归属的进程的ID。所以在上述代码中须要进行推断。以找到对应的线程。
接下来为“恢复进程”button加入代码:void CProcessManageDlg::OnButtonResume()
{
// TODO: Add your control notification handler code here
int nPid = -1;
nPid = GetSelectPid();
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("进程恢复失败!
"); return ; } THREADENTRY32 Te32 = { 0 }; Te32.dwSize = sizeof(THREADENTRY32); BOOL bRet = Thread32First(hSnap, &Te32); while ( bRet ) { if ( Te32.th32OwnerProcessID == nPid ) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, Te32.th32ThreadID); ResumeThread(hThread); CloseHandle(hThread); } bRet = Thread32Next(hSnap, &Te32); } }
由于它与暂停进程原理同样,不再赘述。
六、程序效果
上述程序编译成功后,就行对进程实现结束、暂停与恢复的效果。比方对一个“记事本”程序做实验。
打开记事本。执行本软件。找到记事本的进程,单击“暂停进程”button,可见尽管记事本程序仍可见,可是已无法对其操作。
直至单击“恢复进程”后,记事本才又恢复原样。然后单击“结束进程”,则记事本就被关闭,它已经从列表框中消失了。说明我们的程序是有效的。