Win32 API Programming with Visual Basic

Page 160
  11. Processes  
   
  As we have seen, a process is a running instance of an application, together with a set of resources that are allocated to the running application. These resources include:  
   
  A virtual address space.  
   
  System resources, such as bitmaps, files, memory allocations, and so on.  
   
  Process modules, that is, executables that are mapped (loaded) into the process's address space. This includes such things as DLLs, DRVs, and OCXs, as well as the main EXE of the process, which is, unfortunately, sometimes referred to as the module. We will elaborate on the issue of modules later in this chapter. However, it is important to understand that the actual module data (and code) either resides on disk or is loaded into physical memory (RAM). However, the term loaded has a different meaning with respect to a process's virtual address space. The term mapped is more appropriate because this mapping is simply an assignment of virtual addresses to physical addresses. Once a module is loaded into physical memory, those physical addresses can be mapped into multiple virtual address spaces, perhaps using different virtual addresses in each process. The mapping does not necessarily require that any actual data (or code) be physically moved (although it might, as we will see).  
   
  A unique identification number, called a process ID.  
   
  One or more threads of execution.  
   
  A thread is an object within a process that is allocated processor time by the operating system. Every process must have at least one thread. More specifically, a thread includes:  
Page 161
   
  The current contents of the CPU registers  
   
  Two stacks one for the thread to use when running in kernel mode and one for the thread to use when running in user mode  
   
  A private storage area for use by subsystems, runtime libraries, and DLLs  
   
  A unique identifier called a thread ID  
   
  The register contents, stack contents, and storage area contents are referred to as the thread's context.  
   
  As mentioned, the purpose of threads is to allow a process to maintain more than one line of execution, that is, to do more than one thing at a time. In a multiprocessor environment (a computer with more than one CPU), Windows NT (but not Windows 9x) can assign different threads to different processors at different times, providing true multiprocessing. In a single-processor environment, the CPU must provide time slices to each thread that is currently running on the system.  
   
  We will devote this chapter to a discussion of processes, leaving threads for the next chapter.  
 
  Process Handles and IDs  
   
  The distinction between process IDs and handles can be gleaned by looking at the CreateProcess API function. One possible VB declaration of this rather complex function is:  
 
  Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" ( _

   ByVal lpApplicationName As String, _

   ByVal lpCommandLine As String, _

   lpProcessAttributes As SECURITY_ATTRIBUTES, _

   lpThreadAttributes As SECURITY_ATTRIBUTES, _

   ByVal bInheritHandles As Long, _

   ByVal dwCreationFlags As Long, _

   lpEnvironment As Any, _

   ByVal lpCurrentDirectory As String, _

   lpStartupInfo As STARTUPINFO, _

   lpProcessInformation As PROCESS_INFORMATION _

) As Long

 
   
  Now, the last parameter is a pointer to a PROCESS_INFORMATION structure:  
 
  typedef struct _PROCESS_INFORMATION {

    HANDLE hProcess;

    HANDLE hThread;

    DWORD dwProcessId;

    DWORD dwThreadId;

} PROCESS_INFORMATION;

 
   
  where, to quote the documentation, the members are as follows.  
Page 162
 
  hProcess

Returns a handle to the newly created process. The handle is used to specify the process in all functions that perform operations on the process object.

 
 
  hThread

Returns a handle to the primary thread of the newly created process. The handle is used to specify the thread in all functions that perform operations on the thread object.

 
 
  dwProcessId

Returns a global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.

 
 
  dwThreadId

Returns a global thread identifier that can be used to identify a thread. The value is valid from the time the thread is created until the time the thread is terminated.

 
   
  Thus, the main differences between a process handle and a process ID (or thread handle and thread ID) are:  
   
  A process handle is process-specific, whereas a process ID is valid system-wide. Thus, a process handle, whether it be for the current process or a foreign process, is valid only in the process in which it was created.  
   
  Each process has one and only one process ID, but a process may have several process handles, even from within a single process.  
   
  Some API functions require the process ID; others require a process handle.  
   
  We should emphasize that although a process handle is process-specific, one process can get a handle to another process. More specifically, if Process A has a handle to Process B, then that handle identifies Process B, but it is valid only in Process A. The handle can be used in Process A to call some API functions that relate to Process B. (Don't get too excited about this Process B's memory is still inaccessible to Process A.)  
 
  Module Handles  
   
  Every module (DLL, OCX, DRV, etc.) that is loaded into a process space has a module handle, also called an instance handle. (In 16-bit Windows, these were different objects: in 32-bit Windows, they are the same.)  
   
  An executable's module handle is simply the starting or base address of the executable in the process's address space. This makes it rather clear that such a handle has meaning only within the process containing the module.  
Page 163
   
  The default base address of an EXE created using Visual C++ is &H400000. Note, however, that the VC++ programmer can change this default address, and there are important reasons to do so in order to avoid conflict with other modules. (As we will see, relocation of a module due to a base address conflict is costly in terms of both time and physical memory.) In addition, modules created by other programming environments may have different default base addresses.  
   
  Module handles are often used for calling API functions that allocate resources to the process. For instance, the LoadBitmap function loads a bitmap resource from the process's EXE file. Its declaration is:  
   
  HBITMAP LoadBitmap(  
 
 
  HINSTANCE hInstance,  
   
  // handle to application instance  
 
  LPCTSTR lpBitmapName  
   
  // address of bitmap resource name  
   
  );  

   
  The first parameter is the process's instance handle.  
   
  Unfortunately, the module handle for a process's EXE is often confusingly referred to as the module handle for the process. Also, the name of the EXE is sometimes referred to as the process's module name.  
 
  Identifying a Process  
   
  The following four items occur frequently in process-related API programming:  
   
  process ID  
   
  process handle  
   
  fully qualified filename of EXE  
   
  module handle of process EXE  
   
  The question is: ''Given one of these items, can we get the others?" Figure 11-1 illustrates the rather complex answer to this question. The arrows indicate the possibilities.  
   
  A: Getting a Process Handle from the Process ID  
   
  It is relatively easy to get a process handle from the process ID, but doing the reverse does not seem possible (at least in any direct way).  
   
  We can get a handle to any process from its process ID using the OpenProcess function:  
   
  HANDLE OpenProcess(  
 
  DWORD dwDesireAccess,  
   
  // access flag  
 
  BOOL bInheritHandle,  
   
  // handle inheritance flag  
 
  DWORD dwProcessId  
   
  // process identifier  
   
  );  

Page 164
   
   
   
  Figure 11-1.

IDs, handles, and so on

 
   
  The dwDesiredAccess parameter refers to security rights and can be set to various values, such as:  
 
  PROCESS_ALL_ACCESS

