在windows系统中获取进程信息
写在前面
本篇文章主要是为了记录我在编写Windows平台下任务管理器程序时用到的一些API,希望对有类似需求的读者有帮助。
关键头文件
为了获取进程的详细信息,我们需要用到两个头文件TlHelp32.h
和psapi.h
。
TlHelp32.h
先说说使用TlHelp32.h
获取进程信息。该头文件中使用的函数较为复杂,微软文档中的描述以及给出的示例代码都不是很完美(后面会提到)。关于TlHelp32.h
中的接口可以获取进程的哪些信息,我们可以在微软文档中查看TlHelp32.h
的帮助文档,在那里可以找到一些结构体的定义,也就是可以获取的信息。
枚举进程
首先介绍枚举进程的操作。在TlHelp32.h
中,我们先创建当前所有进程的快照,然后利用这个快照,调用Process32First
和Process32Next
函数遍历进程,这两个函数填充PROCESSENTRY32
结构体,该结构体中保存了进程的基本信息。下面来看代码(代码中忽略了错误处理的部分)。
// 创建快照,第二个参数指明了需要包含在快照中的信息
HANDLE hSnapAll = CreateToolhelp32SnapShot(TH32CS_SNAPALL, 0);
// 我们需要获取的基本进程信息是PROCESSENTRY32的一些字段
PROCESSENTRY32 pe;
// 结构体大小
pe.dwSize = sizeof(PROCESSENTRY32);
// 获取快照中第一个进程的信息
Process32First(hSnapAll, &pe);
do
{
// 可以获取pe中的字段
// 以此来进行得到想要的信息
} while (Process32Next(hSnapAll, &pe));
使用TlHelp32.h
还可以枚举系统中的线程、模块,代码和上面类似,只不过把保存相关信息的结构体改为其它的结构体类型,枚举函数也对应发生改变。接下来重点说说如何获取指定进程的信息。
获取指定进程的信息
先说第一个坑。在上一节的示例代码中,CreateToolhelp32SnapShot
的第一个参数我们设置了TH32CS_SNAPALL
,第二个参数我们填了0,表示我们希望获取创建一个包含所有进程的信息的快照。自然而然的,如果希望获取指定进程的信息,我们也许会把第二个参数设置为那个进程的PID。如果你是这样想的,那就大错特错了。我们来看看微软文档中对第二个参数的解释:
要包含在快照中的进程的进程标识符。此参数可以为零以指示当前进程。指定TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPMODULE32或TH32CS_SNAPALL值时,使用此参数。否则,将忽略它,并且所有进程都包含在快照中。
从这里不难看出,第一个参数是TH32CS_SNAPALL
时,第二个参数是没有任何作用的。因此,为了获取指定进程的信息,我们只好使用如下的示例代码:
// 假设我们需要获取信息的进程PID为1645
DWORD dwPid = 1645;
PROCESSENTRY32 pe;
pe.dSize = sizeof(pe);
HANDLE hSnapAll = CreateToolhelp32SnapSHot(TH32CS_SNAPALL, 0);
ProcessFirst(hSnapAll,&pe);
// PROCESSENTRY32结构体中th32ProcessID代表进程的PID
while(pe.th32ProcessID != dwPid)
{
Process32Next(hSnapAll,&pe);
}
当然在获取指定进程的线程、模块和堆的信息时,通过设置第一个参数为TH32CS_SNAPTHREAD
或TH32CS_SNAPMODULE
,第二个参数为对应进程的PID,这样我们不需要遍历快照便可以得到想要的信息了。就像下面这样:
DWORD dwPID = 1645;
// 输出进程的所有线程的信息
HANDLE hSnapThread = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPID);
Thread32First(hSnapThread, &te);
do
{
if (te.th32OwnerProcessID == dwPID)
{
_tprintf(TEXT("TID:%d\n内核优先级:%d\n"), te.th32ThreadID, te.tpBasePri);
}
} while (Thread32Next(hSnapThread, &te));
//输出所有进程的所有模块信息
HANDLE hSnapModule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
Module32First(hSnapModule, &me);
do
{
_tprintf(TEXT("模块基址:%x\n模块路径:%s\n模块大小:%d byte\n"), (DWORD)me.modBaseAddr, me.szExePath, me.modBaseSize);
} while (Module32Next(hSnapAll, &me));
Psapi.h
相较于TlHelp32.h
,Psapi.h
中的接口就方便多了。枚举进程直接用EnumProcesses
函数填充一个DWORD
数组就可以。要获取进程的其它信息,可以使用EnumProcessesModules
、GetModuleInformation
等等函数。下面以获取进程模块为例进行说明,相关代码如下:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
//获取模块句柄数组需要的大小
DWORD cbSize = 0;
EnumProcessModules(hProcess, NULL, 0, &cbSize);
DWORD dwModNums = cbSize / sizeof(HMODULE);
//创建模块句柄数组
HMODULE* lphModules = new HMODULE[dwModNums];
//填充模块句柄数组
EnumProcessModules(hProcess, lphModules, cbSize + sizeof(HMODULE), &cbSize)
//获取模块信息
MODULEINFO mi;
LPTSTR lpszModuleName = new TCHAR[MAX_PATH];
for (DWORD i = 0; i < dwModNums; i++)
{
GetModuleInformation(hProcess, lphModules[i], &mi, sizeof(MODULEINFO))
GetModuleFileNameEx(hProcess, lphModules[i], lpszModuleName, MAX_PATH)
}
总结
本文只是在写任务管理器项目的随手记录,其中不乏存在许多纰漏,写得也不够详细,希望大家能够指出并谅解。