
Introduction
This program will show you several ways to find out what other programs, tasks, and DLLs are running in Windows at any given time. You will see how you can determine the main-level windows that are on the screen, as well as the actual tasks running (which may or may not have a window on the screen) and the DLLs that are loaded by programs.
Using this methodology doesn't mean you can replace Windows Task Manager, but it can show you how to get to other running processes when you need to do so in your applications.
Technique
A task is a running Windows application, whether it is displaying a window on the screen or not. A task may have several windows associated with it. The main window is the "parent" window on the desktop, and all other windows are "children" of the parent. Child windows normally are displayed within the borders of the main window but can appear outside of it. In either case, all child windows belong to a task as well.
Steps
I will give you the steps of creating the code. As we go along the code, I will explain much of the functionality as comments in the source code so you can catch up with the specific elements of the code. Then I will summarize how it all works.
1.
Create a simple Dialog based application using MFC AppWizard (exe).
2.
In the Dialog Resource Editor, add a list box and three buttons and label them as shown in the screen shot. Delete the Cancel button and change the caption of the OK button to read "Close".
3.
Add the following code to the OnInitDialog()
member function of the dialog just before the function returns:
CenterWindow();
if(!InitToolhelp32())
{
MessageBox("Unable to initialize tool help functions", "Error",MB_OK);
EndDialog(IDCANCEL);
return FALSE;
}
The function InitToolhelp32()
is an initializer function that we will create to get to the Windows kernel and use it to call a number of other functions that we will use to deal with processes, modules, and threads.
4.
Add the following code to the BN_CLICKED
message handler of the Process button:
HANDLE hSnapshot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
if(!hSnapshot)
return;
CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST1);
pList->ResetContent();
pe.dwSize = sizeof(pe);
for(int i = pProcess32First(hSnapshot, &pe); i; i=
pProcess32Next(hSnapshot, &pe))
{
HANDLE hModuleSnap = NULL;
MODULEENTRY32 me;
hModuleSnap = pCreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
pe.th32ProcessID);
if(hModuleSnap == (HANDLE) -1)
return;
me.dwSize = sizeof(MODULEENTRY32);
if(pModule32First(hModuleSnap, &me))
{
do
{
if(me.th32ModuleID == pe.th32ModuleID)
{
pList->AddString(me.szExePath);
break;
}
}
while(pModule32Next(hModuleSnap, &me));
}
}
CloseHandle(hSnapshot);
5.
Add the following code to the BN_CLICKED
message handler of the Windows button:
CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST1);
pList->ResetContent();
FARPROC EnumProcInstance = MakeProcInstance((FARPROC)EnumWindowsProc,
AfxGetInstanceHandle());
EnumWindows((WNDENUMPROC)EnumProcInstance, (LPARAM)pList);
FreeProcInstance(EnumProcInstance);
UpdateData();
6.
Add the following code just above the handler for the Windows button:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
CListBox* pList = (CListBox*)lParam;
char buffer[256];
GetWindowText(hWnd, buffer, 256);
if(strlen(buffer))
pList->AddString(buffer);
return TRUE;
}
7.
Add the following code to the BN_CLICKED
message handler of the Modules button:
MODULEENTRY32 me;
CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST1);
pList->ResetContent();
memset(&me, 0, sizeof(me));
me.dwSize = sizeof(MODULEENTRY32);
HANDLE hSnapshot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(!hSnapshot)
return;
for(int i = pModule32First(hSnapshot, &me); i; i=
pModule32Next(hSnapshot, &me))
{
pList->AddString(me.szExePath);
}
CloseHandle(hSnapshot);
8.
Add the following declarations and code to the top of the dialog's .cpp file:
typedef BOOL (WINAPI* MODULEWALK)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
typedef BOOL (WINAPI* THREADWALK)(HANDLE hSnapshot, LPTHREADENTRY32 lpte);
typedef BOOL (WINAPI* PROCESSWALK)(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
typedef HANDLE (WINAPI* CREATESNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);
static CREATESNAPSHOT pCreateToolhelp32Snapshot = NULL;
static MODULEWALK pModule32First = NULL;
static MODULEWALK pModule32Next = NULL;
static PROCESSWALK pProcess32First = NULL;
static PROCESSWALK pProcess32Next = NULL;
static THREADWALK pThread32First = NULL;
static THREADWALK pThread32Next = NULL;
BOOL InitToolhelp32(void)
{
BOOL bRet = FALSE;
HINSTANCE hKernel = NULL;
hKernel = GetModuleHandle("KERNEL32.DLL");
if(hKernel)
{
pCreateToolhelp32Snapshot =
(CREATESNAPSHOT)GetProcAddress(hKernel, "CreateToolhelp32Snapshot");
pModule32First = (MODULEWALK)GetProcAddress(hKernel,"Module32First");
pModule32Next = (MODULEWALK)GetProcAddress(hKernel,"Module32Next");
pProcess32First=(PROCESSWALK)GetProcAddress(hKernel,"Process32First");
pProcess32Next = (PROCESSWALK)GetProcAddress(hKernel,"Process32Next");
pThread32First = (THREADWALK)GetProcAddress(hKernel,"Thread32First");
pThread32Next = (THREADWALK)GetProcAddress(hKernel,"Thread32Next");
bRet = pModule32First && pModule32Next && pProcess32First &&
pProcess32Next && pThread32First && pThread32Next &&
pCreateToolhelp32Snapshot;
}
else
bRet = FALSE;
return bRet;
}
9.
Finally, add the following include file line to the dialog's .cpp file:
#include <tlhelp32.h>
Then compile and run the application.
How it works
Here, we are using three different Windows API function sets.
The first method, listing processes, makes use of the Windows API functions Process32First()
and Process32Next()
. To use these functions, the programmer must first initialize the Toolhelp functionality by loading the functions from the Kernel32.dll. This is accomplished using the InitToolhelp32()
function defined in step 8. These functions will "walk" the process list, returning process identifiers for each running process. From these identifiers, the Module functions (Module32First()
and Module32Next()
) are used to walk through the modules to find the corresponding names of the processes.
The second method, listing windows, uses a more advanced and slightly less intuitive set of Windows API functions, the MakeProcInstance()
and EnumWindows()
functions. MakeProcInstance()
takes the address of a function in your program and converts it into something called FARPROC
. This FARPROC
is then passed to Windows to iterate through all of the open windows on the desktop. Note that only main-level windows are listed by the function. The function that is called is EnumWindowsProc()
and accepts two arguments. The first will be the handle of the window that is currently being examined, and the second is a user-defined argument that is passed to the EnumWindows()
function. In our case, the argument passed is the CListBox
member that we would like data stored in. In the EnumWindowsProc()
function (also known as a callback function, since it is called by Windows), the title of the window is stored in the list box for the user to view.