Equivalent to specifying all access flags

 
 
  PROCESS_DUP_HANDLE

Can use the process handle as either the source or target process in the DuplicateHandle function (discussed later) to duplicate a handle

 
 
  PROCESS_QUERY_INFORMATION

Can use the process handle to read information from the process object

 
 
  PROCESS_VM_OPERATION

Can use the process handle to modify the virtual memory of the process

 
Page 165
 
  PROCESS_TERMINATE

Can use the process handle in the TerminateProcess function to terminate the process

 
 
  PROCESS_VM_READ

Can use the process handle in the ReadProcessMemory function to read from the virtual memory of the process

 
 
  PROCESS_VM_WRITE

Can use the process handle in the WriteProcessMemory function to write to the virtual memory of the process

 
 
  SYNCHRONIZE (Windows NT)

Can use the process handle in any of the wait functions (such as WaitForSingleObject, discussed in Chapter 12, Threads) to wait for the process to terminate.

 
   
  The bInheritHandle parameter is set to True to allow child processes to inherit the current handle when the current process creates a child process. (The handle value may change, but the point is that the child gets a handle to its parent. We won't be concerned with process inheritance.)  
   
  The dwProcessID parameter should be set to the process ID of the process whose handle we desire. The OpenProcess function returns a handle to the process.  
   
  The VB declaration of OpenProcess is:  
 
  Declare Function OpenProcess Lib "kernel32" ( _

   ByVal dwDesiredAccess As Long, _

   ByVal bInheritHandle As Long, _

   ByVal dwProcessId As Long _

) As Long

 
   
  Since getting a process handle from the process ID will come up from time to time, let us create a small function for this purpose. Note that access to the process is for getting information only. The function is:  
 
  Public Function ProcHndFromProcID(ByVal lProcID As Long) As Long

' DON'T FORGET TO CLOSE HANDLE

ProcHndFromProcID = OpenProcess ( _

   PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0&, lProcID)

End Function

 
   
  For Windows NT, we should add an additional flag for use with thread synchronization (discussed in Chapter 12):  
 
  Public Function ProcHndFromProcIDSync(ByVal lProcID As Long) As Long

' DON'T FORGET TO CLOSE HANDLE

ProcHndFromProcIDSync = OpenProcess( _

   PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ Or SYNCHRONIZE, 0&, lProcID)

End Function

 
Page 166
   
  It is very important to remember that the returned handle must be closed after it is no longer needed. This is done by calling the CloseHandle function:  
 
  BOOL CloseHandle(

   HANDLE hObject // handle to object to close

);

 
   
  or, in VB:  
 
  Private Declare Function CloseHandle Lib "kernel32" ( _

   ByVal hObject As Long _

) As Long

 
   
  We will see examples of using OpenProcess a bit later in this chapter.  
   
  B: Module Filenames and Module Handles  
   
  It is not difficult to go from a module filename to a module handle and vice versa, at least from within the process itself. The GetModuleFileName function  
   
  DWORD GetModuleFileName(  
 
  HMODULE hModule,  
   
  // handle to module  
 
  LPTSTR lpFilename,  
   
  // pointer to buffer to receive module path  
 
  DWORD nSize  
   
  // size of buffer, in characters  
   
  );  

   
  uses the module's handle to retrieve the fully qualified filename (name and path) of the executable file. Note, however, that this function works from within the process in question we cannot use this function from another process.  
   
  Conversely, from within a process, the GetModuleHandle function returns the module's handle from its filename (no path here):  
   
  HMODULE GetModuleHandle(  
 
  LPCTSTR lpModuleName  
   
  // module name  
   
  );  

   
  Again, this does not work for retrieving module handles in foreign processes.  
   
  These two function declarations can be translated into VB as follows:  
 
  Declare Function GetModuleFileName Lib "kernel32" _

   Alias "GetModuleFileNameA" ( _

   ByVal hModule As Long, _

   ByVal lpFileName As String, _

   ByVal nSize As Long _

) As Long

Declare Function GetModuleHandle Lib "kernel32" _

   Alias "GetModuleHandleA" ( _

   ByVal lpModuleName As String _

) As Long

 
   
  Given either the module's handle, name, or fully qualified name, the function in Example 11-1 will retrieve the other two items in its OUT parameters.  
Page 167
   
  Example 11-1. Getting a Module's Handle, Name, or Fully Qualified Name  
   
  Public Function Proc_ModuleInfo(ByRef lHnd As Long, _

   ByRef sName As String, _

   ByRef sFQName As String) As Long

' Algorithm:

' If lHnd = -1 then get EXE handle, name and FQ name

' ElseIf lHnd > 0 then get module name and fully qualified name

' ElseIf sName <>   then get handle and FQ name

' ElseIf sFQName <>   then get handle and name

'' Required: Public Const MAX_PATH = 260

Dim lret As Long, x as Integer

Dim hModule As Long, s As String

Proc_ModuleInfo = 0

If lHnd = -1 Or lHnd > 0 Then

   hModule = lHnd ' Get handle

   If lHnd = -1 Then ' Get EXE handle

      hModule = GetModuleHandle(vbNullString)

      If hModule = 0 Then

         Proc_ModuleInfo = 1

         Exit Function

      End If

   End If

   ' Get names from handle

   s = String(MAX_PATH + 1, 0)

   lret = GetModuleFileName(hModule, s, MAX_PATH)

   sFQName = Left(s, lret)

   x = InStrRev(sFQName,  \ )

   If x > 0 Then sName = Mid$(sFQName, x + 1)

ElseIf sName <>   Then

   ' Get handle and FQ name from name

   hModule = GetModuleHandle(sName)

   If hModule = 0 Then

     Proc_ModuleInfo = 2

      Exit Function

   Else

      lHnd = hModule

      s = String(MAX_PATH + 1, 0)

      lret = GetModuleFileName(hModule, s, MAX_PATH)

      sFQName = Left(s, lret)

   End If

ElseIf sFQName <>   Then

   'Get handle and name from FQ name

   hModule = GetModuleHandle(sFQName)

   If hModule = 0 Then

 
Page 168
   
  Example 11-1. Getting a Module's Handle, Name, or Fully Qualified Name (continued)  
   
        Proc_ModuleInfo = 3

      Exit Function

Else

      lHnd = hModule

      x = InStrRev(sFQName,  \ )

      If x > 0 Then sName = Mid$(sFQName, x + 1)

   End If

End If

End Function

 
   
  Example 11-2 shows some code that exercises this function. The output on my system is shown in Example 11-3.  
   
  Example 11-2 Calling the Proc_ModuleInfo Function  
   
  Public Sub Proc_ModuleInfoExample

Dim sModName As String, sFQModName As String, hMod As Long

Dim lret As Long

hMod = -1: sModName =  : sFQModName = 

lret = Proc_ModuleInfo(hMod, sModName, sFQModName)

Debug.Print  Handle: &H  & Hex(hMod)

Debug.Print  Name:   & sModName

Debug.Print  FQName:   & sFQModName

Debug.Print

hMod = 0: sModName =  User32.dll : sFQModName = 

lret = Proc_ModuleInfo(hMod, sModName, sFQModName)

Debug.Print  Handle: &H  & Hex(hMod)

Debug.Print  Name:   & sModName

Debug.Print  FQName:   & sFQModName

Debug.Print

hMod = 0: sModName =  : sFQModName =  C:\WINNT\system32\USER32.dll

lret = Proc_ModuleInfo(hMod, sModName, sFQModName)

Debug.Print  Handle: &H  & Hex(hMod)

Debug.Print  Name:   & sModName

Debug.Print  FQName:   & sFQModName

Debug.Print

hMod = 2011627520: sModName =  : sFQModName = 

lret = Proc_ModuleInfo(hMod, sModName, sFQModName)

Debug.Print  Handle: &H  & Hex(hMod)

Debug.Print  Name:   & sModName

Debug.Print  FQName:   & sFQModName

Debug.Print

End Sub

 
Page 169
   
  Example 11-3. Output from Example 11-2  
   
  Handle: &HFFFFFFFF

Name: VB6.EXE

FQName: G:\Visual Studio\VB98\VB6.EXE

Handle: &H77E70000

Name: User32.dll

FQName: C:\WINNT\system32\USER32.dll

Handle: &H77E70000

Name: USER32.dll

FQName: C:\WINNT\system32\USER32.dll

Handle: &H77E70000

Name: USER32.dll

FQName: C:\WINNT\system32\USER32.dll

 
   
  C: Getting the Current Process ID  
   
  To get the process ID of the current process, we just use GetCurrentProcessId, which is declared as:  
 
  DWORD GetCurrentProcessId(VOID)  
   
  or, in VB:  
 
  Declare Function GetCurrentProcessId Lib "kernel32" () As Long  
   
  This simply returns the process ID. Note that process IDs can be in the upper range of an unsigned long, so a conversion of the return type may be necessary. Note also that this works only for the current process. There is no way (of which I am aware) to get the process ID of a foreign process, other than possibly listing all processes and picking the one you want using some other criteria.  
   
  D: Getting the Process ID from a Window  
   
  In Chapter 6, Strings, we discussed the FindWindow function:  
   
  HWND FindWindow(  
   
  LPCTSTR lpClassName,  
   
  // pointer to class name  
   
  LPCTSTR lpWindowName  
   
  // pointer to window name  
   
  );  

   
  or, in VB:  
 
  Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _

   ByVal lpClassName As String, _

   ByVal lpWindowName As String _

) As Long

 
   
  This function uses either the class name or the window's caption to retrieve a handle to the window. From this handle, we can call GetWindowThreadProcessId,  
