IntelliTask






4.73/5 (53投票s)
任务管理器会显示您计算机上当前正在运行的程序、进程和服务。您可以使用任务管理器来监控计算机的性能,或者关闭无响应的程序。
引言
任务管理器会显示您计算机上当前正在运行的程序、进程和服务。您可以使用任务管理器来监控计算机的性能,或者关闭无响应的程序。
背景
检查当前正在运行进程的最简单方法是创建内存快照。为此,我们使用 CreateToolhelp32Snapshot
、Process32First
和 Process32Next
函数,其语法如下:
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
参数
dwFlags
:要包含在快照中的系统部分。此参数可以是一个或多个以下值:TH32CS_INHERIT
(指示快照句柄是可继承的)、TH32CS_SNAPALL
(包括系统中所有进程和线程,以及th32ProcessID
中指定的进程的堆和模块)、TH32CS_SNAPHEAPLIST
(包括th32ProcessID
中指定的进程在快照中的所有堆)、TH32CS_SNAPMODULE
(包括th32ProcessID
中指定的进程在快照中的所有模块)、TH32CS_SNAPMODULE32
(当从 64 位进程调用时,包括th32ProcessID
中指定的进程在快照中的所有 32 位模块)、TH32CS_SNAPPROCESS
(包括快照中系统中的所有进程),以及TH32CS_SNAPTHREAD
(包括快照中系统中的所有线程)。th32ProcessID
:要包含在快照中的进程标识符。此参数可以为零,表示当前进程。当指定TH32CS_SNAPHEAPLIST
、TH32CS_SNAPMODULE
、TH32CS_SNAPMODULE32
或TH32CS_SNAPALL
值时,将使用此参数。否则,它将被忽略,并且快照中将包含所有进程。
返回值
如果函数成功,它将返回指定快照的打开句柄。
如果函数失败,它将返回 INVALID_HANDLE_VALUE
。要获取扩展的错误信息,请调用 GetLastError
。可能的错误代码包括 ERROR_BAD_LENGTH
。
BOOL WINAPI Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
BOOL WINAPI Process32Next(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
参数
hSnapshot
:来自先前调用CreateToolhelp32Snapshot
函数的快照句柄。lppe
:指向PROCESSENTRY32
结构的指针。它包含进程信息,例如可执行文件的名称、进程标识符以及父进程的进程标识符。
返回值
如果进程列表的第一个条目已复制到缓冲区,则返回 TRUE
,否则返回 FALSE
。如果不存在进程或快照不包含进程信息,则 GetLastError
函数会返回 ERROR_NO_MORE_FILES
错误值。
因此,我们使用上述函数的实现将是:
BOOL CSystemSnapshot::Refresh()
{
HANDLE hSnapshot = NULL;
PROCESSENTRY32 pe32 = { 0 };
VERIFY(RemoveAll());
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) != INVALID_HANDLE_VALUE)
{
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe32))
{
return FALSE;
}
do
{
VERIFY(InsertProcess(pe32));
} while (Process32Next(hSnapshot, &pe32));
VERIFY(CloseHandle(hSnapshot));
}
else
{
return FALSE;
}
return TRUE;
}
BOOL CSystemSnapshot::InsertProcess(PROCESSENTRY32& pe32)
{
HANDLE hProcess = NULL;
PROCESS_MEMORY_COUNTERS pmc = { 0 };
pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
CProcessData* pProcessData = new CProcessData();
m_arrProcessList.Add(pProcessData);
pProcessData->SetProcessID(pe32.th32ProcessID);
pProcessData->SetParentProcessID(pe32.th32ParentProcessID);
pProcessData->SetFileName(pe32.szExeFile);
if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ, FALSE, pe32.th32ProcessID)) != NULL)
{
pProcessData->m_pCpuUsage.SetProcessID(pe32.th32ProcessID);
pProcessData->SetProcessorUsage(pProcessData->m_pCpuUsage.GetUsage());
if (GetProcessMemoryInfo(hProcess, &pmc, pmc.cb))
{
pProcessData->SetMemoryUsage(pmc.WorkingSetSize);
}
TCHAR lpszFullPath[MAX_PATH] = { 0 };
if (GetModuleFileNameEx(hProcess, NULL, lpszFullPath, MAX_PATH))
{
CVersionInfo pVersionInfo;
if (pVersionInfo.Load(lpszFullPath))
{
pProcessData->SetDescription(pVersionInfo.GetFileDescription());
pProcessData->SetCompany(pVersionInfo.GetCompanyName());
pProcessData->SetVersion(pVersionInfo.GetFileVersionAsString());
}
}
VERIFY(CloseHandle(hProcess));
}
return TRUE;
}
CProcessData* CSystemSnapshot::UpdateProcess(DWORD dwProcessID)
{
HANDLE hProcess = NULL;
PROCESS_MEMORY_COUNTERS pmc = { 0 };
pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
CProcessData* pProcessData = GetProcessID(dwProcessID);
if (pProcessData != NULL)
{
if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ, FALSE, dwProcessID)) != NULL)
{
pProcessData->SetProcessorUsage(pProcessData->m_pCpuUsage.GetUsage());
if (GetProcessMemoryInfo(hProcess, &pmc, pmc.cb))
{
pProcessData->SetMemoryUsage(pmc.WorkingSetSize);
}
VERIFY(CloseHandle(hProcess));
}
return pProcessData;
}
return NULL;
}
架构
每个进程的定义都包含在一个 CProcessData
类中,该类的接口如下:
DWORD GetProcessID();
- 获取当前进程定义的 IDSetProcessID(DWORD dwProcessID);
- 设置当前进程定义的 IDDWORD GetParentProcessID();
- 获取当前进程定义的父进程 IDvoid SetParentProcessID(DWORD dwParentProcessID);
- 设置当前进程定义的父进程 IDDWORD GetPriority();
- 获取当前进程定义的优先级void SetPriority(DWORD dwPriority);
- 设置当前进程定义的优先级DOUBLE GetProcessorUsage();
- 获取当前进程定义的 CPU 使用率void SetProcessorUsage(DOUBLE cpuUsage);
- 设置当前进程定义的 CPU 使用率DWORD GetMemoryUsage();
- 获取当前进程定义的内存使用量void SetMemoryUsage(DWORD memUsage);
- 设置当前进程定义的内存使用量CString GetFileName();
- 获取当前进程定义的 Y 文件的名称void SetFileName(CString strFileName);
- 设置当前进程定义的 Y 文件的名称CString GetFilePath();
- 获取当前进程定义的 Y 文件的路径void SetFilePath(CString strFilePath);
- 设置当前进程定义的 Y 文件的路径CString GetDescription();
- 获取当前进程定义的描述void SetDescription(CString strDescription);
- 设置当前进程定义的描述CString GetCompany();
- 获取当前进程定义的 Y 公司void SetCompany(CString strCompany);
- 设置当前进程定义的 Y 公司CString GetVersion();
- 获取当前进程定义的 Y 版本void SetVersion(CString strVersion);
- 设置当前进程定义的 Y 版本
然后,我们将 CProcessList
定义为 typedef CArray<CProcessData*> CProcessList;
。
此列表在 CSystemSnapshot
类中进行管理,该类的接口如下:
BOOL RemoveAll();
- 从列表中删除所有进程定义int GetSize();
- 获取进程定义列表的大小CProcessData* GetAt(int nIndex);
- 从列表中获取进程定义BOOL Refresh();
- 更新列表中每个进程定义的 Y 状态BOOL InsertProcess(PROCESSENTRY32& pe32);
- 将进程定义插入列表CProcessData* UpdateProcess(DWORD dwProcessID);
- 从列表中更新进程定义BOOL DeleteProcess(DWORD dwProcessID);
- 从列表中删除进程定义
关注点
IntelliTask 的挑战在于计算每个进程的确切 CPU 使用率,如下所示:
CpuUsage::CpuUsage(void)
: m_dwProcessID(0)
, m_nCpuUsage(-1)
, m_dwLastRun(0)
, m_lRunCount(0)
{
ZeroMemory(&m_ftPrevSysKernel, sizeof(FILETIME));
ZeroMemory(&m_ftPrevSysUser, sizeof(FILETIME));
ZeroMemory(&m_ftPrevProcKernel, sizeof(FILETIME));
ZeroMemory(&m_ftPrevProcUser, sizeof(FILETIME));
}
/**********************************************
* CpuUsage::GetUsage
* returns the percent of the CPU that this process
* has used since the last time the method was called.
* If there is not enough information, -1 is returned.
* If the method is recalled to quickly, the previous value
* is returned.
***********************************************/
DOUBLE CpuUsage::GetUsage()
{
HANDLE hProcess = NULL;
// create a local copy to protect against race conditions
// in setting the member variable
DOUBLE nCpuCopy = m_nCpuUsage;
if (::InterlockedIncrement(&m_lRunCount) == 1)
{
// If this is called too often, the measurement itself will
// greatly affect the results.
if (!EnoughTimePassed())
{
::InterlockedDecrement(&m_lRunCount);
return nCpuCopy;
}
FILETIME ftSysIdle, ftSysKernel, ftSysUser;
FILETIME ftProcCreation, ftProcExit, ftProcKernel, ftProcUser;
if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ, FALSE, m_dwProcessID)) != NULL)
{
if (!GetSystemTimes(&ftSysIdle, &ftSysKernel, &ftSysUser) ||
!GetProcessTimes(hProcess, &ftProcCreation,
&ftProcExit, &ftProcKernel, &ftProcUser))
{
::InterlockedDecrement(&m_lRunCount);
CloseHandle(hProcess);
return nCpuCopy;
}
CloseHandle(hProcess);
hProcess = NULL;
/* CPU usage is calculated by getting the total amount of time
the system has operated since the last measurement
(made up of kernel + user) and the total
amount of time the process has run (kernel + user) */
ULONGLONG ftSysIdleDiff = SubtractTimes(ftSysIdle, m_ftPrevSysIdle);
ULONGLONG ftSysKernelDiff = SubtractTimes(ftSysKernel, m_ftPrevSysKernel);
ULONGLONG ftSysUserDiff = SubtractTimes(ftSysUser, m_ftPrevSysUser);
ULONGLONG ftProcKernelDiff = SubtractTimes(ftProcKernel, m_ftPrevProcKernel);
ULONGLONG ftProcUserDiff = SubtractTimes(ftProcUser, m_ftPrevProcUser);
ULONGLONG nTotalSys = ftSysKernelDiff + ftSysUserDiff;
ULONGLONG nTotalProc = ftProcKernelDiff + ftProcUserDiff;
if (nTotalSys > 0)
{
m_nCpuUsage = ((100.0 * nTotalProc) / nTotalSys);
}
m_ftPrevSysIdle = ftSysIdle;
m_ftPrevSysKernel = ftSysKernel;
m_ftPrevSysUser = ftSysUser;
m_ftPrevProcKernel = ftProcKernel;
m_ftPrevProcUser = ftProcUser;
m_dwLastRun = GetTickCount64();
nCpuCopy = m_nCpuUsage;
}
}
::InterlockedDecrement(&m_lRunCount);
return nCpuCopy;
}
ULONGLONG CpuUsage::SubtractTimes(const FILETIME& ftA, const FILETIME& ftB)
{
LARGE_INTEGER a, b;
a.LowPart = ftA.dwLowDateTime;
a.HighPart = ftA.dwHighDateTime;
b.LowPart = ftB.dwLowDateTime;
b.HighPart = ftB.dwHighDateTime;
return a.QuadPart - b.QuadPart;
}
bool CpuUsage::EnoughTimePassed()
{
const int minElapsedMS = 250; //milliseconds
ULONGLONG dwCurrentTickCount = GetTickCount64();
return (dwCurrentTickCount - m_dwLastRun) > minElapsedMS;
}
结束语
IntelliTask 应用程序使用了许多在 CodeProject 上发布的组件。非常感谢:
- 我的
CMFCListView
窗体视图(参见 源代码); - PJ Naughter 的
CVersionInfo
类。
进一步计划:我希望尽快添加对**服务**的支持,以及额外的进程**管理功能**。
历史
- 版本 1.03(2014 年 1 月 18 日):初始发布
- 将源代码从 CodeProject 迁移到 GitLab(2019 年 12 月 7 日)
- 版本 1.04(2022 年 1 月 7 日):更新了**关于**对话框,并添加了新的电子邮件地址
- 版本 1.05(2022 年 1 月 14 日):更新了 PJ Naughter 的
CVersionInfo
类。 - 版本 1.06(2022 年 2 月 4 日):更改了外部网站地址
- 版本 1.07(2022 年 4 月 28 日):将 LICENSE 添加到安装文件夹
- 版本 1.08(2022 年 9 月 9 日):在**关于框**对话框中添加了贡献者超链接
- 2022 年 12 月 23 日:将源代码从 GitLab 迁移到 GitHub
- 版本 1.09(2022 年 1 月 21 日):添加了 PJ Naughter 的单实例类
- 版本 1.10(2023 年 1 月 23 日):将 PJ Naughter 的
CVersionInfo
库更新到可用的最新版本更新了代码,对所有变量声明使用C++统一初始化。
- 版本 1.11(2023 年 1 月 24 日):将 PJ Naughter 的
CInstanceChecker
库更新到可用的最新版本更新了代码,对所有变量声明使用C++统一初始化。
- 将代码库中的
NULL
替换为nullptr
。
将代码库中的BOOL
替换为bool
。
这意味着该应用程序的最低要求现在是Microsoft Visual C++ 2010。 - 版本 1.12(2023 年 4 月 13 日):修复了进程列表更新和一些小的 UI 错误
- 版本 1.13(2023 年 4 月 14 日):修复了标题列排序
- 版本 1.14(2023 年 5 月 27 日):在**关于**对话框中添加了 GPLv3 通知
- 版本 1.15(2023 年 6 月 9 日):添加了双击显示文件属性的功能
- 版本 1.16(2023 年 6 月 15 日):使界面列宽持久化
- 版本 1.17(2023 年 7 月 22 日):用 PJ Naughter 的
CHLinkCtrl
库替换了旧的CHyperlinkStatic
类 - 版本 1.18(2023 年 8 月 20 日)
- 更改了文章的下载链接。更新了关于对话框(电子邮件和网站)
- 添加社交媒体链接:Twitter、LinkedIn、Facebook 和 Instagram
- 添加 GitHub 存储库的问题、讨论和 Wiki 的快捷方式
- 版本 1.19(2024 年 1 月 27 日):将 ReleaseNotes.html 和 SoftwareContentRegister.html 添加到 GitHub 仓库
- 版本 1.20(2024 年 2 月 3 日)
- 使用 Setup API 实现“设备管理器”功能
- 使用 Windows 注册表实现“已安装程序”功能
- 版本 1.21(2024 年 2 月 10 日)
- 使用 PJ Naughter 的
DtWinVer
库将操作系统版本添加到已安装程序对话框 - 重新设计了设备管理器功能。使用
CWndResizer
库使两个对话框都可调整大小
- 使用 PJ Naughter 的
- 版本 1.22(2024 年 2 月 21 日):将 MFC 应用程序的主题切换回原生 Windows。
- 版本 1.23(2024 年 4 月 8 日):将 PJ Naughter 的
DtWinVer
库更新到可用的最新版本。提供了一个新的 IsWindows11Version24H2 方法。
- 版本 1.24.1(2024 年 9 月 21 日)
- 修复了所有进程的 CPU 使用率,特别是 PID=0(系统进程)。
- 在帮助菜单中实现了 用户手册 选项。
- 在帮助菜单中添加了检查更新…选项。