In this article, we do a brief overview of IntelliTask's parameters, return values, and architecture.
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 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
.
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:
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 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:
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));
}
DOUBLE CpuUsage::GetUsage()
{
HANDLE hProcess = NULL;
DOUBLE nCpuCopy = m_nCpuUsage;
if (::InterlockedIncrement(&m_lRunCount) == 1)
{
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;
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; 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.