Page 170
   
  which retrieves the thread ID of the thread that created the window, as well as the process ID of the process that owns that thread. The syntax is:  
 
  DWORD GetWindowThreadProcessId(

   HWND hWnd,             // handle to window

   LPDWORD lpdwProcessId // address of variable for process identifier

);

 
   
  This function returns the thread ID. Moreover, when the function is passed a pointer to a DWORD in lpdwProcessId (as opposed to being passed 0), the function will return the process ID in the target variable.  
   
  We can translate this into VB as follows:  
 
  Declare Function GetWindowThreadProcessId Lib "user32" ( _

   ByVal hwnd As Long, _

   lpdwProcessId As Long _

) As Long

 
   
  Here is a short function that returns the process ID from a window handle:  
 
  Public Function ProcIDFromhWnd(ByVal hWnd As Long) As Long

Dim lret As Long, hProcessID As Long

lret = GetWindowThreadProcessId(hWnd, hProcessID)

ProcIDFromhWnd = hProcessID

End Function

 
   
  We will discuss window handles in more detail in Chapter 15, Windows: The Basics.  
   
  E: Getting Module Names and Handles  
   
  A single process generally has many modules loaded in its address space, and this naturally complicates the issue of getting the module handles and names. While it is not difficult to do so, we must unfortunately use entirely different methods in Windows 9x and Windows NT.  
   
  Windows NT 4.0 requires the use of a DLL called PSAPI.DLL (which stands for Process Status API). This DLL, which is not compatible with Windows 9x, exports functions to enumerate all of the processes in the system, as well as all of the device drivers. It can also get information about all of the modules running in a given process. There will be an example that makes use of this DLL later in this chapter. To enumerate the threads in a Windows NT system, we need to use the Performance Data Helper library (PDH.DLL) that comes with the NT Resource Toolkit. However, since we don't have any real need for enumerating threads, we will not do so.  
   
  On the other hand, Windows 9x supports the Toolhelp functions in the Windows 9x version of KERNEL32.DLL. These functions can be used to take a snapshot of  
