65.9K
CodeProject 正在变化。 阅读更多。
Home

IntelliTask

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (53投票s)

2015 年 1 月 18 日

GPL3

6分钟阅读

viewsIcon

159314

downloadIcon

852

任务管理器会显示您计算机上当前正在运行的程序、进程和服务。您可以使用任务管理器来监控计算机的性能,或者关闭无响应的程序。

TaskManager

引言

任务管理器会显示您计算机上当前正在运行的程序、进程和服务。您可以使用任务管理器来监控计算机的性能,或者关闭无响应的程序。

背景

检查当前正在运行进程的最简单方法是创建内存快照。为此,我们使用 CreateToolhelp32SnapshotProcess32FirstProcess32Next 函数,其语法如下:

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_SNAPHEAPLISTTH32CS_SNAPMODULETH32CS_SNAPMODULE32TH32CS_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.htmlSoftwareContentRegister.html 添加到 GitHub 仓库
  • 版本 1.20(2024 年 2 月 3 日)
    • 使用 Setup API 实现“设备管理器”功能
    • 使用 Windows 注册表实现“已安装程序”功能
  • 版本 1.21(2024 年 2 月 10 日)
    • 使用 PJ Naughter 的 DtWinVer 库将操作系统版本添加到已安装程序对话框
    • 重新设计了设备管理器功能。使用 CWndResizer 库使两个对话框都可调整大小
  • 版本 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(系统进程)。
    • 在帮助菜单中实现了 用户手册 选项。
    • 在帮助菜单中添加了检查更新…选项。
© . All rights reserved.