当程序出现异常而失去响应,我们通常的做法是打开Windows任务管理器强行将其"杀死"。Windows任务管理器是个好东西,它能显示当前系统中运行的所有进程,以及它们的实时性能参数。但是作为程序员,你知道这些功能是怎么实现的吗?
"这有什么难的?!"你可能会说,"不就是调用那几个进程枚举函数嘛!"是啊,单纯实现Windows任务管理器类似的功能是不难。但是,你先别急,关于进程枚举,可能你只知其一,不知其二;更何况,我们这里还有其三、其四。除此之外,我们这里还要增强功能,显示与各个进程相关联的模块(即DLL,动态链接库)信息。
进程与DLL的基础知识
大家知道,Windows 98/2000/XP都是多任务操作系统。所谓多任务,就是系统中可以同时运行多个进程。而所谓进程,就是应用程序的运行实例。通俗地讲,进程就是一个运行起来的.EXE程序。
系统中的进程都用一个DWORD类型的数据来唯一标识,我们称之为PID。即使同一个应用程序运行多个实例,它们的PID也是不一样的。另外,进程拥有自己私有的虚拟地址空间,进程与进程之间不会相互干扰;每个进程都至少包含一条线程。
那么,DLL与进程又有什么关系呢?大家知道,自Windows诞生之日起,Windows操作系统就使用DLL来支持公共函数调用。DLL中实现的函数代码不出现在.EXE文件中,但可以被各个进程所使用。
使用DLL的好处包括:
1) 可以显著地减小每个组件的大小(特别是对于一些大型软件系统)。
2) 使升级更为简单。如果我们想要使用新版本的函数,改变DLL中的函数后,只需重新编译DLL项目,然后再连接使用该函数的各个应用程序;而应用程序本身不需要重新编译。
3) 便于功能模块化,乃至开发任务的团队协作。
一般来说,一个进程总是调用这个或那个DLL中的函数。进程与DLL是一种依赖关系。在我们的演示程序中,我们不仅要做进程枚举,我们还要来揭示进程与DLL的这种依赖关系。演示程序的用户界面如下:
图1 演示程序之用户界面
好了,言归正转,我们直奔主题。接下去,我们就来逐一介绍各种进程枚举方法。
方法一:使用工具库(Tool Help Library)函数
这是一种历史最悠久、也是最基本的方法(从Windows 95开始就支持这种方法)。这些API函数中,最重要的当属CreateToolhelp32Snapshot,它的函数原型如下:
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
BOOL CToolHelpSpy::BuildProcessList(void) { // 给系统中所有进程拍快照 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { return FALSE; } PROCESSENTRY32 pe32 = {0}; pe32.dwSize = sizeof(PROCESSENTRY32); // 遍历拍下来的所有进程 if (Process32First(hProcessSnap, &pe32)) { do { if (pe32.th32ProcessID && strcmp(pe32.szExeFile, "System")) { // 保存进程的名字、PID CProcessItem processItem; processItem.SetProcessName(pe32.szExeFile); processItem.SetProcessId(pe32.th32ProcessID); // 加入列表保存 mProcList.AddTail(processItem); } } while (Process32Next(hProcessSnap, &pe32)); } CloseHandle(hProcessSnap); return TRUE; } BOOL CToolHelpSpy::BuildModuleList(CProcessItem& inProcess) { // 给指定的进程调用的所有模块拍快照 HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, inProcess.GetProcessId()); if (hModuleSnap == INVALID_HANDLE_VALUE) { return FALSE; } MODULEENTRY32 me32 = {0}; me32.dwSize = sizeof(MODULEENTRY32); inProcess.CleanupModuleList(); // 遍历所有模块 if (Module32First(hModuleSnap, &me32)) { do { // 保存模块文件全路径 inProcess.AddModuleItem(me32.szExePath); } while (Module32Next(hModuleSnap, &me32)); } CloseHandle(hModuleSnap); return TRUE; }
BOOL CPSApiSpy::BuildProcessList(void) { // 枚举获得系统中的所有进程的PID DWORD processes[1024], needed; if (!EnumProcesses(processes, sizeof(processes), &needed)) { return FALSE; } char szName[MAX_PATH] = ""; DWORD actualProcessCount = needed / sizeof(DWORD); for (DWORD i = 0; i < actualProcessCount; i++) { // 保存进程的PID CProcessItem processItem; processItem.SetProcessId(processes[i]); // 打开当前进程以获得进程操作句柄 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processes[i]); if (hProcess) { HMODULE hModule; DWORD needed; // 枚举当前进程调用的所有模块 if (EnumProcessModules(hProcess, &hModule, sizeof(hModule), &needed)) { // 获得并保存进程的名字 GetModuleBaseName(hProcess, hModule, szName, sizeof(szName)); processItem.SetProcessName(szName); mProcList.AddTail(processItem); } CloseHandle(hProcess); } } return TRUE; } BOOL CPSApiSpy::BuildModuleList(CProcessItem& inProcess) { // 根据PID打开该进程,获得一个进程操作句柄 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, inProcess.GetProcessId()); if (hProcess) { HMODULE modules[1024]; DWORD needed; // 枚举当前进程调用的所有模块 if (EnumProcessModules(hProcess, modules, sizeof(modules), &needed)) { char szName[MAX_PATH] = ""; inProcess.CleanupModuleList(); DWORD actualModuleCount = needed / sizeof(DWORD); // 获得各个模块文件的全路径 for (DWORD i = 1; i < actualModuleCount; i++) { GetModuleFileNameEx(hProcess, modules[i], szName, sizeof(szName)); inProcess.AddModuleItem(szName); } } CloseHandle(hProcess); } return TRUE; }
#define INITIAL_SIZE 51200 #define EXTEND_SIZE 25600 #define REGKEY_PERF _T("Software\\Microsoft\\Windows NT\\Currentversion\\Perflib") #define REGSUBKEY_COUNTERS _T("Counters") #define PROCESS_COUNTER _T("process") #define PROCESSID_COUNTER _T("id process") BOOL CPerformanceSpy::BuildProcessList(void) { // 步骤一:从特定的注册表路径下获取系统中所有的对象、计数器的名字 LANGID lid = MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL); TCHAR szSubKey[1024]; _stprintf(szSubKey, _T("%s\\%03x"), REGKEY_PERF, lid); HKEY hSubKey; DWORD rt = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szSubKey, 0, KEY_READ, &hSubKey); if (rt != ERROR_SUCCESS) { return FALSE; } DWORD dwType = 0; DWORD dwSize = 0; LPBYTE buffer = NULL; BOOL pass = FALSE; // 获得装载所有计数器名字的缓冲大小 rt = RegQueryValueEx(hSubKey, REGSUBKEY_COUNTERS, NULL, &dwType, NULL, &dwSize); if (rt == ERROR_SUCCESS) { buffer = (LPBYTE) malloc(dwSize); memset(buffer, 0, dwSize); rt = RegQueryValueEx(hSubKey, REGSUBKEY_COUNTERS, NULL, &dwType, buffer, &dwSize); } LPSTR p, p2; DWORD dwProcessIdTitle; DWORD dwProcessIdCounter; PPERF_DATA_BLOCK pPerf; PPERF_OBJECT_TYPE pObj; PPERF_INSTANCE_DEFINITION pInst; PPERF_COUNTER_BLOCK pCounter; PPERF_COUNTER_DEFINITION pCounterDef; if (rt == ERROR_SUCCESS) { pass = TRUE; // 步骤二:查找名为"Process"的对象以及名为"ID Process"的计数器 // 获取它们的索引值(因为对象、计数器在性能数据中是靠索引来标识的) p = (LPSTR) buffer; while (*p) { if (p > (LPSTR) buffer) { for (p2 = p - 2; _istdigit(*p2); p2--) ; } if (_tcsicmp(p, PROCESS_COUNTER) == 0) { // 获取"Process"对象的索引 for (p2 = p - 2; _istdigit(*p2); p2--) ; _tcscpy(szSubKey, p2+1); } else if (stricmp(p, PROCESSID_COUNTER) == 0) { // 获取"ID Process"计数器的索引 for (p2 = p - 2; _istdigit(*p2); p2--) ; dwProcessIdTitle = atol(p2 + 1); } // Point to the next string p += (_tcslen(p) + 1); } // 步骤三:获取进程对象的所有性能数据 free(buffer); buffer = NULL; dwSize = INITIAL_SIZE; buffer = (LPBYTE) malloc(dwSize); memset(buffer, 0, dwSize); while (pass) { rt = RegQueryValueEx(HKEY_PERFORMANCE_DATA, szSubKey, NULL, &dwType, buffer, &dwSize); pPerf = (PPERF_DATA_BLOCK) buffer; // 性能数据块开头以四个字符"PERF"标识 if ((rt == ERROR_SUCCESS) && (dwSize > 0) && pPerf->Signature[0] == (WCHAR)'P' && pPerf->Signature[1] == (WCHAR)'E' && pPerf->Signature[2] == (WCHAR)'R' && pPerf->Signature[3] == (WCHAR)'F') { break; } // 如果缓冲不够大,扩大缓冲后再试 if (rt == ERROR_MORE_DATA) { dwSize += EXTEND_SIZE; buffer = (LPBYTE) realloc(buffer, dwSize ); memset(buffer, 0, dwSize ); } else { pass = FALSE; } } } if (pass) { pObj = (PPERF_OBJECT_TYPE) ((DWORD)pPerf + pPerf->HeaderLength); // 步骤四:在进程对象数据的计数器定义部分寻找"ID Process"计数器 pCounterDef = (PPERF_COUNTER_DEFINITION) ((DWORD)pObj + pObj->HeaderLength); for (DWORD i = 0; i < (DWORD)pObj->NumCounters; i++) { if (pCounterDef->CounterNameTitleIndex == dwProcessIdTitle) { dwProcessIdCounter = pCounterDef->CounterOffset; break; } pCounterDef++; } // 步骤五:遍历所有实例,获取实例的名字(即进程名)以及PID TCHAR szProcessName[MAX_PATH]; pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pObj + pObj->DefinitionLength); for (i = 0; i < (DWORD)pObj->NumInstances; i++) { // 获取进程名 p = (LPSTR) ((DWORD)pInst + pInst->NameOffset); rt = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)p, -1, szProcessName, sizeof(szProcessName), NULL, NULL); // 获取进程PID pCounter = (PPERF_COUNTER_BLOCK) ((DWORD)pInst + pInst->ByteLength); DWORD processId = *((LPDWORD) ((DWORD)pCounter + dwProcessIdCounter)); if (strcmp(szProcessName, "System") && processId) { CProcessItem processItem; processItem.SetProcessId(processId); processItem.SetProcessName(szProcessName); mProcList.AddTail(processItem); } // Point to the next process pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pCounter + pCounter->ByteLength); } } if (buffer) { free(buffer); buffer = NULL; } RegCloseKey(hSubKey); RegCloseKey(HKEY_PERFORMANCE_DATA); return pass; }
BOOL CPDHSpy::BuildProcessList(void)
{
LPTSTR szCounterListBuffer = NULL;
DWORD dwCounterListSize = 0;
LPTSTR szInstanceListBuffer = NULL;
DWORD dwInstanceListSize = 0;
BOOL pass = FALSE;
// 第一次调用PdhEnumObjectItems以获取需要的列表长度
PDH_STATUS pdhStatus = PdhEnumObjectItems(NULL, NULL, TEXT("Process"),
szCounterListBuffer, &dwCounterListSize, szInstanceListBuffer,
&dwInstanceListSize, PERF_DETAIL_WIZARD, 0);
if (pdhStatus == ERROR_SUCCESS)
{
szCounterListBuffer = (LPTSTR) malloc((dwCounterListSize * sizeof (TCHAR)));
szInstanceListBuffer = (LPTSTR) malloc((dwInstanceListSize * sizeof (TCHAR)));
// 第二次调用PdhEnumObjectItems
// 获得"Process"对象的所有计数器和实例
pdhStatus = PdhEnumObjectItems(NULL, NULL, TEXT("Process"),
szCounterListBuffer, &dwCounterListSize, szInstanceListBuffer,
&dwInstanceListSize, PERF_DETAIL_WIZARD, 0);
if (pdhStatus == ERROR_SUCCESS)
{
pass = TRUE;
LPTSTR pInst = szInstanceListBuffer;
// 获得每个实例名,也就是进程名
for (; *pInst != 0; pInst += lstrlen(pInst) + 1)
{
if (strcmp(pInst, "System") && strcmp(pInst, "Idle") &&
strcmp(pInst, "_Total"))
{
CProcessItem processItem;
// 获得进程的PID
processItem.SetProcessId(GetPIDCounterValue(pInst));
processItem.SetProcessName(pInst);
mProcList.AddTail(processItem);
}
}
}
}
if (szCounterListBuffer != NULL)
{
free(szCounterListBuffer);
szCounterListBuffer = NULL;
}
if (szInstanceListBuffer != NULL)
{
free(szInstanceListBuffer);
szInstanceListBuffer = NULL;
}
return pass;
}
DWORD CPDHSpy::GetPIDCounterValue(LPTSTR inInstanceName)
{
// 打开一个查询对象
HQUERY hQuery = NULL;
PDH_STATUS pdhStatus = PdhOpenQuery (0, 0, &hQuery);
HCOUNTER hCounter = NULL;
char szPathBuffer[MAX_PATH];
sprintf(szPathBuffer, "\\Process(%s)\\ID Process", inInstanceName);
pdhStatus = PdhAddCounter(hQuery, szPathBuffer, 0, &hCounter);
pdhStatus = PdhCollectQueryData(hQuery);
// 获得当前实例的"ID Process"计数器的值
DWORD ctrType;
PDH_FMT_COUNTERVALUE fmtValue;
pdhStatus = PdhGetFormattedCounterValue(hCounter, PDH_FMT_LONG,
&ctrType, &fmtValue);
// 关闭查询对象
pdhStatus = PdhCloseQuery (hQuery);
return fmtValue.longValue;
}