Page 171
   
  the process space for any process. From this snapshot, we can get all sorts of information, including the current processes as well as the modules and threads for each process. (However, unlike PSAPI.DLL, it does not give device driver information.)  
   
  This unfortunate situation requires us to write different code for Windows NT and for Windows 9x. Fortunately, Windows 2000 will support Toolhelp. (I only hope it will also continue to support the perfectly good PSAPI.DLL, so I don't need to rewrite my code!)  
   
  Before looking at an example, however, let us complete the process handle story.  
 
  Process Pseudohandles  
   
  There is yet another wrinkle in the saga of process handles and process IDs. The function GetCurrentProcess:  
 
  HANDLE GetCurrentProcess(VOID)  
   
  returns a pseudohandle to the current process. A pseudohandle is a kind of lightweight handle. In particular, a pseudohandle is a process-specific number that identifies the process and can be used in calls to API functions that require a process handle.  
   
  While pseudohandles sound like ordinary handles, there are some key differences. Pseudohandles cannot be inherited by child processes, as can real handles. Also, a pseudohandle can refer only to the current process, whereas a real handle can refer to a foreign process.  
   
  Windows does provide a way to get a real handle from a pseudohandle, by using the DuplicateHandle API function, which we discussed in Chapter 10, Objects and Their Handles. This function is defined as:  
   
  BOOL DuplicateHandle(  
 
  HANDLE hSourceProcessHandle,  
   
  // handle to the source process  
 
  HANDLE hSourceHandle,  
   
  // handle to duplicate  
 
  HANDLE hTargetProcessHandle,  
   
  // handle to process to duplicate to  
 
  LPHANDLE lpTargetHandle,  
   
  // pointer to duplicate handle  
 
  DWORD dwDesiredAccess,  
   
  // access for duplicate handle  
 
  BOOL bInheritHandle,  
   
  // handle inheritance flag  
 
  DWORD dwOptions  
   
  // optional actions  
   
  );  

   
  Here is some illustrative code, showing how to get process handles and pseudohandles:  
 
  Public Const PROCESS_VM_READ = &H10

Public Const PROCESS_QUERY_INFORMATION = &H400

Public Const DUPLICATE_SAME_ACCESS = &H2

 
Page 172
 
  Public Sub DuplicateHandleExample()

Dim lret As Long

Dim hPseudoHandle As Long

Dim hProcessID As Long

Dim hProcess As Long

Dim hDupHandle As Long

' Process ID

hProcessID = GetCurrentProcessId

Debug.Print "Current process ID: " & hProcessID

' Pseudohandle

hPseudoHandle = GetCurrentProcess

Debug.Print "Pseudohandle: " & hPseudoHandle

' Duplicate handle

lret = DuplicateHandle(hPseudoHandle, _

   hPseudoHandle, _

   hPseudoHandle, _

   hDupHandle, _

   0&, -&, DUPLICATE_SAME_ACCESS)

Debug.Print "DuplicateHandle handle: " & hDupHandle

' Handle from OpenProcess

hProcess = OpenProcess( _

   PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, _

   0&, _

   hProcessID)

Debug.Print "OpenProcess Handle: " & hProcess

' Close real handles

Debug.Print

lret = CloseHandle(hProcess)

Debug.Print "Closing OpenProcess handle: " & hProcess

lret = CloseHandle(hDupHandle)

Debug.Print "Closing DuplicateHandle handle: " & hDupHandle

End Sub

 
   
  This code duplicates the source pseudohandle into the variable hDupHandle. It also gets a real (but different) handle using OpenProcess. Note that the real handles must be closed. However, pseudohandles do not need to be closed. The output on my system is:  
 
  Current process ID: 183

Pseudohandle: -1

DuplicateHandle handle: 412

OpenProcess Handle: 392

Closing OpenProcess handle: 392

Closing DuplicateHandle handle: 412

 
Page 173
 
  Enumerating Processes  
   
  Now let us consider the problem of enumerating the processes on a system. As we have said, the technique differs under Windows 9x and Windows NT.  
   
  Enumerating Processes Under Windows NT  
   
  We will use the following functions from PSAPI.DLL:  
 
  EnumProcesses

Enumerates the process IDs for each process in the system.

 
 
  EnumProcessModules

Enumerates the handle of each module in a given process.

 
 
  GetModuleBaseName

Retrieves the name of a module from its handle.

 
 
  GetModuleFileNameEx

Retrieves the fully qualified path of a module from its handle.

 
 
  GetModuleInformation

Retrieves information about a module.

 
 
  GetProcessMemoryInfo

Retrieves information about the memory usage of a process.

 
   
  The accompanying CD contains an application called rpiEnumProcsNT that displays each process, along with the EXE filename for that process. In addition, the utility displays the modules that are in the selected process's address space and some information related to memory usage by the process. Figure 11-2 shows the utility's main window. The complete source code is on the CD.  
   
  Hitting the MemMap button produces a page-by-page memory map (one page is 4 KB) of the selected process's address space, as shown in Figure 11-3. We will discuss this memory map in Chapter 13, Windows Memory Architecture.  
   
  Clicking the Refresh button starts the process of enumerating the system's processes. The EnumProcesses function is:  
   
  BOOL EnumProcesses(  
 
  DWORD *lpidProcess,  
   
  // array to receive the process identifiers  
 
  DWORD cb,  
   
  // size of the DWORD array in bytes  
 
  DWORD *cbNeeded  
   
  // receives the number of bytes returned  
   
  );  

   
  Since the first parameter is a pointer to the first DWORD in an array of DWORDs, we can pass the first DWORD by reference in VB. Now, a DWORD is actually an unsigned long, so we must use a VB long and do any necessary signed-to-unsigned conversion. However, process IDs are very small numbers (they seem to start at 1 and increase consecutively), and cbNeeded is just a count of bytes, so no  
Page 174
   
   
   
  Figure 11-2.

Enumerating processes under Windows NT

 
   
  conversion will be needed for either DWORD* parameter. Hence, one possible VB declaration is:  
 
  Public Declare Function EnumProcesses Lib "PSAPI.DLL" ( _

   idProcess As Long, _

   ByVal cBytes As Long, _

   cbNeeded As Long _

) As Long

 
   
  Note that Win32 supplies no direct way to tell how large to make the idProcess array. The only technique is to guess, try the function, and then compare the returned value of cbNeeded, which contains the number of bytes returned (that is, four times the number of longs returned), with the size of our array. If the number of longs returned is equal to the size of our array (it is guaranteed never to be bigger), then we need to increase the size of the array and try again!  
   
  The GetProcesses procedure shown in Example 11-4 begins with this sort of Terpsichore. Once the processes are enumerated, we sort them and use the OpenProcess function within a For loop to get handles to each process, with which we can call  
Page 175
   
   
   
  Figure 11-3.

A process memory map

 
   
  such functions as GetModuleNameEx. Note that we use the EnumProcessModules function to get the first module in each process, since this is always the EXE for the process. (You will find the RaiseAPIError function defined at the end of Chapter 3, API Declarations.)  
   
  Example 11-4. Enumerating Processes  
   
  Public Const PROCESS_VM_READ = &H10

Public Const PROCESS_QUERY_INFORMATION = &H400

Sub GetProcesses()

' Fills the arrays: lProcessIDs, hProcesses, sEXENames, sFQEXENames

' Sets cProcesses

Dim i As Integer, j As Integer, l As Long

Dim cbNeeded As Long

Dim hEXE As Long

Dim hProcess As Long

Dim lPriority As Long

' Initial guess

cProcesses = 25

Do

   ' Size array

   ReDim lProcessIDs(1 To cProcesses)

 
Page 176
   
  Example 11-4. Enumerating Processes (continued)  
   
      ' Enumerate

   lret = EnumProcesses(lProcessIDs(1), cProcesses * 4, cbNeeded)

   If lret = 0 Then

      RaiseApiError Err.LastDllError

      Exit Sub

   End If

   ' Compare needed bytes with array size in bytes.

   ' If less, then we got them all.

   If cbNeeded < cProcesses * 4 Then

      Exit Do

   Else

      cProcesses = cProcesses * 2

   End If

Loop

cProcesses = cbNeeded / 4

' Sort by processID

For i = 1 To cProcesses

   For j = i + 1 To cProcesses

      If lProcessIDs(i) > lProcessIDs(j) Then

         ' Swap

         l = lProcessIDs(i)

         lProcessIDs(i) = lProcessIDs(j)

         lProcessIDs(j) = 1

      End If

   Next

Next

ReDim Preserve lProcessIDs(1 To cProcesses)

ReDim sEXENames(1 To cProcesses)

ReDim sFQEXENames(1 To cProcesses)

ReDim sPriorityClass(1 To cProcesses)

' Now we have the process IDs

' Use OpenProcess to get a handle to each process

For i = 1 To cProcesses

   hProcess = OpenProcess( _

      PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, _

      0&, _

      lProcessIDs(i))

   ' Watch out for special processes

   Select Case lProcessIDs(i)

      Case 0 ' System Idle Process

         sEXENames(i) =  Idle Process

         sFQEXENames(i) =  Idle Process

      Case 2

         sEXENames(i) =  System

         sFQEXENames(i) =  System

 
Page 177
   
  Example 11-4. Enumerating Processes (continued)  
   
        Case 28

         sEXENames(i) =  csrss.exe (Win32)

         sFQEXENames(i) =  csrss.exe (Win32)

   End Select

   ' If error skip this process

   If hProcess <> 0 Then

      ' Now get the handle of the first module

      ' in this process, since first module is EXE

      hEXE = 0

      lret = EnumProcessModules(hProcess, hEXE, 4&, cbNeeded)

      If hEXE <> 0 Then

         ' Get the name of the module

         sEXENames(i) = String$(MAX_PATH, 0)

         lret = GetModuleBaseName(hProcess, hEXE, sEXENames(i), Len(sEXENames(i)))

         sEXENames(i) = Trim0(sEXENames(i))

         ' Get full path name

         sFQEXENames(i) = String$(MAX_PATH, 0)

         lret = GetModuleFileNameEx(hProcess, hEXE, sFQEXENames(i), _

                                    Len(sFQEXENames(i)))

         sFQEXENames(i) = Trim0(sFQEXENames(i))

         ' Get priority

         lPriority = GetPriorityClass(hProcess)

         Select Case lPriority

            Case IDLE_PRIORITY_CLASS

               sPriorityClass(i) =  idle

            Case NORMAL_PRIORITY_CLASS

               sPriorityClass(i) =  normal

            Case HIGH_PRIORITY_CLASS

               sPriorityClass(i) =  high

            Case REALTIME_PRIORITY_CLASS

               sPriorityClass(i) =  real

            Case Else

               sPriorityClass(i) =  ???

         End Select

      End If   ' EXE <> 0

   End If      ' hProcess <> 0

   ' Close handle

   lret = CloseHandle(hProcess)

Next

End Sub

 
Page 178
   
  When the user selects a process, the utility goes through a very similar procedure to enumerate the modules for the selected process, using EnumProcessModules, GetModuleBaseName, and GetModuleFileNameEx. In addition, it calls GetModuleInformation, which fills the MODULEINFO structure:  
   
  Type MODULEINFO  
 
  lpBaseOfDll As Long  
   
  ' pointer to starting point of module  
 
  SizeOfImage As Long  
   
  ' size in bytes of module  
 
  EntryPoint As Long  
   
  ' pointer to entry point of module  
   
  End Type  

   
  From this structure, we retrieve the size of the module and its base address.  
   
  Also, we can get some memory information by calling GetProcessMemoryInfo. This fills a structure with all sorts of interesting stuff, but we single out the working set size, the page file usage, and the page fault count. We will discuss memory in more detail in Chapter 13, but, briefly, here is what you need to know:  
   
  The working set size is the amount of actual physical RAM that is currently used by the process.  
   
  The page file usage is the amount of the Windows page file (swap file) that is being used by the selected process.  
   
  The page fault count is the number of times that the process tried to access a memory address that was not currently mapped to actual physical memory. This necessitated a swapping of a memory page out of RAM to make room for the requested page.  
   
  Device drivers are also executable files, but they are system-wide, rather than belonging to a specific process. The functions EnumDeviceDrivers, GetDeviceDriverBaseName, and GetDeviceDriverFileName are similar to the corresponding functions for processes and modules.  
   
  Once we have module and device driver information, we can create a memory map (at the bottom of the screen). Clicking on a module or driver will change its memory map entry to white for easier identification. Note how the memory map changes as you scroll through the list of processes. We will discuss memory maps in Chapter 13.  
   
  Enumerating Processes Under Windows 9x  
   
  The Windows 9x version of the previous utility is also on the CD. The main window is shown in Figure 11-4.  
   
  The code uses the Toolhelp DLL mentioned in the section ''Getting Module Names and Handles" earlier in this chapter. While the Win95 version does not enumerate drivers, it does take advantage of the ability of user mode code to examine the upper region of a process's address space.  
Page 179
   
   
   
  Figure 11-4.

Enumerating processes under Windows 9x

 
   
  Note that, by comparing the memory maps for a process under Windows NT and Windows 95/98, it is possible to see that Windows 95/98 puts the Win32 (and other) system DLLs in a different location than Windows NT. For example, under Windows NT, KERNEL32.DLL is just under the 2GB mark, which is in the area of memory reserved for applications. However, under Windows 95/98, this DLL is at the 3GB mark, in the area of memory reserved for the operating system.  
   
  In fact, Figure 11-5 shows the upper portion of the memory map of a Win95 process. Note the location of KERNEL32.DLL and the other system DLLs. Under Windows NT, this memory area is protected. We will discuss memory maps in more detail in Chapter 13.  
 
  Is This Application Already Running?  
   
  One of the most often asked questions by programmers is, "What is the best way to tell whether or not a given application is running?"  
   
  I can think of several methods for determining whether a particular application is currently running, but I would not be surprised to learn that there are many more.  
Page 180
   
   
   
  Figure 11-5.

The upper portion of a Win95 process space

 
   
  Using FindWindow  
   
  The first method is the simplest, but works only if the application has a uniquely identifiable top level window caption that does not change. In this case, we can use the FindWindow function to see if a window with that caption exists. There is, however, a subtlety involved here.  
   
  To illustrate, here is some code from the Load event of the Clipboard viewer application that we will write later in the book:  
 
  ' Check for running viewer and switch if it exists

hnd = FindWindow("ThunderRT6FormDC", "rpiClipViewer")

If hnd <> 0 And hnd <> Me.hwnd Then

   SetForegroundWindow hnd

   End

End If

 
   
  The problem here is that as soon as this program is run, a form with caption "rpiClipViewer" will be created, so FindWindow will always report that such a form (window) exists. However, this is easily overcome with a bit of prestidigitation. In  
Page 181
   
  particular, we change the design time caption for the main form to, say, "rpiClipView." Then, in the Activate event for the main form, we change it to its final value:  
 
  Private Sub Form_Activate()

Me.Caption = "rpiClipViewer"

End Sub

 
   
  Now, during the Form_Load event, the caption will be "rpiClipView" and thus will not trigger a positive response from FindWindow. Indeed, FindWindow will only report that such a window exists if there is another running instance of the application, which is precisely what we want.  
   
  The SetForegroundWindow problem  
   
  Under Windows 95 and Windows NT 4, the SetForegroundWindow function:  
 
  Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) _ As Long  
   
  will bring the application that owns the window with handle hwnd to the foreground. However, Microsoft has thrown us a curve in Windows 2000 and Windows 98. Here is what the documentation states:  
  Windows NT 5.0 and later: An application cannot force a window to the foreground while the user is working with another window. Instead, SetForegroundWindow will activate the window (see SetActiveWindow) and call the FlashWindowEx function to notify the user.  
   
  Unfortunately, Microsoft has decided to take one more measure of control out of our hands by not allowing us to change which application is in the foreground. (To be sure, abuse of this capability leads to obnoxious behavior, but I was not planning on abusing it!)  
   
  Fortunately, SetForegroundWindow does work if called from within an application that is, it will force its own application to the foreground. This is just enough rope to let us hang ourselves, so to speak.  
   
  The rpiAccessProcess DLL that we will discuss in Chapter 20, DLL Injection and Foreign Process Access, exports a function called rpiSetForegroundWindow. The VB declaration of this function is just like that of Win32's SetForegroundWindow:  
 
  Declare Function rpiSetForegroundWindow Lib "rpiAccessProcess.dll" ( _ ByVal hwnd As Long) As Long  
   
  The function is designed to work just like SetForegroundWindow works under Windows 95 and Windows NT 4, even under Windows 98 and Windows 2000. It does so by injecting the rpiAccessProcess DLL into the foreign process space so that the SetForegroundWindow function can be run from that process, thus bringing it to the foreground. We will discuss how this is done in Chapter 20. In any  
