目录
[2.1 进程信息的获取](#2.1 进程信息的获取)
[2.2 线程信息的获取](#2.2 线程信息的获取)
[2.3 进程模块信息的获取](#2.3 进程模块信息的获取)
[2.3.1 模块快照](#2.3.1 模块快照)
[2.3.2 枚举模块](#2.3.2 枚举模块)
[2.4 进程堆信息的获取](#2.4 进程堆信息的获取)
[2.5 窗口信息的获取](#2.5 窗口信息的获取)
[2.6 文件信息的获取](#2.6 文件信息的获取)
[2.7 内存信息和CPU占用率的获取](#2.7 内存信息和CPU占用率的获取)
[2.7.1 内存信息相关结构体](#2.7.1 内存信息相关结构体)
[2.7.2 相关函数](#2.7.2 相关函数)
[2.7.3 使用例子](#2.7.3 使用例子)
[2.7.4 CPU占用率](#2.7.4 CPU占用率)
[3.1 关机、重启,注销,休眠](#3.1 关机、重启,注销,休眠)
[3.2 老板键](#3.2 老板键)
[4.1 完成的基础功能](#4.1 完成的基础功能)
[4.2 界面说明](#4.2 界面说明)
[4.3 操作说明](#4.3 操作说明)
一、项目概述
在软件开发领域,构建一个带界面且功能丰富的项目往往充满挑战。本次项目不仅要应对多样化的数据展示需求,涵盖文件、进程、线程等多源信息,还需处理各类用户交互操作。接下来,让我们一同深入探索这个项目的关键要点,包括复杂数据的获取方法、界面展示的考量,以及如关机操作、老板键设置等特色功能的实现途径 ,为项目开发提供全面且清晰的指引,并提供实现demo。
本次开发基础除了MFC相关知识要求,还要熟悉windows sdk开发知识点,不会的请看前面的章节。下面简单介绍该项目要点,界面不在此讲述细节实现。
二、项目开发的各种功能关键
2.1 进程信息的获取
在Windows系统里,要获取当前正在运行的进程,可以依靠快照系列API 。这套API挺厉害的,它不光能把正在运行的进程找出来,还能获取到进程的模块列表、堆这些信息。使用这套API有这么几个步骤:
(1)拍摄快照
(2)对快照进行遍历
(3)把快照关闭
下面就是创建进程快照,然后获取进程列表的具体做法:
cpp
//1.先创建一个进程快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 stcPe32;
//2.通过一组API遍历进程快照
Process32First(hProcessSnap, &stcPe32);
do {
//3.使用从进程快照中提取出来的信息,信息已经提取到stcPe32变量中
//这些信息包含进程的PID, 进程名等数据,这些数据可以显示到界面上
//4.获取快照中下一个进程的信息
} while (Process32Next(hProcessSnap, &stcPe32));
//5关闭快照
CloseHandle(hSanp);
2.2 线程信息的获取
获取线程信息的办法和获取进程信息差不多,也能用快照API来操作。但是,通过这个方法遍历得到的线程,是系统里所有的线程,并不是属于某一个特定进程的线程。这就可能出问题了,当我们要把这些线程数据显示到界面上的时候,就容易产生冲突。比如说,我们已经遍历出了进程列表,接下来想查看某个特定进程的线程列表,要是直接用刚才那种获取所有线程的方式,得到的线程就不是我们想要的。不过还好,遍历得到的线程信息里面,有一项是父进程的PID,利用这个PID进行筛选,就能找到指定进程的全部线程了。具体的操作方法如下:
cpp
//1.创建一个线程相关的快照句柄
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
//2.循环遍历线程信息
//通过线程快照句柄获取第一个线程信息
Thread32First(hThreadSnap, &stcTe32))
do {
//判断这个线程的父进程ID
if (stcTe32.th320wnerProcessID == 指定进程的PID) {
//将线程的信息显示到界面.
}
//获取快照中下一个线程信息
} while (Thread32Next(hThreadSnap, &stcTe32))
2.3 进程模块信息的获取
模块信息也可以通过快照API来获取。每个进程都有自己特有的模块信息,当我们创建快照的时候,得指定一个进程的PID,这样创建快照的函数就能根据这个PID,把指定进程的模块列表给取出来。
在获取模块信息的过程中,经常会碰到一个问题,那就是一般只能获取到32位进程的模块信息,对于64位进程的模块,就获取不了了。不过有个解决办法,就是用EnumProcessModulesEx函数,这个函数比较强大,32位和64位进程的模块它都能获取。但是要注意,使用这个函数有个前提,就是调用这个API的进程必须得是64位的。
下面就分别讲讲这两种获取模块信息方法的具体使用:
2.3.1 模块快照
cpp
//1.创建模块快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPId);
//2.循环遍历快照中的模块信息
MODULEENTRY32 Module32;
Module32First(hModuleSnap, Module32);
do {
//将获取到的信息显示到界面
//4.获取快照中下一个模块信息
} while (Module32Next(hModuleSnap, &m_Module32));
CloseHandle(hSnap);
2.3.2 枚举模块
(想要同时遍历出32位和64位进程的模块,项目必须被编译成64位的)
cpp
HANDLE hProcess = NULL;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPID/*进程ID*/);
HMODULE hModules[0x2000] = {}; // 只用于获取模块句柄个数
DWORD dwNeed = 0; // 获取模块句柄个数
//枚举进程模块,函数会输出模块句柄数组
EnumProcessModulesEx(hProcess, hModules, sizeof(hModules), &dwNeed, LIST_MODULES_ALL);
DWORD dwModuleCount = dwNeed / sizeof(HMODULE);
MODULEINFO moif = {0}; //保存模块信息的结构体
//循环获取模块信息
for (SIZE_T i = 0; i < dwModuleCount; ++i) {
//根据进程句柄和模块句柄获取模块路径
GetModuleFileNameEx(hProcess, hModules[i], 缓冲区, 长度);
//根据进程句柄和模块句柄获取模块其他信息
GetModuleInformation(hProcess, hModules[i], &moif, sizeof(MODULEINFO))
moif.EntryPoint; //dl1入口点
moif.lpBaseOfD11; //dll基址
moif.SizeOfImage; //dll大小
//将获取到的模块信息显示到界面上.
}
2.4 进程堆信息的获取
一个进程堆的数量众多,但遍历方式同样可使用快照系列API。
cpp
//1.创建一个线程相关的快照句柄
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SMAPTHREAD, O);
//2.循环遍历线程信息
do {
//1)通过线程快照句柄获取第一个线程信息
Thread32First(hThreadSnap, &stcTe32))
//2)将信息显示到界面
//3)获取快照中下一个线程信息
} while (Thread32Next(hThreadSnap, &stcTe32))
2.5 窗口信息的获取
窗口信息说的就是当前系统里,正在显示的或者隐藏着的窗口的相关内容。在Windows系统里,EnumWindow函数专门用来把当前系统上所有窗口都列出来。这里的枚举,意思就是Windows会逐个查看所有窗口,在这个查看的过程中,它会调用一个回调函数,然后把查看到的窗口信息传送给这个回调函数。所以,要完成枚举窗口这件事,实际上包含两个部分:第一,调用EnumWinodw函数;第二,在回调函数里把枚举出来的窗口信息保存好。
下面就是枚举窗口函数用到的API原型:
cpp
BOOL CALLBACK EnumWindowsProc(
_In_ HWND hwnd, //遍历到的窗口句柄
_In_ LPARAM 1Param //EnumWindows函数传来的参数
);
枚举窗口的用法为:
cpp
BOOL CALLBACK EnumWinProc(HWND hWnd, LPARAM 1Param) { //回调函数
//根据窗口句柄获取窗口名
TCHAR buff[200];
GetWindowText(hWnd, buff, 200); //得到窗口名,可显示到界面中.
//判断窗口是否被隐藏
if (IsWindowVisible(hWnd) == TRUE && wcslen(buff)!= 0)
//窗口没有被隐藏且窗口标题长度不为0,则将窗口信息显示到界面中.
}
int main() {
EnumWindow(&EnumWinProc/*枚举窗口的回调函数*/, NULL/*回调函数的附加参数*/);
}
2.6 文件信息的获取
Windows系统提供了一组能用来遍历文件的API,分别是FindFirstFile和FindNextFile。FindFirstFile这个API的作用是找到一个文件夹里的第一个文件或者子文件夹。而FindNextFile呢,它负责一个接一个地去查找剩下的文件或文件夹,直到全部找完为止。所以,在使用这组API的时候,通常得先调用FindFirstFile,它会返回一个用来查找文件的句柄,拿到这个句柄后,再用FindNextFile去把剩下的文件都找出来。不过这组API有个局限性,它们只能找到当前目录下的文件和文件夹。要是想把所有层级文件夹里的文件都找出来,那就得自己写个递归函数才能做到了。
下面给你看看这两个函数具体怎么简单使用:
cpp
HANDLE hFind ;
WIN32_FIND_DATA fData;
hFind = FindFirstFile(L"D:\\*", &fData);
if (hFind == (HANDLE)-1) return ;
do {
//将遍历到的信息(fData结构体变量的内容)显示到界面.
} while (FindNextFile(hFind, &fData));
2.7 内存信息和CPU占用率的获取
2.7.1 内存信息相关结构体
cpp
typedef struct _MEMORYSTATUS {
DWORD dwLength; //该结构体大小
DWORD dwMemoryLoad; //当前系统内存的占用率(百分比)
SIZE_T dwTotalPhys; //总的物理内存大小
SIZE_T dwAvailPhys; //可能的物理内存大小以字节为单位
SIZE_T dwTotalPageFile; //交换文件总大小
SIZE_T dwAvailPageFile; //交换文件中空闲部分大小
SIZE_T dwTotalVirtual; //总的虚拟内存大小
SIZE_T dwAvailVirtual; //可用虚拟内存大小
}MEMORYSTATUS, *LPMEMORYSTATUS;
2.7.2 相关函数
获取系统内存信息
cpp
void WINAPI GlobalMemoryStatus(
_Out_ LPMEMORYSTATUS lpBuffer //MEMORYSTATUS结构体指针
);
2.7.3 使用例子
cpp
//1.创建结构体对象并调用获取内存信息的函数
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
//当前内存占用率:memStatus.dwMemoryLoad
//已用物理内存大小:memStatus.dwTotalPhys - memStatus.dwAvailPhys
2.7.4 CPU占用率
CPU占用率,说的就是在单位时间里,CPU处于被占用状态的总时长。要算出这个数值,我们得先知道在一段时间内CPU实际被使用的时间,然后通过下面这个公式来计算它的占用率:
使用率 = 100.0 - (空闲时间÷使用时间)× 100.0;
至于获取当前CPU被占用的时间,就得借助GetSystemTimes函数了。这个函数可以获取到三个时间数据:CPU空闲下来的时间、CPU在内核态下被使用的时间,还有CPU在用户态下被使用的时间。这里面,把CPU在内核态和用户态被使用的时间加起来,得到的就是CPU总的使用时间。下面的代码展示了具体计算CPU占用率的方法:
cpp
double FILETIME2Double(const _FILETIME& fileTime) {
return double(fileTime.dwHighDateTime * 4.294967296e9) + double(fileTime.dwLowDateTime);
}
Int GetCpuUsage(){
// 空闲时间 内核时间 用户时间
_FILETIME idleTime, kernelTime, userTime;
//获取时间
GetSystemTimes(&idleTime, &kernelTime, &userTime);
//等待1000毫秒
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
//等待1000毫秒,使用内核对象等待会更精确
WaitForSingleObject(hEvent, 1000);
//获取新的时间
_FILETIME newIdleTime, newKernelTime, newUserTime;
GetSystemTimes(&newIdleTime, &newKernelTime, &newUserTime);
//将各个时间转换
double d01dIdleTime = FILETIME2Double(idleTime);
double dNewIdleTime = FILETIME2Double(newIdleTime);
double d0IdKernelTime = FILETIME2Double(kernelTime);
double dNewKernelTime = FILETIME2Double(newKernelTime);
double d01dUserTime = FILETIME2Double(userTime);
double dNewUserTime = FILETIME2Double(newUserTime);
//计算出使用率
return int(100.0 - (dNewIdleTime - d0ldIdleTime) / (dNewKernelTime - d01dKernelTime + dNewUserTime - d0ldUserTime) * 100.0);
}
三、其它功能要点
3.1 关机、重启,注销,休眠
关机、重启、注销以及休眠,这些操作都需要较高的权限才能执行。要是一个进程的权限不够高,去执行这些操作就会失败。所以,要是一个程序打算进行这些操作,第一步得用管理员权限来运行,第二步还得通过专门的权限提升函数,让程序获得关机的特权。具体的操作步骤是这样的:
(1)把关机特权获取到
(2)进行关机相关的操作
下面这段代码就是用来开启关机特权的
cpp
HANDLE hToken = NULL;
HANDLE hProcess = GetCurrentProcess(); //该函数能得到该进程的伪句柄
OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
TOKEN_PRIVILEGES tp = {0 };
LookupPrivilegeValue(0, SE_SHUTDOWN_NAME, &tp.Privileges[0].Luid));
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//调用函数提升权限
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
下面的代码可用于关机等操作:
-
关机:ExitWindowsEx(EWX_POWEROFF|EWX_FORCE);
-
重启:ExitWindowsEx(EWX_REBOOT|EWX_FORCE);
-
注销:ExitWindowsEx(EWX_LOGOFF|EWX_FORCE);
-
休眠:SetSuspendState(TRUE,FALSE,FALSE);
-
睡眠:SetSuspendState(FALSE,FALSE,FALSE);
-
锁屏:LockWorkStation();
3.2 老板键
老板键其实就是能快速响应全局按键,这样不用激活窗口,按一组特定按键,窗口就能迅速"消失"。
用这种加强版的快捷键分两步。第一步,用RegisterHotKey函数向系统注册一个全局热键,注册的时候能对热键进行设置,像热键的ID、快捷键之类的。函数原型如下:
cpp
BOOL WINAPI RegisterHotKey(
_In_opt_ HWND hWnd, //响应该热键的窗口句柄
_In_ int id, //该热键的ID,ID值可以是自定义的一个数值
_In_ UINT fsModifiers,//该热键的辅助按键例如Ctrl键盘)
_In_ UINT vk //该热键的键值,一个按键码
);
注册热键的例子如下:
cpp
//注册一个快捷键:Ctrl +Shift+H
::RegisterHotKey(hWnd, //当前窗口的句柄
0x1234, //自定义热键ID值
MOD_CONTROL|MOD_SHIFT, //同时按下ctrl,shift
'h'); //ctrl+shift+h
第二步呢,要在消息响应函数里对WM_HOTKEY消息做出反应。这个消息的WPARAM参数,其实就是我们注册热键时自己设定的那个热键ID 。接着在这个消息处理过程中,就能让窗口显示或者隐藏。
要是用SDK开发,直接在回调函数里写case WM_HOTKEY消息的处理代码就行。但如果是用MFC开发,因为没办法直接编写消息回调函数,就得在类向导里添加PreTranslateMessage虚函数来处理这个消息。下面就是相关代码:
cpp
BOOL CXXXDlg::PreTranslateMessage(MSG* pMsg) {
//判断消息是否是全局热键消息,并判断该热键的ID是否是我们注册过的
if ((pMsg->message == WM_HOTKEY) && (pMsg->wParam == 0x1234)) {
if (IsWindowVisible(m_hWnd) == TRUE)
ShowWindow(SW_HIDE); //隐藏窗口
else
ShowWindow(SW_SHOW); //显示窗口
}
return CDialogEx::PreTranslateMessage(pMsg);
}
四、完整示例demo
MFC任务管理器说明书
4.1 完成的基础功能
1 遍历进程
2 遍历线程
3 遍历模块
4 遍历堆
5 结束进程,挂起线程,恢复运行线程,结束线程
6 获取CPU,内存信息
7 遍历文件信息:文件名,创建时间,修改时间,文件大小
8 遍历窗口信息
9 扫描并清理VS工程垃圾
附加功能如下:
1 老板键及其他快捷键:如ctrl+7隐藏程序,再次恢复显示程序
2 进程列表按列排序,根据整数或者字符串排序显示
3 利用定时器自主设置进程表刷新速度
4 主菜单项:关机,重启,注销,休眠,睡眠,锁屏
5 刷新窗口信息和关闭窗口
6 增加线程更新cpu和内存信息
7 查看有些堆信息有点卡顿,增加线程查看堆信息
开发环境:Windows10+VS2015+CPP
4.2 界面说明
1 菜单选项有:
刷新速度
关机 ctrl+1
重启 ctrl+2
注销 ctrl+3
休眠 ctrl+4
睡眠 ctrl+5
锁屏 ctrl+6
隐藏 ctrl+7
2 CPU利用率 内存占用率
3 TAB选项
进程 文件 窗口 清理
进程:显示进程列表,根据进程ID可以查看对应的线程,模块和堆
文件:查看某文件夹或磁盘的文件
窗口:查看当前所有显示窗口
清理:清理VS工程的工具
4.3 操作说明
可以鼠标左键单击菜单选项操作,在进程列表,线程模块堆和窗口列表可以使用鼠标右键菜单操作。其他子窗口看界面说明。
注意事项:程序操作中,如结束进程,挂起线程,恢复运行线程,结束线程,查看模块,堆信息有些会失败,可能因为权限不够或者被限制。
进程

文件

窗口

清理

demo代码: https://download.csdn.net/download/linshantang/90530350