IntelliTask






4.73/5 (53 votes)
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.
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:
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 inth32ProcessID
),TH32CS_SNAPHEAPLIST
(includes all heaps of the process specified inth32ProcessID
in the snapshot.),TH32CS_SNAPMODULE
(includes all modules of the process specified inth32ProcessID
in the snapshot.),TH32CS_SNAPMODULE32
(includes all 32-bit modules of the process specified inth32ProcessID
in the snapshot when called from a 64-bit process.),TH32CS_SNAPPROCESS
(includes all processes in the system in the snapshot.), andTH32CS_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 theTH32CS_SNAPHEAPLIST
,TH32CS_SNAPMODULE
,TH32CS_SNAPMODULE32
, orTH32CS_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
.
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 theCreateToolhelp32Snapshot
function.lppe
: A pointer to aPROCESSENTRY32
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:
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;
}
The Architecture
Each process definition is contained in a CProcessData
class, with the following interface:
DWORD GetProcessID();
- Gets ID of the current process definitionSetProcessID(DWORD dwProcessID);
- Sets ID for the current process definitionDWORD GetParentProcessID();
- Gets parent's process ID for current process definitionvoid SetParentProcessID(DWORD dwParentProcessID);
- Sets parent's process ID for current process definitionDWORD GetPriority();
- Gets priority for current process definitionvoid SetPriority(DWORD dwPriority);
- Sets priority for current process definitionDOUBLE GetProcessorUsage();
- Gets CPU usage for current process definitionvoid SetProcessorUsage(DOUBLE cpuUsage);
- Sets CPU usage for current process definitionDWORD GetMemoryUsage();
- Gets memory usage for current process definitionvoid SetMemoryUsage(DWORD memUsage);
- Sets memory usage for current process definitionCString GetFileName();
- Gets file name for current process definitionvoid SetFileName(CString strFileName);
- Sets file name for current process definitionCString GetFilePath();
- Gets file path for current process definitionvoid SetFilePath(CString strFilePath);
- Sets file path for current process definitionCString GetDescription();
- Gets description for current process definitionvoid SetDescription(CString strDescription);
- Sets description for current process definitionCString GetCompany();
- Gets company for current process definitionvoid SetCompany(CString strCompany);
- Sets company for current process definitionCString GetVersion();
- Gets version for current process definitionvoid 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 listint GetSize();
- Gets the size of process definition listCProcessData* GetAt(int nIndex);
- Geta an process definition from listBOOL Refresh();
- Updates the status for each process definition from listBOOL InsertProcess(PROCESSENTRY32& pe32);
- Inserts a process definition into listCProcessData* UpdateProcess(DWORD dwProcessID);
- Updates a process definition from listBOOL 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:
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 availableUpdated 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 availableUpdated the code to use C++ uniform initialization for all variable declarations
- Replaced
NULL
throughout the codebase withnullptr
ReplacedBOOL
throughout the codebase withbool
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'sCHLinkCtrl
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
- Added Operating System version to Installed Programs dialog using PJ Naughter's
- 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.