Page 182
   
  case, you should be able to use this function whenever you need SetForegroundWindow under Windows 98/2000. (By the way, I do believe that this feature should be used only in very special situations.)  
   
  Using a Usage Count  
   
  Conceptually, the simplest approach to this problem is just to have our VB application maintain a small text file that contains a single number serving as a usage count for the application and that is located in some fixed directory, such as the Windows system directory. The application can, in its main Load event, check the usage count by simply opening the file in the standard way.  
   
  If the count is 1, the application terminates abruptly, without firing its Unload event. This can be done by using the much maligned End statement. If the count is 0, the application sets the usage count to 1 and executes normally. Then, in its Unload event, the application sets the usage count to 0. In this way, one and only one instance of the application is allowed to run normally, and it is the only instance that alters the usage count.  
   
  Of course, this approach can be made more elegant by using a memory-mapped file, but this brings with it considerable additional baggage in the form of extra code.  
   
  Here is some pseudocode for the Load and Unload events of the main form:  
 
  Private Sub Form_Load()

Dim lUsageCount As Long

' Get the current usage count from the memory-mapped file

lUsageCount = GetUsageCount

If lUsageCount > 0 Then

   MsgBox "Application is already running"

   End

Else

   'Set the usage count to 1

   SetUsageCount 1

