【第39节】windows编程:打造MFC版本任务管理器

目录

一、项目概述

二、项目开发的各种功能关键

[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 老板键)

四、完整示例demo

[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);

下面的代码可用于关机等操作:

  1. 关机:ExitWindowsEx(EWX_POWEROFF|EWX_FORCE);

  2. 重启:ExitWindowsEx(EWX_REBOOT|EWX_FORCE);

  3. 注销:ExitWindowsEx(EWX_LOGOFF|EWX_FORCE);

  4. 休眠:SetSuspendState(TRUE,FALSE,FALSE);

  5. 睡眠:SetSuspendState(FALSE,FALSE,FALSE);

  6. 锁屏: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

相关推荐
咖喱年糕4 分钟前
【Windows】系统安全移除移动存储设备指南:告别「设备被占用」弹窗
windows·安全·系统安全
良人眷1 小时前
win10win11启用组策略编辑器
windows·编辑器
Ring__Rain2 小时前
visual studio 常用的快捷键(已经熟悉的就不记录了)
c++·git·visual studio
辛姜_千尘红回2 小时前
AT_abc398_e [ABC398E] Tree Game 题解
c语言·c++·笔记·算法
啊阿狸不会拉杆2 小时前
数据结构-限定性线性表 - 栈与队列
java·c语言·数据结构·c++·python·算法
ChiaWei Lee2 小时前
【C++初学】C++核心编程(一):内存管理和引用
c++
开开心心就好2 小时前
功能丰富的PDF处理免费软件推荐
java·windows·python·pdf·电脑·生活·软件需求
刃神太酷啦2 小时前
基础算法篇(5)(蓝桥杯常考点)—动态规划(C/C++)
数据结构·c++·算法·leetcode·蓝桥杯·动态规划·蓝桥杯c++组
kchmmd3 小时前
基于QtC++音乐播放器whisper语音转文字歌词解析
c++·qt
dntktop3 小时前
《植物大战僵尸融合版v2.4.1》,塔防与创新融合的完美碰撞
运维·windows·游戏·电脑