Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

IntelliTask

4.73/5 (57 votes)
21 Sep 2024GPL36 min read 157.8K   852  
Task Manager shows you the programs, processes, and services that are currently running on your computer. You can use Task Manager to monitor your computer’s performance or to close a program that is not responding.
In this article, we do a brief overview of IntelliTask's parameters, return values, and architecture.

TaskManager

Introduction

Task Manager shows you the programs, processes, and services that are currently running on your computer. You can use Task Manager to monitor your computer’s performance or to close a program that is not responding.

Background

The easiest way to check the current running processes is to create a snapshot of memory. To accomplish this, we use CreateToolhelp32Snapshot, Process32First, and Process32Next functions, which has the following syntax:

C++
HANDLE WINAPI CreateToolhelp32Snapshot(
	DWORD dwFlags,
	DWORD th32ProcessID
);

Parameters

  • dwFlags: The portions of the system to be included in the snapshot. This parameter can be one or more of the following values: TH32CS_INHERIT (indicates that the snapshot handle is to be inheritable), TH32CS_SNAPALL (includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID), TH32CS_SNAPHEAPLIST (includes all heaps of the process specified in th32ProcessID in the snapshot.), TH32CS_SNAPMODULE (includes all modules of the process specified in th32ProcessID in the snapshot.), TH32CS_SNAPMODULE32 (includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit process.), TH32CS_SNAPPROCESS (includes all processes in the system in the snapshot.), and TH32CS_SNAPTHREAD (includes all threads in the system in the snapshot).
  • th32ProcessID: The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are included in the snapshot.

Return Value

If the function succeeds, it returns an open handle to the specified snapshot.

If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. Possible error codes include ERROR_BAD_LENGTH.

C++
BOOL WINAPI Process32First(
	HANDLE hSnapshot,
	LPPROCESSENTRY32 lppe
);
BOOL WINAPI Process32Next(
	HANDLE hSnapshot,
	LPPROCESSENTRY32 lppe
);

Parameters

  • hSnapshot: A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function.
  • lppe: A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the executable file, the process identifier, and the process identifier of the parent process.

Return Value

Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot does not contain process information.

So, our implementation using the above functions will be:

C++
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;
}

Image 2

The Architecture

Each process definition is contained in a CProcessData class, with the following interface:

  • DWORD GetProcessID(); - Gets ID of the current process definition
  • SetProcessID(DWORD dwProcessID); - Sets ID for the current process definition
  • DWORD GetParentProcessID(); - Gets parent's process ID for current process definition
  • void SetParentProcessID(DWORD dwParentProcessID); - Sets parent's process ID for current process definition
  • DWORD GetPriority(); - Gets priority for current process definition
  • void SetPriority(DWORD dwPriority); - Sets priority for current process definition
  • DOUBLE GetProcessorUsage(); - Gets CPU usage for current process definition
  • void SetProcessorUsage(DOUBLE cpuUsage); - Sets CPU usage for current process definition
  • DWORD GetMemoryUsage(); - Gets memory usage for current process definition
  • void SetMemoryUsage(DWORD memUsage); - Sets memory usage for current process definition
  • CString GetFileName(); - Gets file name for current process definition
  • void SetFileName(CString strFileName); - Sets file name for current process definition
  • CString GetFilePath(); - Gets file path for current process definition
  • void SetFilePath(CString strFilePath); - Sets file path for current process definition
  • CString GetDescription(); - Gets description for current process definition
  • void SetDescription(CString strDescription); - Sets description for current process definition
  • CString GetCompany(); - Gets company for current process definition
  • void SetCompany(CString strCompany); - Sets company for current process definition
  • CString GetVersion(); - Gets version for current process definition
  • void SetVersion(CString strVersion); - Sets version for current process definition

Then, we define CProcessList as typedef CArray<CProcessData*> CProcessList;.