End If

End Sub

Private Sub Form_Unload()

SetUsageCount 0

End Sub

 
   
  We will leave the implementation of this approach to the reader and turn to a somewhat simpler implementation along these same lines.  
Page 183
   
  The rpiUsageCount DLL  
   
  As we will see when we discuss the rpiAccessProcess DLL for use in allocating foreign memory, an executable file (DLL or EXE) can contain shared memory. This memory is shared by every instance of the executable. Thus, if we place a shared variable in a DLL, every process that uses this DLL will have access to this variable.  
   
  To be absolutely clear, a shared variable is not the same as a global variable. Global variables are accessible to the entire DLL, but each process that loads the DLL gets a separate copy of each global variable. Thus, global variables are accessible within a single process. Shared variables are accessible throughout the system.  
   
  Now, while VB does not allow us to create shared memory in a VB executable, it is very easy to do in a DLL written in VC++.  
   
  On the accompanying CD, you will find a DLL called rpiUsageCount.dll. The entire VC++ source code is shown in Example 11-5.  
   
  Example 11-5. VC++ Source Code for the rpiUsageCount DLL  
   
  // rpiUsageCount.cpp

#include <windows.h>

// Set up shared data section in DLL

// MUST INITIALIZE ALL SHARED VARIABLES

#pragma data_seg( Shared )

long giUsageCount = 0;

#pragma data_seg()

// Tell linker to make this section shared and read-write

#pragma comment(linker,  /section:Shared,rws )

////////////////////////////////////////////////////////////

// Prototypes of exported functions

////////////////////////////////////////////////////////////

long WINAPI rpiIncrementUsageCount();

long WINAPI rpiDecrementUsageCount();

long WINAPI rpiGetUsageCount();

long WINAPI rpiSetUsageCount(long lNewCount);

/////////////////////////////////////////////////////////////

// DllMain

///////////////////////////////////////////////////////////

HANDLE hDLLInst = NULL;

BOOL WINAPI DllMain (HANDLE hInst, ULONG ul_reason_for_call,

                    LPVOID lpReserved)

{

        // Keep the instance handle for later use

        hDLLInst = hInst;

 
Page 184
   
  Example 11-5. VC++ Source Code for the rpiUsageCount DLL (continued)  
   
          switch(ul_reason_for_call)

        {

        case DLL_PROCESS_ATTACH:

                // Initialization here

                break

        case DLL_PROCESS_DETACH:

                // Clean-up here

              break;

        }

        return TRUE;

}

////////////////////////////////////////////////////////////

// Functions for export

////////////////////////////////////////////////////////////

long WINAPI rpiIncrementUsageCount()

{

   return InterlockedIncrement(&giUsageCount);

}

long WINAPI rpiDecrementUsageCount()

{

   return InterlockedDecrement(&giUsageCount);

}

long WINAPI rpiGetUsageCount()

{

   return giUsageCount;

}

long WINAPI rpiSetUsageCount(long lNewCount)

{

   giUsageCount = lNewCount;

   return giUsageCount;

}

 
   
  This DLL has a single, shared long variable, called giUsageCount. The DLL exports four functions for use with this variable. (This is more than is needed, but I got carried away.)  
 
  rpiIncrementUsageCount

rpiDecrementUsageCount

rpiGetUsageCount

rpiSetUsageCount

 
   
  Here are the VB declarations:  
 
  Declare Function rpiIncrementUsageCount Lib "rpiUsageCount.dll" () As Long

