## 一、前言
進程是計算機中運行的程序,是向操作系統申請資源的基本單位。我們運行一個程序,那么就會相應地創建一個甚至多個進程,關閉程序時,進程也就結束了。查看進程最常用的手段是按下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();
~~~
然后為“結束進程”按鈕添加代碼:
~~~
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()這兩個函數進行枚舉。為“暫停進程”按鈕添加代碼:
~~~
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。所以在上述代碼中需要進行判斷,以找到相應的線程。
接下來為“恢復進程”按鈕添加代碼:
~~~
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);
}
}
~~~
因為它與暫停進程原理相同,不再贅述。
## 六、程序效果
上述程序編譯成功后,就能夠對進程實現結束、暫停與恢復的效果。

圖2 查看記事本進程
比如對一個“記事本”程序做實驗。打開記事本,運行本軟件,找到記事本的進程,單擊“暫停進程”按鈕,可見雖然記事本程序仍可見,但是已無法對其操作。直至單擊“恢復進程”后,記事本才又恢復原樣。然后單擊“結束進程”,則記事本就被關閉,它已經從列表框中消失了。說明我們的程序是有效的。
****
## 七、小結
這次實現了一個簡單的進程管理器程序,這類程序往往在手動查殺病毒方面有很大的用處。也希望讀者能夠舉一反三,在這個基礎上開發出功能更加全面的程序出來。