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();- 获取当前进程定义的 ID
- SetProcessID(DWORD dwProcessID);- 设置当前进程定义的 ID
- DWORD GetParentProcessID();- 获取当前进程定义的父进程 ID
- void SetParentProcessID(DWORD dwParentProcessID);- 设置当前进程定义的父进程 ID
- DWORD 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(系统进程)。
- 在帮助菜单中实现了 用户手册 选项。
- 在帮助菜单中添加了检查更新…选项。
 