Declare Function rpiDecrementUsageCount Lib "rpiUsageCount.dll" () As Long

Declare Function rpiGetUsageCount Lib "rpiUsageCount.dll" () As Long

Declare Function rpiSetUsageCount Lib "rpiUsageCount.dll" () As Long

 
   
  To use this DLL, we just add the code shown in Example 11-6 to the Load and Unload events of the main VB form.  
Page 185
   
  Example 11-6. Calling the rpiGetUsageCount Function  
   
  Private Sub Form_Load()

Dim lUsageCount As Long

' Get the current usage count

lUsageCount = rpiGetUsageCount

If lUsageCount > 0 Then

   MsgBox  Application is already running

   End

Else

   rpiSetUsageCount 1

End If

End Sub

Private Sub Form_Unload()

rpiSetUsageCount 0

End Sub

 
   
  The downside of using this DLL is that it uses 49,152 bytes of memory. Also, it does not automatically switch to an already running instance of the application. For this, we still need to use FindWindow to get a window handle to use with SetForegroundWindow (or rpiSetForegroundWindow).  
   
  Walking the Process List  
   
  Our final approach to checking for a running application is the most obvious one and should always work (although for some reason I get a funny feeling saying "always"). Namely, we walk through the list of all current processes to check every EXE filename (and perhaps even complete path). Unfortunately, as we have seen, this requires different code under Windows NT and Windows 9x. Nevertheless, it is important, so here is a utility that will do the job.  
   
  The Windows NT version is GetWinNTProcessID. We feed this function either an EXE filename or a fully qualified EXE name (path and filename). The function walks the process list and tries to do a case-insensitive match of the name. It returns the process ID of the last match and a count of the total number of matches. If the return value is 0, then this application is not running. Examples 11-7 and 11-8 show the code (both versions), including the necessary declarations.  
   
  Example 11-7. Walking the Windows NT Process List  
   
  Option Explicit

 '*************************

' NOTE: Windows NT 4.0 only

' *************************

 
Page 186
   
  Example 11-7. Walking the Windows NT Process List (continued)  
   
  Public Const MAX_PATH = 260

Public Declare Function EnumProcesses Lib  PSAPI.DLL  ( _

   idProcess As Long, _

   ByVal cBytes As Long, _

   cbNeeded As Long _

) As Long

Public Declare Function EnumProcessModules Lib  PSAPI.DLL  ( _

   ByVal hProcess As Long, _

   hModule As Long, _

   ByVal cb As Long, _

   cbNeeded As Long _

) As Long

Public Declare Function GetModuleBaseName Lib  PSAPI.DLL  _

Alias  GetModuleBaseNameA  ( _

   ByVal hProcess As Long, _

   ByVal hModule As Long, _

   ByVal lpBaseName As String, _

   ByVal nSize As Long _

) As Long

Public Declare Function GetModuleFileNameEx Lib  PSAPI.DLL  _

Alias  GetModuleFileNameExA  ( _

   ByVal hProcess As Long, _

   ByVal hModule As Long, _

   ByVal lpFilename As String, _

   ByVal nSize As Long _

) As Long

Public Const STANDARD_RIGHTS_REQUIRED = &HF0000

Public Const SYNCHRONIZE = &H100000

Public Const PROCESS_VM_READ = &H10

Public Const PROCESS_QUERY_INFORMATION = &H400

Public Const PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED Or _

                SYNCHRONIZE Or &HFFF

Declare Function OpenProcess Lib  kernel32  ( _

   ByVal dwDesiredAccess As Long, _

   ByVal bInheritHandle As Long, _

   ByVal dwProcessId As Long _

) As Long

Declare Function CloseHandle Lib  kernel32  (ByVal hObject As Long) _

  As Long

' -------------------

Public Function GetWinNTProcessID(sFQEXEName As String, _

                sEXEName As String, ByRef cMatches As Long) As Long

' Gets the process ID from the EXE name or fully qualified (path/name)

 
Page 187
   
  Example 11-7. Walking the Windows NT Process List (continued)  
   
  ' EXE name

' If sFQName <>   then uses this to get matches

' If sName <>   uses just the name to get matches

' Returns 0 if no such process, else the process ID of the last match

' Returns count of matches in OUT parameter cMatches

' Returns FQName if that is empty

' Returns -1 if both sFQName and sName are empty

' Returns -2 if error getting process list

Dim i As Integer, j As Integer, l As Long

Dim cbNeeded As Long

Dim hEXE As Long

Dim hProcess As Long

Dim lret As Long

Dim cProcesses As Long

Dim lProcessIDs() As Long

Dim sEXENames() As String

Dim sFQEXENames() As String

' ----------------------------------

' First get the array of process IDs

' ---------------------------------

' Initial guess

cProcesses = 25

Do

   ' Size array

   ReDim lProcessIDs(1 To cProcesses)

   ' Enumerate

   lret = EnumProcesses(lProcessIDs(1), cProcesses * 4, cbNeeded)

   If lret = 0 Then

      GetWinNTProcessID = -2

      Exit Function

   End If

   ' Compare needed bytes with array size in bytes.

   ' If less, then we got them all.

   If cbNeeded < cProcesses * 4 Then

      Exit Do

   Else

      cProcesses = cProcesses * 2

   End If

Loop

cProcesses = cbNeeded / 4

ReDim Preserve lProcessIDs(1 To cProcesses)

ReDim sEXENames(1 To cProcesses)

ReDim sFQEXENames(1 To cProcesses)

' -------------

' Get EXE names

' -------------

 
Page 188
   
  Example 11-7. Walking the Windows NT Process List (continued)  
   
  For i = 1 To cProcesses

   ' Use OpenProcess to get a handle to each process

   hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, _

                          0&, lProcessIDs(i))

   ' Watch out for special processes

   Select Case lProcessIDs(i)

      Case 0 ' System Idle Process

         sEXENames(i) =  Idle Process

         sFQEXENames(i) =  Idle Process

      Case 2

         sEXENames(i) =  System

         sFQEXENames(i) =  System

      Case 28

         sEXENames(i) =  csrss.exe

         sFQEXENames(i) =  csrss.exe

   End Select

    ' If error skip this process

   If hProcess = 0 Then

      GoTo hpContinue

   End If

   ' Now get the handle of the first module

   ' in this process, since first module is EXE

   hEXE = 0

   lret = EnumProcessModules(hProcess, hEXE, 4&, cbNeeded)

   If hEXE = 0 Then GoTo hpContinue

   ' Get the name of the module

   sEXENames(i) = String$ (MAX_PATH, 0)

   lret = GetModuleBaseName(hProcess, hEXE, sEXENames(i), _

Len(sEXENames(i)))

   sEXENames(i) = Trim0(sEXENames(i))

   ' Get full path name

   sFQEXENames(i) = String$ (MAX_PATH, 0)

   lret = GetModuleFileNameEx(hProcess, hEXE, sFQEXENames(i), _

Len(sFQEXENames(i)))

   sFQEXENames(i) = Trim0(sFQEXENames(i))

