ToolHelp32 库函数在 KERNEL32.dll 中,它们都是标准的 API 函数。但是 Windows NT 4.0 不提供这些函。 ToolHelp32 库中有各种各样的函数可以用来枚举系统中的进程、线程以及获取内存和模块信息。
其中枚举进程 只需用如下三个的函数:CreateToolhelp32Snapshot()、Process32First()和 Process32Next()。
使用 ToolHelp32 函数:
第一步 是用 CreateToolhelp32Snapshot() 函数创建系统信息“快照”。这个函数可以让你选择存储在快照中的信息类型。如果你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志即可。 CreateToolhelp32Snapshot() 函数返回一个 HANDLE,完成调用之后,必须将此 HANDLE 传给 CloseHandle()。
其次 是调用一次 Process32First 函数,从快照中获取进程列表,然后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个PROCESSENTRY32 结构。 调用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 可以被传给 OpenProcess() API 以获得该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile 结构成员中。在该结构中还可以找到其它一些有用的信息。
注意:在调用 Process32First() 之前,一定要记住将 PROCESSENTRY32 结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)。
下面的代码是计算程序中“SMSS.EXE”进程的个数的代码:
void CEnumProcessDlg::OnEnumprocess()
{
INT nCount = 0;
HANDLE hToolHelp;
hToolHelp = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
PROCESSENTRY32 process32;
process32.dwSize = sizeof(process32);
BOOL bReturn = Process32First( hToolHelp, &process32 );
while (bReturn)
{
CString strProcessName("SMSS.EXE");
if ( strProcessName == process32.szExeFile )
{
nCount ++;
}
bReturn = Process32Next( hToolHelp, &process32 );
}
}
#include <windows.h>
#include <tlhelp32.h> //toolhelp API 头文件
#include "stdio.h"
int main()
{
HANDLE hSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //建立进程快照
PROCESSENTRY32 pe; //定义一个PROCESSENTRY32结构
pe.dwSize = sizeof(pe); //初始化
BOOL bNext=Process32First(hSnap, &pe); //得到第一个进程
while (bNext)
{
printf("\n%-20s%d",pe.szExeFile,pe.th32ProcessID); //显示
bNext= Process32Next(hSnap, &pe); //得到下一个进程
}
return 0;
}
本篇文章主要讲述进程查看程序利用的主要API——Toolhelp。Toolhelp API位于Toolhelp.DLL中。包含13个函数和5个结构体。利用这些函数你可以编写既适合自己需要,又适合自己实际平台的调试工具。具体函数声明如下表:
函数功能
CreateToolhelp32Snapshot 创建系统快照,在参数2中指定快照的对象,对象包括进程、线程、DLL、堆。因为系统内这些对象的生存期可能非常短,所以形成快照后的数据不一定完全反映真实情况。函数返回快照的句柄。
CloseToolhelp32Snapshot 关闭快照对象。参数为句柄。
Heap32First 函数获得指定进程中指定堆内部的第一个块的信息。信息包括块的首地址、块大小、块标志等
Heap32Next 与上一个函数结合使用,获得下一个块的信息。
Heap32ListFirst 函数获得指定进程中第一个堆的信息。
Heap32ListNext 与上一个函数结合使用,获得下一个堆的信息。
Module32First 函数获得指定进程中第一个模块(DLL)的信息。信息包括模块的ID、引用计数、首地址、大小、路径等。
Module32Next 与上一个函数结合使用,获得下一个模块的信息。
Process32First 函数获得当前系统快照对象中第一个进程信息。
Process32Next 与上一个函数结合使用,获得下一个进程的信息。
Thread32First 函数获得指定进程中第一个线程的信息。信息包括线程ID、优先级、创建此线程的ID、访问键。
Thread32Next 与上一个函数结合使用,获得下一个线程的信息。
Toolhelp32ReadProcessMemory 函数获得指定进程中指定内存区域的数据。
利用这些函数即可编写一个获得当前系统进程、线程、DLL、堆的信息。但是有几点需要注意,也是CE帮助文档中重点强调的。
CreateToolhelp32Snapshot函数将当前系统的进程、线程、DLL、堆的信息全部复制到一个缓冲区里。所以在执行此函数后再调用其它Toolhelp函数,所得到的信息未必是准确的。比如得到的线程句柄在使用时异常,得到模块地址时模块已经释放。 根据上面所说,我们得到的信息未必是准确的,那么就应该在代码中加入异常处理。(我编写的“CEInfo”就没有加入异常处理,因为我只查看系统信息) 关闭快照对象只能用CloseToolhelp32Snapshot,不能用CloseHandle。
每一个应用程序实例在运行起来后都会在当前系统下产生一个进程,大多数应用程序均拥有可视界面,用户可以通过标题栏上的关闭按钮关闭程序。但是也有为数不少的在后台运行的程序是没有可视界面的,对于这类应用程序用户只能通过CTRL+ALT+DEL热键呼出"关闭程序"对话框显示出当前系统进程列表,从中可以结束指定的任务。显然,该功能在一些系统监控类软件中还是非常必需的,其处理过程大致可以分为两步:借助系统快照实现对系统当前进程的枚举和根据枚举结果对进程进行管理。本文下面即将对此过程的实现进行介绍。 当前进程的枚举 要对当前系统所有已开启的进程进行枚举,就必须首先获得那些加载到内存的进程当前相关状态信息。在Windows操作系统下,这些进程的当前状态信息不能直接从进程本身获取,系统已为所有保存在系统内存中的进程、线程以及模块等的当前状态的信息制作了一个只读副本--系统快照,用户可以通过对系统快照的访问完成对进程当前状态的检测。在具体实现时,系统快照句柄的获取是通过Win32 API函数CreateToolhelp32Snapshot()来完成的,通过该函数不仅可以获取进程快照,而且对于堆、模块和线程的系统快照同样可以获取。该函数原型声明如下: HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID);
其中,参数dwFlags:指定将要创建包含哪一类系统信息的快照句柄,本程序中只需要检索系统进程信息,因此可将其设置为TH32CS_SNAPPROCESS;
函数第二个参数th32ProcessID`则指定了进程的标识号,当设置为0时指定当前进程。如果成功函数将返回一个包含进程信息的系统快照句柄。在得到快照句柄之后只能以只读的方式对其进行访问。至于对系统快照句柄的使用同普通对象句柄的使用并没有什么太大区别,在使用完之后也需要通过CloseHandle()函数将其销毁。 在得到系统的快照句柄后,就可以对当前进程的标识号进行枚举了,通过这些枚举出的进程标识号可以很方便的对进程进行管理。进程标识号通过函数Process32First() 和 Process32Next()而得到,这两个函数可以枚举出系统当前所有开启的进程,并且可以得到相关的进程信息。这两个函数原型声明如下: BOOL WINAPI Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next(HANDLE hSnapshot,LPPROCESSENTRY32 lppe);
以上两个函数分别用于获得系统快照中第一个和下一个进程的信息,并将获取得到的信息保存在指针lppe所指向的PROCESSENTRY32结构中。
函数第一个参数hSnapshot为由CreateToolhelp32Snapshot()函数返回得到的系统快照句柄;
第二个参数lppe为指向结构PROCESSENTRY32的指针,PROCESSENTRY32结构可对进程作一个较为全面的描述,其定义如下:
typedef struct tagPROCESSENTRY32
{ DWORD dwSize; // 结构大小;
DWORD cntUsage; // 此进程的引用计数;
DWORD th32ProcessID; // 进程ID;
DWORD th32DefaultHeapID; // 进程默认堆ID;
DWORD th32ModuleID; // 进程模块ID;
DWORD cntThreads; // 此进程开启的线程计数;
DWORD th32ParentProcessID; // 父进程ID;
LONG pcPriClassBase; // 线程优先权;
DWORD dwFlags; // 保留;
char szExeFile[MAX_PATH]; // 进程全名;
} PROCESSENTRY32;
以上三个API函数均在头文件tlhelp32.h中声明,运行时需要有kernel32.lib库的支持。通过这三个函数可以枚举出当前系统已开启的所有进程,并可获取到进程的各相关信息,下面给出一个简单的应用示例。在此示例中将枚举出系统的所有进程,并获取各进程的标识号和相应程序的绝对路径,进程标识号在下一步对进程的管理中将要用到,程序路径则直接通过列表控件显示出来:
// PROCESSENTRY32结构对象
PROCESSENTRY32 pe;
// 创建快照句柄 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
// 先搜索系统中第一个进程的信息
Process32First(hSnapshot, &pe);
// 下面对系统中的所有进程进行枚举,并保存其信息
do{
// 把进程对应的文件路径名填入列表框
int index = m_ctlwndList.AddString(pe.szExeFile);
// 设置列表框中该项的Data相应的进程的ID号,利于以后终止该进程
m_ctlwndList.SetItemData(index, pe.th32ProcessID);
} while (Process32Next(hSnapshot, &pe));
// 关闭快照句柄
CloseHandle(hSnapshot);
在得到各枚举进程的标识号后就可以实现对进程的管理了,由于被管理进程在当前进程之外,因此必须首先通过OpenProcess()函数来获取一个已经存在的进程对象的句柄,然后才可以通过该句柄对指定的进程进行管理和控制。在OpenProcess()函数的调用时把进程标识号作为参数传入,OpenProcess()函数的原型声明如下:
HANDLE OpenProcess(DWORD dwDesiredAccess, // 访问标志 BOOL bInheritHandle, // 处理继承的标志 DWORD dwProcessId // 进程标识号); 如果函数执行成功将返回由进程标识号指定的进程对象句柄。下面同样也对其给出一个简单的应用示例,在此示例中根据所获取的进程对象句柄通过TerminateProcess()函数将指定的进程终止:
// 获得此时列表框中的所选项的数据,即该项对应的进程的ID值
int index = m_ctlwndList.GetCurSel();
// 获得此时列表框中的选项,即该项对应的进程的ID值
DWORD data = m_ctlwndList.GetItemData(index);
// 利用进程的ID值,打开该进程,获得进程句柄
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE,data);
// 检测句柄的有效性,如有效则终止该进程
if (hProcess) TerminateProcess(hProcess,0);
由于需要在调用TerminateProcess()函数终止进程时确保进程句柄可有效使用,因此在前面调用OpenProcess()时,需要指定其访问标致为PROCESS_TERMINATE。
小结 本文主要对系统快照以及通过借助系统快照而对系统当前进程进行枚举、管理的实现方法做了简要介绍。在本文只讨论了包含有进程信息的系统快照,感兴趣的读者完全可以用类似的方法实现对包含有线程、堆或是摸块等信息的系统快照的应用。