Monitoring processes with Delphi
The other day we were interested on listing the threads of a process with one of my colleagues. We started looking for some information on the internet and ended in a very interesting application from sourceforge. Before continuing, I've made several changes into the blog in order to gain more exposure. I've added the Feedjit widgets to show the list of trafic feed and page popularity and I've also added two more buttons showing my profile into delicious and flickr. And I've truncated the post to gain load pages and bandwidth.
Well, going on in our topic, I've managed to release a little application that shows all the processes and the threads on every process. It's a simple approximation, but it helps to understand how works the API by Using the CreateToolhelp32Snapshot function where we can point the list of process and go to each of them and gather their properties. For the threads, we can use the same function but using the threads functions to cover all of them.
I've got some of information from Experts-exchange and I found a little example from madshi and I've made some modifications and I've adapted the methods for my own intentions.
Here you can get the source code modified:
The example of creating the object TProcessMonitor is the following:
And the execution of the program:

Interesting applications that I've found (Open Source) are asmProfiler and YAPM (Yet another Process Monitor). All of them amazing, and I recommend to visit their pages and downloading the latest version.
Well, going on in our topic, I've managed to release a little application that shows all the processes and the threads on every process. It's a simple approximation, but it helps to understand how works the API by Using the CreateToolhelp32Snapshot function where we can point the list of process and go to each of them and gather their properties. For the threads, we can use the same function but using the threads functions to cover all of them.
I've got some of information from Experts-exchange and I found a little example from madshi and I've made some modifications and I've adapted the methods for my own intentions.
Here you can get the source code modified:
unit processMonitor;
interface
uses
Contnrs, Windows;
type
TExeType = (etUnknown, etDos, etWin16, etConsole, etWin32);
TWindowList = array of record
pid: cardinal;
tid: cardinal;
window: cardinal;
parent: cardinal;
owner: cardinal;
visible: boolean;
enabled: boolean;
inTaskbar: boolean;
rect: TRect;
title: string;
className: string;
end;
TThread = class(TObject)
private
Fwindows: TWindowList;
FpId: cardinal;
FtId: cardinal;
procedure SetpId(const Value: cardinal);
procedure SettId(const Value: cardinal);
procedure Setwindows(const Value: TWindowList);
public
property pId: cardinal read FpId write SetpId;
property tId: cardinal read FtId write SettId;
property windows: TWindowList read Fwindows write Setwindows;
function Clone() : TThread;
end;
TThreadList = class(TObjectList)
protected
function GetItem(Index: Integer): TThread; overload;
procedure SetItem(Index: Integer; AObject: TThread);
public
property Items[Index: Integer]: TThread read GetItem write SetItem; default;
end;
TProcess = class(TObject)
private
Fname: string;
FexeType: TExeType;
Fpid: cardinal;
Fthreads: TThreadList;
procedure SetexeType(const Value: TExeType);
procedure Setname(const Value: string);
procedure Setpid(const Value: cardinal);
procedure Setthreads(const Value: TThreadList);
public
property pid: cardinal read Fpid write Setpid;
property name: string read Fname write Setname;
property exeType: TExeType read FexeType write SetexeType;
property threads: TThreadList read Fthreads write Setthreads;
constructor Create();
destructor Destroy(); override;
end;
TProcessList = class(TObjectList)
protected
function GetItem(Index: Integer): TProcess; overload;
procedure SetItem(Index: Integer; AObject: TProcess);
public
property Items[Index: Integer]: TProcess read GetItem write SetItem; default;
end;
TProcessMonitor = class(TObject)
private
FListOfProcess: TProcessList;
FListOfThreads: TThreadList;
procedure SetListOfProcess(const Value: TProcessList);
procedure SetListOfThreads(const Value: TThreadList);
public
property ListOfProcess: TProcessList read FListOfProcess write SetListOfProcess;
property ListOfThreads: TThreadList read FListOfThreads write SetListOfThreads;
procedure FillListOfProcess();
procedure FillListOfThreads();
constructor Create();
destructor Destroy(); override;
end;
type
TACardinal = array[0..maxInt shr 2 - 1] of cardinal;
TPACardinal = ^TACardinal;
TDACardinal = array of cardinal;
const
MAX_MODULE_NAME32 = 255;
TH32CS_SnapProcess = 2;
TH32CS_SnapThread = 4;
TH32CS_SnapModule = 8;
type
TProcessEntry32 = record
dwSize: DWORD;
cntUsage: DWORD;
th32ProcessID: DWORD;
th32DefaultHeapID: DWORD;
th32ModuleID: DWORD;
cntThreads: DWORD;
th32ParentProcessID: DWORD;
pcPriClassBase: integer;
dwFlags: DWORD;
szExeFile: array[0..MAX_PATH - 1] of char;
end;
TThreadEntry32 = record
dwSize: DWORD;
cntUsage: DWORD;
th32ThreadID: DWORD;
th32OwnerProcessID: DWORD;
tpBasePri: integer;
tpDeltaPri: integer;
dwFlags: DWORD;
end;
TModuleEntry32 = record
dwSize: DWORD;
th32ModuleID: DWORD;
th32ProcessID: DWORD;
GlblcntUsage: DWORD;
ProccntUsage: DWORD;
modBaseAddr: pointer;
modBaseSize: DWORD;
hModule: HMODULE;
szModule: array[0..MAX_MODULE_NAME32] of char;
szExePath: array[0..MAX_PATH - 1] of char;
end;
implementation
uses
SysUtils, ShellAPI;
var
CreateToolhelp32Snapshot: function(dwFlags, th32ProcessID: cardinal): cardinal; stdcall = nil;
Process32First: function(hSnapshot: cardinal; var lppe: TProcessEntry32): bool; stdcall = nil;
Process32Next: function(hSnapshot: cardinal; var lppe: TProcessEntry32): bool; stdcall = nil;
Thread32First: function(hSnapshot: cardinal; var lpte: TThreadEntry32): bool; stdcall = nil;
Thread32Next: function(hSnapshot: cardinal; var lpte: TThreadEntry32): bool; stdcall = nil;
Module32First: function(hSnapshot: cardinal; var lpme: TModuleEntry32): bool; stdcall = nil;
Module32Next: function(hSnapshot: cardinal; var lpme: TModuleEntry32): bool; stdcall = nil;
EnumProcesses: function(idProcess: TPACardinal; cb: cardinal; var cbNeeded: cardinal): bool; stdcall = nil;
EnumProcessModules: function(hProcess: cardinal; var hModule: cardinal; cb: cardinal; var cbNeeded: cardinal): bool; stdcall = nil;
GetModuleFileNameEx: function(hProcess, hModule: cardinal; fileName: PChar; nSize: cardinal): cardinal; stdcall = nil;
ew_pid, ew_tid: cardinal;
ew_onlyThoseInTaskbar: boolean;
function NextFunctions: boolean;
var
c1: cardinal;
begin
c1 := GetModuleHandle(kernel32);
@CreateToolhelp32Snapshot := GetProcAddress(c1, 'CreateToolhelp32Snapshot');
@Process32First := GetProcAddress(c1, 'Process32First');
@Process32Next := GetProcAddress(c1, 'Process32Next');
@Thread32First := GetProcAddress(c1, 'Thread32First');
@Thread32Next := GetProcAddress(c1, 'Thread32Next');
@Module32First := GetProcAddress(c1, 'Module32First');
@Module32Next := GetProcAddress(c1, 'Module32Next');
result := (@CreateToolhelp32Snapshot <> nil) and
(@Process32First <> nil) and (@Process32Next <> nil) and
(@Thread32First <> nil) and (@Thread32Next <> nil) and
(@Module32First <> nil) and (@Module32Next <> nil);
end;
function GetExeType(exefile: string): TExeType;
var
c1: cardinal;
sfi: TSHFileInfo;
s1: string;
begin
c1 := SHGetFileInfo(pchar(exefile), 0, sfi, SizeOf(sfi), SHGFI_EXETYPE);
s1 := chr(c1 and $FF) + chr((c1 and $FF00) shr 8);
if s1 = 'MZ' then
result := etDos
else if s1 = 'NE' then
result := etWin16
else if (s1 = 'PE') and (hiWord(c1) = 0) then
result := etConsole
else if (s1 = 'PE') and (hiWord(c1) > 0) then
result := etWin32
else if CompareText(AnsiUpperCase(ExtractFileName(exefile)), AnsiUpperCase('winoa386.mod')) = 0 then
result := etDos
else
result := etUnknown;
end;
{ TThread }
function TThread.Clone: TThread;
var
res : TThread;
begin
res := TThread.Create;
res.FpId := Self.FpId;
res.FtId := Self.FtId;
res.Fwindows := Self.Fwindows;
result := res;
end;
procedure TThread.SetpId(const Value: cardinal);
begin
FpId := Value;
end;
procedure TThread.SettId(const Value: cardinal);
begin
FtId := Value;
end;
procedure TThread.Setwindows(const Value: TWindowList);
begin
Fwindows := Value;
end;
{ TThreadList }
function TThreadList.GetItem(Index: Integer): TThread;
begin
Result := TThread(inherited Items[Index]);
end;
procedure TThreadList.SetItem(Index: Integer; AObject: TThread);
begin
inherited Items[Index] := AObject;
end;
{ TProcess }
constructor TProcess.Create;
begin
Fthreads := TThreadList.Create;
end;
destructor TProcess.Destroy;
begin
FreeAndNil(Fthreads);
inherited;
end;
procedure TProcess.SetexeType(const Value: TExeType);
begin
FexeType := Value;
end;
procedure TProcess.Setname(const Value: string);
begin
Fname := Value;
end;
procedure TProcess.Setpid(const Value: cardinal);
begin
Fpid := Value;
end;
procedure TProcess.Setthreads(const Value: TThreadList);
begin
Fthreads := Value;
end;
{ TProcessMonitor }
constructor TProcessMonitor.Create;
begin
FListOfProcess := TProcessList.Create();
FListOfThreads := TThreadList.Create();
FillListOfProcess();
end;
destructor TProcessMonitor.Destroy;
begin
FreeAndNil(FListOfProcess);
FreeAndNil(FListOfThreads);
inherited;
end;
procedure TProcessMonitor.FillListOfProcess;
var
c1: cardinal;
pe: TProcessEntry32;
process: TProcess;
i1, i2: Integer;
begin
if not NextFunctions then exit;
c1 := CreateToolHelp32Snapshot(TH32CS_SnapProcess, 0);
try
pe.dwSize := sizeOf(pe);
if Process32First(c1, pe) then
repeat
process := TProcess.Create;
process.pid := pe.th32ProcessID;
process.name := pe.szExeFile;
process.exeType := GetExeType(process.name);
FListOfProcess.Add(process);
until not Process32Next(c1, pe);
finally
CloseHandle(c1)
end;
FillListOfThreads();
for i1 := 0 to FListOfThreads.count - 1 do
for i2 := 0 to FListOfProcess.count - 1 do
if FListOfThreads[i1].pid = FListOfProcess[i2].pid then
begin
FListOfProcess[i2].threads.Add(FListOfThreads[i1].clone);
end;
end;
procedure TProcessMonitor.FillListOfThreads;
function EnumWindowsProc(hwnd: cardinal; lParam: integer): LongBool; stdcall;
var
pwl: ^TWindowList;
i1: integer;
cpid, ctid: cardinal;
cpar, cown: cardinal;
bvis, btsk: boolean;
begin
result := true;
ctid := GetWindowThreadProcessID(hwnd, @cpid);
if ((ew_pid = 0) or (ew_pid = cpid)) and ((ew_tid = 0) or (ew_tid = ctid)) then
begin
bvis := IsWindowVisible(hwnd);
cown := GetWindow(hwnd, GW_OWNER);
cpar := GetParent(hwnd);
btsk := (cown = 0) and (cpar = 0) and bvis and (GetWindowLong(hwnd, GWL_EXSTYLE) and WS_EX_TOOLWINDOW = 0);
if (not ew_onlyThoseInTaskbar) or btsk then
begin
pwl := pointer(lParam);
i1 := length(pwl^);
SetLength(pwl^, i1 + 1);
with pwl^[i1] do
begin
window := hwnd;
parent := cpar;
owner := cown;
visible := bvis;
enabled := IsWindowEnabled(hwnd);
inTaskbar := btsk;
GetWindowRect(hwnd, rect);
SetLength(title, MAX_PATH);
SetLength(title, GetWindowText(hwnd, pchar(title), MAX_PATH));
SetLength(className, MAX_PATH);
SetLength(className, GetClassName(hwnd, pchar(className), MAX_PATH));
pid := cpid;
tid := ctid;
end;
end;
end;
end;
function GetWindowList(pid: cardinal = 0; tid: cardinal = 0; onlyThoseInTaskbar: boolean = false): TWindowList;
begin
result := nil;
ew_pid := pid;
ew_tid := tid;
ew_onlyThoseInTaskbar := onlyThoseInTaskbar;
if ew_tid = 0 then
EnumWindows(@EnumWindowsProc, integer(@result))
else
EnumThreadWindows(ew_tid, @EnumWindowsProc, integer(@result));
end;
var
c1: cardinal;
i1: integer;
te: TThreadEntry32;
thread: TThread;
begin
if not NextFunctions then exit;
c1 := CreateToolHelp32Snapshot(TH32CS_SnapThread, 0);
try
te.dwSize := sizeOf(te);
if Thread32First(c1, te) then
repeat
thread := TThread.Create();
thread.tId := te.th32ThreadID;
thread.pId := te.th32OwnerProcessID;
FListOfThreads.Add(thread);
until not Thread32Next(c1, te);
finally
CloseHandle(c1)
end;
for i1 := 0 to FListOfThreads.Count - 1 do
if (FListOfThreads[i1].pid <> 0) then
FListOfThreads[i1].windows := GetWindowList(FListOfThreads[i1].pid, FListOfThreads[i1].tid);
end;
procedure TProcessMonitor.SetListOfProcess(const Value: TProcessList);
begin
FListOfProcess := Value;
end;
procedure TProcessMonitor.SetListOfThreads(const Value: TThreadList);
begin
FListOfThreads := Value;
end;
{ TProcessList }
function TProcessList.GetItem(Index: Integer): TProcess;
begin
Result := TProcess(inherited Items[Index]);
end;
procedure TProcessList.SetItem(Index: Integer; AObject: TProcess);
begin
inherited Items[Index] := AObject;
end;
end.
The example of creating the object TProcessMonitor is the following:
var
processMonitor : TProcessMonitor;
implementation
procedure TFMain.Button1Click(Sender: TObject);
var
i : Integer;
begin
if Assigned(processMonitor) then
FreeAndNil(processMonitor);
processMonitor := TProcessMonitor.Create();
for i := 0 to processMonitor.ListOfProcess.Count - 1 do
ListBox1.AddItem('pid: ' + IntToStr(processMonitor.ListOfProcess[i].pid)
+ ' appName: ' + processMonitor.ListOfProcess[i].name
+ ' number of Threads: ' + Inttostr(processMonitor.ListOfProcess[i].threads.count-1),
processMonitor.ListOfProcess[i].threads);
end;
procedure TFMain.FormDestroy(Sender: TObject);
begin
FreeAndNil(processMonitor);
end;
procedure TFMain.ListBox1DblClick(Sender: TObject);
var
t : TThreadList;
i, j : integer;
begin
if ListBox1.Items.Count <>0 then
begin
Memo2.clear;
Memo1.clear;
t := TThreadList(ListBox1.Items.Objects[ListBox1.ItemIndex]);
for i := 0 to t.Count -1 do
begin
memo2.Lines.Add('pid: ' + IntToStr(t[i].tId));
for j := 0 to Length(t[i].windows)-1 do
begin
Memo1.lines.Add('pid: ' + IntToStr(t[i].windows[j].pid)
+ ' tid: ' + IntToStr(t[i].windows[j].tid)
+ ' window: ' + IntToStr(t[i].windows[j].window)
+ ' parent: ' + IntToStr(t[i].windows[j].parent)
+ ' owner: ' + IntToStr(t[i].windows[j].owner)
+ ' title: ' + t[i].windows[j].title
+ ' className: ' + t[i].windows[j].className);
end;
end;
end;
end;
And the execution of the program:

Interesting applications that I've found (Open Source) are asmProfiler and YAPM (Yet another Process Monitor). All of them amazing, and I recommend to visit their pages and downloading the latest version.
%20applied%20to%20Transformer%20models%20in%20machine%20learning.%20The%20image%20shows%20a%20neural%20networ.webp)

"asmProfiler" and "YAPM" point to the same url. You should fix YAPM's link.
ReplyDeleteDone!
DeleteThank you.
ReplyDeleteI try to compile here, but in the "for i := 0 to t.Count -1 do" say thar Count does not contain a member and "t[i].windows" is undeclared. Please, i am doing something wrong?
ReplyDeleteHi Abraão,
DeleteHave you declared T as a THreadList and added the correct uses on the usage section?
Jordi