hpContinue:

   ' Close handle

   lret = CloseHandle(hProcess)

Next

' ----------------

' Check for match

' ----------------

 
Page 189
   
  Example 11-7. Walking the Windows NT Process List (continued)  
   
  cMatches = 0

If sFQEXEName <>   Then

   For i = 1 To cProcesses

      If LCase$ (sFQEXENames(i)) = LCase$ (sFQEXEName) Then

         cMatches = cMatches + 1

         GetWinNTProcessID = lProcessIDs(i)

      End If

   Next

ElseIf sEXEName <>   Then

   For i = 1 To cProcesses

      If LCase$ (sEXENames(i)) = LCase$ (sEXEName) Then

         cMatches = cMatches + 1

         GetWinNTProcessID = lProcessIDs(i)

         sFQEXEName = sFQEXENames(i)

      End If

   Next

Else

   GetWinNTProcessID = -1

End If

End Function

 
   
  The Windows 9x version uses Toolhelp. The corresponding function (and required declarations) is shown in Example 11-8.  
   
  Example 11-8. Walking the Windows 9x Process List  
   
  Option Explicit

 '************************

' NOTE: Windows 95/98 only

' ************************

Public Const MAX_MODULE_NAME32 = 255

Public Const MAX_PATH = 260

Public Const TH32CS_SNAPHEAPLIST = &H1

Public Const TH32CS_SNAPPROCESS = &H2

Public Const TH32CS_SNAPTHREAD = &H4

Public Const TH32CS_SNAPMODULE = &H8

Public Const TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST Or TH32CS_SNAPPROCESS

                              Or TH32CS_SNAPTHREAD Or TH32CS_SNAPMODULE)

Public Const TH32CS_INHERIT = &H80000000

''HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags,

''  DWORD th32ProcessID );

Public Declare Function CreateToolhelp32Snapshot Lib  kernel32  ( _

    ByVal dwFlags As Long, _

    ByVal th32ProcessID As Long _

) As Long

Public Declare Function Process32First Lib  kernel32  ( _

 
Page 190
   
  Example 11-8. Walking the Windows 9x Process List (continued)  
   
      ByVal hSnapShot As Long, _

    lppe As PROCESSENTRY32 _

) As Long

Public Declare Function Process32Next Lib  kernel32  ( _

    ByVal hSnapShot As Long, _

    lppe As PROCESSENTRY32 _

) As Long

Public Type PROCESSENTRY32

    dwSize As Long

    cntUsage As Long

    th32ProcessID As Long           ' process ID

    th32DefaultHeapID As Long

    th32ModuleID As Long            ' only for Toolhelp functions

    cntThreads As Long              ' number of threads

    th32ParentProcessID As Long        ' process ID of parent

    pcPriClassBase As Long

    dwFlags As Long

    szExeFile As String * MAX_PATH ' path/file of EXE file

End Type

Declare Function CloseHandle Lib  kernel32  (ByVal hObject As Long) _

      As Long

' --------------------------

Function GetWin95ProcessID(sFQName As String, sName As String, _

         ByRef cMatches As Long) As Long

' *************************

' NOTE: Windows 95/98 only

' *************************

' Gets the process ID

' If sFQName <>   then uses this to get matches

' If sName <>   uses just the name to get matches

' Returns 0 if no such process, else the process ID of the last match

' Returns count of matches in OUT parameter cMatches

' Returns FQName if that is empty

' Returns -1 if could not get snapshot

Dim i As Integer, c As Currency

Dim hSnapShot As Long

Dim lret As Long ' for generic return values

Dim cProcesses As Long

Dim cProcessIDs() As Currency

Dim sEXENames() As String

Dim sFQEXENames() As String

Dim procEntry As PROCESSENTRY32

procEntry.dwSize = LenB(procEntry)

 
Page 191
   
  Example 11-8. Walking the Windows 9x Process List (continued)  
   
  ' Scan all the processes.

hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0&)

If hSnapShot = -1 Then

    GetProcessID = -1

    Exit Function

End If

' Initialize

ReDim sFQEXENames(1 To 25)

ReDim sEXENames(1 To 25)

ReDim cProcessIDs(1 To 25)

cProcesses = 0

' Do first process

lret = Process32First(hSnapShot, procEntry)

If lret > 0 Then

    cProcesses = cProcesses + 1

    sFQEXENames(cProcesses) = Trim0(procEntry.szExeFile

    sEXENames(cProcesses) = GetFileName(sFQEXENames(cProcesses))

    If procEntry.th32ProcessID < 0 Then

        c = CCur(procEntry.th32ProcessID) + 2 ^ 32

    Else

        c = CCur(procEntry.th32ProcessID)

    End If

    cProcessIDs(cProcesses) = c

End If

' Do rest

Do

    lret = Process32Next(hSnapShot, procEntry)

    If lret = 0 Then Exit Do

    cProcesses = cProcesses + 1

    If UBound(sFQEXENames) < cProcesses Then

        ReDim Preserve sFQEXENames(1 To cProcesses + 10)

        ReDim Preserve sEXENames(1 To cProcesses + 10)

        ReDim Preserve cProcessIDs(1 To cProcesses + 10)

    End If

    sFQEXENames(cProcesses) = Trim0(procEntry.szExeFile)

    sEXENames(cProcesses) = GetFileName(sFQEXENames(cProcesses))

    If procEntry.th32ProcessID < 0 Then

        c = CCur(procEntry.th32ProcessID) + 2 ^ 32

    Else

        c = CCur(procEntry.th32ProcessID)

End If

    cProcessIDs(cProcesses) = c

Loop

CloseHandle hSnapShot

' ----------

' Find Match

' ----------

 
Page 192
   
  Example 11-8. Walking the Windows 9x Process List (continued)  
   
  cMatches = 0

If sFQName <>   Then

   For i = 1 To cProcesses

      If LCase$ (sFQEXENames(i)) = LCase$ (sFQName) Then

         cMatches = cMatches + 1

         GetProcessID = lProcessIDs(i)

      End If

   Next

ElseIf sName <>   Then

   For i = 1 To cProcesses

        If LCase$ (sEXENames(i)) = LCase$ (sName) Then

         cMatches = cMatches + 1

         GetProcessID = lProcessIDs(i)

         sFQName = sFQEXENames(i)

      End If

   Next

Else

   GetProcessID = -1

End If

End Function

 

Категории