Introduction
This tutorial is for programmatically controlling processes on the Windows 2000 Platform using VC++ 6. This tutorial may consist of a minimum of three parts. The following installment is mentioned at the end of every tutorial. At the time of writing this tutorial I was using Windows 2000 Professional with VC++ 6, but porting it to other versions of Windows or VC should not require too many changes, though I haven't tested that.
This tutorial, though exhaustive, is not the place to look for a quick patch. It is designed to enable you to learn how to handle Processes in relation to Services. The need for this tutorial came from the lack of such material on the internet, especially regarding Processes. Feedback and suggestions should be sent to me via email at r_thampi@hotmail.com. Kindly use a relevant subject line so as to differentiate your email from spam.
You may distribute this tutorial on the conditions that you do not change, edit, or delete anything in the tutorial including but not limited to the credits and that the tutorial is not sold for personal gains – monetary or other.
The examples in this tutorial do not follow a conventional Windows programming model. I have maintained the project as a simple console .exe. This is done to concentrate on the intended tasks, rather than illustrating the Windows programming model, which would include so much more code.
Outline
- Getting all services and their Pid's.
- What you need
- Functions involved, including a brief description of how they work
- Process.cpp
- Process.cpp dissected
- Part II.
- Appendix
A. Getting all services and their PID's
1:- What you need
To make playing around with processes easier, there is a helper library. In this tutorial I use the PSAPI (Process Status Application Programming Interface) library provided by Microsoft to explain Processes. The files in question are Psapi.h, Psapi.lib and Psapi.dll.
Psapi.h and Psapi.lib: These files contain all of the functions that will be discussed in the examples. These files must be included in the code and referenced in the project. You can download the installable for Windows 200 from http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en. (Incase the file location has changed, you might have to google for the installable.)
The installable file, when run, offers to install a lot more than needed for this project. To install just what is required, choose Custom and install Windows Core SDK.
Paspi.dll: After compilation, the files Psapi.h and Psapi.lib will attempt to refer to Psapi.dll. Therefore, this is a necessary file to get your exe working, so make sure to include this DLL along with your project before distribution. Unfortunately, this DLL does not generally come packaged with Windows, but comes along with the Windows Core SDK installer, but in case you need only the DLL you can download it using the following link:
http://www.microsoft.com/downloads/release.asp?releaseid=30337
It is expected that the reader understands basic C and is familiar with VC. If you are good at C programming, but have issues with setting up the project in VC you should turn to Appendix B.
2: - Functions involved, including a brief description of how they work
Before beginning the project it is worthwhile to understand the following functions. The functions are EnumProcesses
, OpenProcess
, and GetModuleBaseName
. It is OK if you do not understand them right away, because they are explained in detail in the code as well.
EnumProcesses
:- The EnumProcesses
function retrieves the process identifier (PID
) for each process in the system.
BOOL EnumProcesses(DWORD* pProcessIds, DWORD cb, DWORD* pBytesReturned);
The Parameters:
pProcessIds
: [out] This is a pointer to an array we define. The function will fill the list of process identifiers in this array.
cb
: [in] This is a DWORD
that contains the size of the pProcessIDs
array, in bytes.
pBytesReturned
: [out] This is a pointer to a DWORD
that windows will use to store the number of bytes returned into the pProcessIds
array.
As you can see, once you call this function you get all the data necessary to list the processes running on your computer system! The return value for EnumProcesses
, if there are no errors will be zero.
OpenProcess
:- The next function that you need to understand is OpenProcess
and as indicated by its name, the function gives you the handle to the process. It requires three parameters.
HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
The Parameters
dwDesiredAccess
: [in] This DWORD
should contain the desired (one or more) access rights you wish to have to the process and is crosschecked against the security descriptor for the process. If the caller has enabled the SeDebugPrivilege
privilege, the requested access is granted regardless of the contents of the security descriptor. Refer to Appendix A for a list of Access rights.
bInheritHandle
: [in] If the value in this variable is TRUE
, processes created will inherit the handle, otherwise the processes will not inherit this handle. You will understand what this implies when we get to the third part of this tutorial.
dwProcessId
: [in] This variable should contain the identifier of the process to open, in other words, the PID
of the process.
If the function succeeds, the return value is an open handle to the specified process and if it fails, the return value is NULL
.
GetModuleBaseName
:- The GetModuleBaseName
function retrieves the base name of the specified module. Part 2 of this tutorial deals with "modules" in depth. For now you just need to know that many modules make up a Process.
DWORD GetModuleBaseName(HANDLE hProcess, HMODULE hModule, LPTSTR lpBaseName, DWORD nSize);
The Parameters
hProcess
: [in] The Handle to the process that contains the module is available in this variable. To use this function, the handle must have PROCESS_QUERY_INFORMATION
and PROCESS_VM_READ
access rights. (Refer to Appendix A for what they stand for.)
hModule
: [in] This should contain the handle to the module. If this parameter is NULL
, this function returns the name of the file used to create the calling process.
lpBaseName
: [out] This variable gives you a pointer to the buffer that receives the base name of the module. If the base name is longer than the maximum number of characters specified by the nSize
parameter, the base name is truncated.
nSize
: [in] This should contain the size of the lpBaseName
buffer, in characters.
If the function succeeds, the return value specifies the length of the string copied to the buffer in characters and if the function fails the return value is zero.
3: - Process.cpp
include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>
void main()
{
DWORD ProcessesIDs[50], cbNeeded, cProcesses;
unsigned int i;
TCHAR szProcessName[50] = TEXT("<unknown>");
if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
return;
cProcesses = cbNeeded / sizeof(DWORD);
for ( i = 0; i < cProcesses; i++ )
{
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, ProcessesIDs[i] );
if (NULL != hProcess )
{
GetModuleBaseName( hProcess, NULL, szProcessName,
sizeof(szProcessName)/sizeof(TCHAR) );
}
_tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);
CloseHandle( hProcess );
}
}
4: - Process.cpp dissected
The code above can be copied to VC++ 6 and compiled, BUT remember to link in the include file Psapi.h and the library Psapi.lib. Refer to Appendix B for details.
Now let's try and understand the code itself. First you declare a couple of variables. The first three are ProcessesIDs[50]
, cbNeeded
and cProcesses
, all of type DWORD
.
The first, ProcessIDs
is an array of type DWORD
. This is used to store the PID's. If you have more than 50 processes running on your system, then you will need to increase the size of this array.
The second is cbNeeded
and is used to store the number of bytes returned into the array ProcessIDs
.
The third is cProcesses
. This is used to get the count of the number of processes running in the system.
The last variable szProcessName[50]
(The i
variable is simply used for the for
loop). This variable is used to store the module name. If you have processes running with a name more than 50 TCHAR
long, then you can increase the size of the array accordingly.
Now, after the declarations, lets move to the statements.
if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
return;
The function EnumProcesses
returns zero if it fails and a non-zero value if it succeeds, so you can safely use an if
check. If the function fails, you can stop further execution.
You can see that there are three arguments for EnumProcess
. Of the three arguments, the only argument that is providing a value into the function is the second argument that indicates the size of the first argument variable. In this case, the first argument variable (ProcessIDs
) is an array of DWORD
50 elements long. Since a DWORD
is four bytes long, the return value of sizeof(ProcessesIDs)
would be 4*50 = 200
bytes.
It does not matter what is contained in the argument variable ProcessIDs
. The function returns with ProcessesIDs
as a pointer to an array that will contain the list of PID
's. After the function is executed, you can go through the ProcessesIDs
array and read each PID
from each element.
The third argument is a pointer to a DWORD
. The function returns the actual count of bytes that the function returned in the ProcessIDs
array, so after the function is run you will have in cbNeeded
the number of bytes in ProcessesIDs
that contain the required data.
As an example, lets say there are 15 PID's. When EnumProcesses
is run, it returns with 15 PID
s in the ProcessesIDs
array and cbNeeded
indicates that only 15 elements have been filled. It does so by returning 15 * 4 = 60 bytes, where 4 is the size of one element, a DWORD
in bytes.
Now armed with the array of the PID
's running in the system in ProcessesIDs
, we can move to the next step, but before moving ahead you can make things easier if you store the number of PID
's returned. Therefore, add in the line:
cProcesses = cbNeeded / sizeof(DWORD);
The above code takes the size of a DWORD
, 4 bytes, and divides it with the value in cbNeeded
, which contains the total number of bytes returned to in array ProcessesIDs
. This will give the total count of PID
's stored in ProcessesIDs
and is stored in cProcesses
.
Now you need a loop to go through each PID
in the array ProcessesIDs
. For this you loop cProcesses
number of times. Once in the loop, you need the handle to the PID
that is in the element in ProcessesIDs[i]
. And that is exactly where the OpenProcess
function comes in.
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, ProcessesIDs[i] );
The OpenProcess
function also takes three arguments. The first indicates the kind of access you would like to have on that process. For a list of access rights and their meanings turn to Appendix A. The second is a BOOL
value. If this value is TRUE
, then the processes created by this process will inherit the handle. Since you do not need to create any other processes you can leave it as FALSE
. The last argument is the PID
to which you need a handle.
Once you put in all these inputs and call the function, the function returns the handle to the process, which is the stored in a variable called hProcess
and is checked to make sure that hProcess
is not NULL
.
Now look at the last function that will get the name of the process!
GetModuleBaseName( hProcess, NULL, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) );
This function has four arguments, for three of which you have to provide the data and it returns one argument. The first argument provides the handle to the Process, that is a return value from OpenProcess
. All you have to do is to use hProcess
here.
For the second argument (related to modules and explained in detail in Part 2 of this tutorial) just enter NULL
for now.
For the fourth argument, provide the the size of szProcessName
in relation as to how many characters it can hold. Remember, anything returned more than the array size will be truncated. For this take the size of the whole array in bytes and divide it with the size of a TCHAR
.
The third argument is the array to be returned from the function. It will contain the name of the Process. Viola…
And before the for
loop ends, you print the process name and PID
with this line:
_tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);
Close the handle using OpenProcess
as follows:
CloseHandle( hProcess );
B.Part II
The second part of this tutorial will enumerate more about processes and take a detailed view of modules in Processes.
C. Appendix
Appendix A:
The access rights can be:
Process name |
Description |
PROCESS_ALL_ACCESS |
All possible access rights for a process object |
PROCESS_CREATE_PROCESS |
Required to create a process |
PROCESS_CREATE_THREAD |
Required to create a thread |
PROCESS_DUP_HANDLE |
Required to duplicate a handle using DuplicateHandle |
PROCESS_QUERY_INFORMATION |
Required to retrieve certain information about a process, such as its token, exit code, and priority class |
PROCESS_QUERY_LIMITED_INFORMATION |
Required to retrieve certain information about a process |
PROCESS_SET_QUOTA |
Required to set memory limits using SetProcessWorkingSetSize |
PROCESS_SET_INFORMATION |
Required to set certain information about a process, such as its priority class |
PROCESS_SUSPEND_RESUME |
Required to suspend or resume a process |
PROCESS_TERMINATE |
Required to terminate a process using TerminateProcess |
PROCESS_VM_OPERATION |
Required to perform an operation on the address space of a process |
PROCESS_VM_READ |
Required to read memory in a process using ReadProcessMemory |
PROCESS_VM_WRITE |
Required to write to memory in a process using WriteProcessMemory |
SYNCHRONIZE |
Required to wait for the process to terminate using the wait functions |
Appendix B
Setting up VC for the project.
First determine whether your system already has the following files: Psapi.h, Psapi.lib and Psapi.dll. If you do, go to the "setting VC" section of this Appendix.
If not, download the PSAPI SDK re-distributable for NT from the following link: http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en. You may have to search for the SDK if the site has moved or is temporarily unavailable. The alternate sites are MSDN or Microsoft home.
Install this package, making a note of the directory where it is being installed. For example, if it installs in C:\Program Files\, then within the Program Files directory you will find another directory named Microsoft Platform SDK. This folder in turn would have two more folders named Lib and Include, both containing Psapi.lib and psapi.h respectively. The psapi.dll will be found inC:\WINNT\system32.
Setting Up VC
Open VC and go to File | New | Projects | Win32 Console Application. Enter a Project name and click OK.
Then click File | New | Files | C++ Source File. Enter a file name and click OK. The C++ source file opens up where you write your code.
- Paste the code from this document to the C++ source file you just created.
- Click Tools | Options | Directories. Under Show Directories for choose Include files.
- Double click on the empty path under the Directories label. (This space looks like a text area). After you double click, you will see an ellipse.
- Click the ellipse and browse to C:\Program Files\Microsoft Platform SDK\Include (or the path to the Include folder in Microsoft Platform SDK&).
- Click OK.
- Under Show Directories for select Library files and follow the earlier procedure to point to C:\Program Files\Microsoft Platform SDK\Lib.
- Click OK.
- To the left of the VC main window, under the File View tab, right click on Resource Files and click Add files to folder.
- On the browser that opens, point it to Psapi.lib.
- Click OK
- Press F5 to build
- Go to the command prompt and navigate to the project location.
- Execute the exe file and you will see all the
PID
s and names of all the processes in your system.