Click here to Skip to main content
15,920,217 members
Articles / Desktop Programming / MFC

IntelliTask

Rate me:
Please Sign up or sign in to vote.
4.71/5 (56 votes)
8 Apr 2024GPL36 min read 146.6K   113   58
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;
}

TaskManager

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.

License

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


Written By
Software Developer NXP Semiconductors
Romania Romania
My professional background includes knowledge of analyst programmer for Microsoft Visual C++, Microsoft Visual C#, Microsoft Visual Basic, Sun Java, assembly for Intel 80x86 microprocessors, assembly for PIC microcontrollers (produced by Microchip Inc.), relational databases (MySQL, Oracle, SQL Server), concurrent version systems, bug tracking systems, web design (HTML5, CSS3, XML, PHP/MySQL, JavaScript).

Comments and Discussions

 
GeneralRe: Where? Pin
Ștefan-Mihai MOGA22-Jan-23 6:16
professionalȘtefan-Mihai MOGA22-Jan-23 6:16 
AnswerRe: Where? Pin
Sergey Alexandrovich Kryukov22-Jan-23 8:35
mvaSergey Alexandrovich Kryukov22-Jan-23 8:35 
GeneralRe: Where? Pin
Ștefan-Mihai MOGA22-Jan-23 8:39
professionalȘtefan-Mihai MOGA22-Jan-23 8:39 
GeneralRibbon UI Pin
.:floyd:.18-Jan-15 12:14
.:floyd:.18-Jan-15 12:14 
GeneralRe: Ribbon UI Pin
Ștefan-Mihai MOGA18-Jan-15 19:24
professionalȘtefan-Mihai MOGA18-Jan-15 19:24 
GeneralVote 5 Pin
Roger6518-Jan-15 11:34
Roger6518-Jan-15 11:34 
GeneralRe: Vote 5 Pin
Ștefan-Mihai MOGA18-Jan-15 19:24
professionalȘtefan-Mihai MOGA18-Jan-15 19:24 
GeneralRe: Vote 5 Pin
netizenk19-Jul-18 5:49
professionalnetizenk19-Jul-18 5:49 
Vertical space on monitors is typically very limited. That ribbon takes way too much vertical space for no reason...
QuestionCreateToolhelp32Snapshot() vs Win32_Process? Pin
dandy7218-Jan-15 7:28
dandy7218-Jan-15 7:28 
AnswerRe: CreateToolhelp32Snapshot() vs Win32_Process? Pin
Randor 23-Sep-17 11:15
professional Randor 23-Sep-17 11:15 
GeneralRe: CreateToolhelp32Snapshot() vs Win32_Process? Pin
dandy7224-Sep-17 9:37
dandy7224-Sep-17 9:37 
GeneralRe: CreateToolhelp32Snapshot() vs Win32_Process? Pin
Randor 29-Sep-17 1:13
professional Randor 29-Sep-17 1:13 
GeneralRe: CreateToolhelp32Snapshot() vs Win32_Process? Pin
dandy7229-Sep-17 7:28
dandy7229-Sep-17 7:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.