More About Handles and Process IDs
Almost everything in Windows has a handle, an integer value that identifies it. Every window has an HWND, every module has an HMODULE, and every 32-bit process has a process HANDLE. (Don’t ask me why it isn’t called an HPROCESS.) Each process consists of threads, each of which has a thread HANDLE.
For now, we’re interested in three types of handles—window, process, and module. You could also throw in instance handle—except that it’s really a pointer, not a handle. In addition, you often need to use process IDs. What you really want before you can do actual work in Visual Basic is the handle to a window, process, or module. Process IDs and instance handles are red herrings thrown in to confuse you. The only thing you can do with them is turn them into usable handles. In fact, turning one thing into another is a common task. Table 6-4 shows a grid of conversion functions:
Table 6-4. Handle and process ID conversion functions.
There are a few points worth noting here. There is no row for window handle and no column for module handle. You can’t work back from any of the other handles to get a window handle because any process or module can have multiple windows. You can’t start with a module and figure out its process or window because it’s at the bottom of the chain.
Some of the conversion procedures are trivial wrappers for API functions. For example, here’s how you get a process ID from a window handle:
Function ProcIDFromWnd(ByVal hWnd As Long) As Long
Dim idProc As Long
Call GetWindowThreadProcessId(hWnd, idProc)
ProcIDFromWnd = idProc
End Function
A process ID isn’t much use in itself. Most API functions that control or return information about processes take a process handle. Here’s how you get one from a process ID:
Function ProcFromProcID(idProc As Long)
ProcFromProcID = OpenProcess(PROCESS_QUERY_INFORMATION Or _
PROCESS_VM_READ, 0, idProc)
End Function
The ProcFromProcID function passes two constant flags to OpenProcess that happen to be the ones that I and others have discovered, through trial and error, to be the most convenient for getting common information about processes. But some applications might want to modify process data or do various other operations that would require different flags. You can call OpenProcess multiple times to get different handles for different purposes from the same process ID. We’ll see other uses for process handles in Chapter 11.
Generally, you’ll do real work with process handles. There’s not much reason to use module handles. A program like WinWatch is the exception. In those rare cases when you need the name of a running program other than your own, the module handle is the only way to get it. The module handle can also be used to access resources (see Chapter 8). The problem is that there’s no easy way to get a module handle. The only Win32 function I know of that returns one is GetModuleHandle, but it requires that you know the module name. If you knew that, you wouldn’t need the module handle. You must resort to the OS-specific ToolHelp32 and PSAPI functions described earlier.
Here’s another example of a translation function that is a simple API wrapper. This one gets an instance handle from a window:
Function InstFromWnd(ByVal hWnd As Long) As Long
BugAssert hWnd <> hNull
InstFromWnd = GetWindowLong(hWnd, GWL_HINSTANCE)
End Function
Very simple, and very useful—in 16-bit Windows. Unfortunately, an instance handle isn’t worth much in 32-bit windows because, as noted before, it’s not really a handle. It’s the base address of the module. In fact, if you trace through the process and module lists, you’ll find that the base module address often has exactly the same value as the module handle. Unfortunately, you’ll find enough exceptions to make this trend unreliable. Technically, you can get almost anything from an instance handle that you can get from a process handle or ID, but you probably don’t want to because the technique is complicated and inefficient. You loop through all the processes in the system. For each one, you compare the base address to your instance handle. When you find a match, you’ve got the right process and you can use its ID just as if you’d started with that. But you could have gotten the process ID directly from the window and skipped the instance altogether. So that’s the last I’ll say about instance handles.
Getting the name of an executable file was easy in 16-bit windows. You could get the instance handle from the window and pass it to the GetModuleFileName function. You were supposed to pass a module handle to GetModuleFileName, but an instance handle would do because Windows knew how to find the module from the instance. When Win32 functions say they want a module handle, they mean it. But even with a module handle, there’s no portable technique for getting an EXE name. The workaround is on the following page.
Function ExePathFromProcID(idProc As Long) As String
If Not IsNT Then
Dim process As PROCESSENTRY32, hSnap As Long, f As Long
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
If hSnap = hNull Then Exit Function
process.dwSize = Len(process)
f = Process32First(hSnap, process)
Do While f
If process.th32ProcessID = idProc Then
ExePathFromProcID = StrZToStr(process.szExeFile)
Exit Function
End If
f = Process32Next(hSnap, process)
Loop
Else
Dim s As String, c As Long
s = String$(cMaxPath, 0)
c = GetModuleFileNameEx(ProcFromProcID(idProc), _
ModFromProcID(idProc), s, cMaxPath)
If c Then ExePathFromProcID = Left$(s, c)
End If
End Function
The Windows NT version is simple. The PSAPI DLL provides an extended version of the old GetModuleFileName function. You need both the module and process handles, but they’re easy enough to get. The Windows 95 section is more difficult. There’s no GetModuleFileNameEx as there is in PSAPI, and there’s no ModuleFindName as there was in the old 16-bit TOOLHELP DLL. You have to do it the hard way—by looping through all the processes until you find the one with a matching ID.