This list is managed inside the CSystemSnapshot class, with the following interface:

  • BOOL RemoveAll(); - Removes all process definitions from list
  • int GetSize(); - Gets the size of process definition list
  • CProcessData* GetAt(int nIndex); - Geta an process definition from list
  • BOOL Refresh(); - Updates the status for each process definition from list
  • BOOL InsertProcess(PROCESSENTRY32& pe32); - Inserts a process definition into list
  • CProcessData* UpdateProcess(DWORD dwProcessID); - Updates a process definition from list
  • BOOL DeleteProcess(DWORD dwProcessID); - Removes a process definition from list

Points of Interest

The challenge for IntelliTask was to compute exact CPU usage for each process, as shown below:

C++
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;
}

Final Words

IntelliTask application uses many components that have been published on CodeProject. Many thanks to:

  • my CMFCListView form view (see source code);
  • PJ Naughter for his CVersionInfo class.

Further plans: I would like to add support for services, and additional management functions for processes as soon as possible.

History

  • Version 1.03 (January 18th, 2014): Initial release
  • Moved source code from CodeProject to GitLab (December 7th, 2019)
  • Version 1.04 (January 7th, 2022): Updated About dialog with new e-mail address
  • Version 1.05 (January 14th, 2022): Updated PJ Naughter's CVersionInfo class.
  • Version 1.06 (February 4th, 2022): Changed external website address
  • Version 1.07 (April 28th, 2022): Added LICENSE to installation folder
  • Version 1.08 (September 9th, 2022): Added Contributors hyperlink to AboutBox dialog
  • December 23rd, 2022: Moved source code from GitLab to GitHub
  • Version 1.09 (January 21st, 2022): Added PJ Naughter's Single Instance class
  • Version 1.10 (January 23rd, 2023): Updated PJ Naughter's CVersionInfo library to the latest version available
    Updated the code to use C++ uniform initialization for all variable declarations
  • Version 1.11 (January 24th, 2023): Updated PJ Naughter's CInstanceChecker library to the latest version available
    Updated the code to use C++ uniform initialization for all variable declarations
  • Replaced NULL throughout the codebase with nullptr
    Replaced BOOL throughout the codebase with bool
    This means that the minimum requirement for the application is now Microsoft Visual C++ 2010.
  • Version 1.12 (April 13th, 2023): Fixed processes' list update, and some minor UI bugs
  • Version 1.13 (April 14th, 2023): Fixed header column sorting
  • Version 1.14 (May 27th, 2023): Updated About dialog with GPLv3 notice
  • Version 1.15 (June 9th, 2023): Added show file properties on double click
  • Version 1.16 (June 15th, 2023): Made persistent the width of columns from interface
  • Version 1.17 (July 22nd, 2023): Replaced old CHyperlinkStatic class with PJ Naughter's CHLinkCtrl library
  • Version 1.18 (August 20th, 2023):
    • Changed article's download link. Updated the About dialog (email & website)
    • Added social media links: Twitter, LinkedIn, Facebook, and Instagram
    • Added shortcuts to GitHub repository's Issues, Discussions, and Wiki
  • Version 1.19 (January 27th, 2024): Added ReleaseNotes.html and SoftwareContentRegister.html to GitHub repo
  • Version 1.20 (February 3rd, 2024)
    • Implemented "Device Manager" functionality using Setup API
    • Implemented "Installed Programs" functionality using Windows registry
  • Version 1.21 (February 10th, 2024):
    • Added Operating System version to Installed Programs dialog using PJ Naughter's DtWinVer library
    • Reworked the Device Manager functionality. Made both dialog resizable using CWndResizer library
  • Version 1.22 (February 21st, 2024): Switched MFC application' theme back to native Windows.
  • Version 1.23 (April 8th, 2024): Updated PJ Naughter's DtWinVer library to the latest version available.
    Provided a new IsWindows11Version24H2 method.
  • Version 1.24.1 (September 21st, 2024):
    • Fix CPU Usage for all processes, especially PID=0 (System Process).
    • Implemented User Manual option into Help menu.
    • Implemented Check for updates... option into Help menu.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)