<title>Enumerate Threads For Windows NT
Introduction
I had been developing on Windows 95 and Windows 98 long before I started on Windows NT 4.0. One of the great libraries to me of Win9x is the ToolHelp32 library. Some really great
and useful helper functions for processes if I ever saw any. I did of course notice the 'Windows 95 Only' remark in the Win32 SDK documentation. It didn't bother me at
the time anyway.
Having shifted to WinNT 4.0, it has proven to be quite a drag to port a lot of my utilities to NT 4.0 as ToolHelp32
functions are not implemented in Kernel32.dll in WinNT. In fact,
some of the code in my utilities were just excluded from compilation for NT 4.0.
The PSAPI
functions did help in providing enumeration functions for processes and process modules. However, after doing searches on MSDN, I found no functions available to
enumerate the threads of a process. Not that I've heard of any either, other than those provided by ToolHelp32
. It's great that Windows 2000 had unified the Win9x Win32 functions and
the WinNT set so we don't get any more of that 'Windows 95 only feature' or 'Windows NT only feature'.
As I often need to enumerate the threads of processes, I thought of looking for other ways. One of which was I started to implement the ToolHelp32
functions for NT 4.0. I started
porting the code of CreateToolhelp32Snapshot()
from Win2000 to NT 4.0. I got quite a bit of it to work, but decided to give up as that function just got bigger and bigger
as I went along porting it. Being lazy, I looked for other ways.
That's when I came to the performance data provided by WinNT. Having written code to parse the data before, I sure wasn't enthusiastic about doing it again. Fortunately, I came
across the Performance Data Helper (PDH)
interface. This made things much easier, although you'd learn more parsing the data yourself.
The Performance Data Helper (PDH) Interface
As it is documented in MSDN, I will not go into any depth here. Basically, I used the performance data (PD) to the get the count of threads in processes. Following, are the
functions used to accomplish the task
PDH_STATUS PdhOpenQuery( LPCTSTR szDataSource,
DWORD_PTR dwUserData,
PDH_HQUERY *phQuery );
PDH_STATUS PdhCloseQuery( PDH_HQUERY hQuery );
PDH_STATUS PdhExpandCounterPath( LPCTSTR szWildCardPath,
LPTSTR mszExpandedPathList,
LPDWORD pcchPathListLength );
PDH_STATUS PdhAddCounter( PDH_HQUERY hQuery,
LPCTSTR szFullCounterPath,
DWORD_PTR dwUserData,
PDH_HCOUNTER* phCounter );
PDH_STATUS PdhCollectQueryData( PDH_HQUERY hQuery );
PDH_STATUS PdhGetFormattedCounterValue( PDH_HCOUNTER hCounter,
DWORD dwFormat,
LPDWORD lpdwType,
PPDH_FMT_COUNTERVALUE pValue );
The PDH functions are exported from the redistributable PDH.dll which I will include with the source code because it's the one I developed with and installed in my NT 4.0 system.
I got this one from Microsoft's website, and it turns out to be a little different from the one that comes with Win2000. I also found that the function PdhExpandCounterPath()
doesn't seem to work right in Win2000. I also used the Pdh.h
header and import library from the latest Platform SDK which I will also include for your convenience.
I encountered some compile errors with the PDH header file. It was that the types DWORD_PTR
and LONG_PTR
types were not defined. In fact, I couldn't find
them defined anywhere, so I defined them as LPDWORD
and LPLONG
respectively in the Pdh.h header that I've included.
Please read the instructions that come with the DLL. Points to note is to NOT replace the PDH.dll in the system folder for Win2000. As the version of the DLL that comes with
Win2000 doesn't seem to work right with my code, I just included the PDH.dll that I downloaded from Microsoft into the working path of my apps that use it. If any of you figure out
why the calls don't seem to return the right stuff in Win2000, please let me know.
Implementation
I made my functions stylistically consistent with the Win32 API functions and even made them in the WINAPI
standard call calling convention. As I wanted them to be
like the PSAPI
functions, and as I also seldom use C++ in my systems programming, they are just regular run-of-the-mill functions and not in C++ classes. I would say that they would
be nicer being wrapped in C++ classes, and also that they are perfect candidates as well. I'm sure a few people have already wrapped the PSAPI
and other process/module/thread
functions into classes already. Any of you can go ahead and wrap these as well. Sam Blackburn might want to consider this.
I implemented the functions in the file PdhUtil.cpp and declared all that's necessary in PdhUtil.h. Just plonk those two files into your projects,
include the header and you can start calling the functions. Also, you might want to make them into DLL's or static libraries if it suits your needs. All you have to do then is to
distribute the header file and include the necessary storage specifiers if exporting from a DLL.
Following are the function prototypes
BOOL WINAPI EnumProcessNames(LPPROCINFO lpProcInfo, DWORD cb, LPDWORD cbNeeded);
BOOL WINAPI EnumThreads(LPTHREADINFO lpThreadInfo, DWORD cb, LPDWORD cbNeeded);
BOOL WINAPI EnumProcessThreads(LPTHREADINFO lpThreadInfo, DWORD cb, LPDWORD cbNeeded);
BOOL WINAPI EnumProcessThreadsEx(DWORD dwPid, LPTHREADINFO lpThreadInfo, DWORD cb,
LPDWORD cbNeeded);
These are the data structures used
typedef struct _PROCINFO
{
DWORD dwPID;
TCHAR szProcName[32];
} PROCINFO, *LPPROCINFO;
typedef struct _THREADINFO
{
DWORD dwPID;
TCHAR szProcName[32];
DWORD dwThreadId;
LPVOID lpStartAddress;
DWORD dwThreadState;
DWORD dwThreadWaitReason;
} THREADINFO, *LPTHREADINFO;
As you might notice, I also implemented a EnumProcessNames()
function as there doesn't seem to be any Win32 API functions or otherwise that would get you the process
names with its PID. The process name returned does not include an extension like .exe that you would get in the Task Manager. I guess you can assume that it is
with the exception of the Idle and System processes.
All the functions return TRUE
on success and FALSE
on failure.
The lpThreadInfo
and lpProcInfo
are output buffers that will receive the data. You cannot pass NULL
into this parameter as the functions will just fail.
The cb
parameter specifies the number of elements in the array of the output buffer, NOT the size of the buffer in bytes.
The cbNeeded
parameter is an output buffer that:
If the function succeeds, it will return the number of elements in the output buffer array.
If the function fails, it will return the number of elements needed in the output buffer array.
Another quirk about this is that since it's the PD, it's possible that it will change between the calls. I would then suggest that you give it a pretty large buffer to begin with.
EnumProcessThreads()
basically calls EnumProcessThreadsEx()
with the current PID. The dwPid
parameter of EnumProcessThreadsEx()
can except 0xFFFFFFFF
and will take that to mean the current PID.
Implementation Details
The workings of these functions is based on enumerating the performance counters that we're interested in. Mainly, those for processes and threads. This is accomplished by using
PdhExpandCounterPath()
by specifying wildcards. The general counter path format is as follows
\\machine\object(parent/instance#index)\counter
The wildcards are in place of parent
, instance
and index
. If \\machine
is excluded, the local machine is assumed.
As the counters may change between calls to PdhExpandCounterPath()
, I've called them all together for the counters I'm interested in like so
BOOL GetThreadCounters(LPCTSTR *lpszCounter, DWORD dwCounters)
{
...
for (i = 0; i < dwCounters; i++)
{
dw = MB1;
while (1)
{
lpv = HeapAlloc(GetProcessHeap(), 0, dw);
if (lpv == NULL)
{
return FALSE;
}
((LPDWORD)alpvCounters)[i] = (DWORD)lpv;
lpsz = (LPTSTR)lpv;
memset(lpsz, 0, dw);
sprintf(szCounter, "\\Thread(*/*#*)\\%s", lpszCounter[i]);
pdhRes = PdhExpandCounterPath((LPCTSTR)szCounter, lpsz, &dw);
if (pdhRes != PDH_CSTATUS_VALID_DATA)
{
if (pdhRes == PDH_MORE_DATA)
{
HeapFree(GetProcessHeap(), 0, lpv);
lpv = NULL;
}
else
{
bRes = FALSE;
goto Leave;
}
}
else
{
break;
}
}
}
The counters are defined as
LPCTSTR gaszCounters[] = { "ID Process",
"ID Thread",
"Start Address",
"Thread State",
"Thread Wait Reason"
};
For enumerating processes, I wanted to make sure I keep only unique processes, so I filter them using a map with the key as ProcessName:PID
. Following is the
implementation
for (dw = 0, gdwProcInfo = 0; dw < dwCounters; dw++)
{
if (PdhGetFormattedCounterValue(ahCounters[dw],
PDH_FMT_LONG,
NULL,
&pdhFormattedValue) != ERROR_SUCCESS)
{
TRACE("Failed to get formatted counter value for %s : 0x%x\n",
(LPCTSTR)asbuff[dw],
ahCounters[dw]);
continue;
}
sbuff = GetProcessNameFromCounter(asbuff[dw]);
sKey.Format("%s:%u", sbuff, pdhFormattedValue.longValue);
if (mProcess.Lookup((LPCTSTR)sKey, stemp))
{
continue;
}
if (!sbuff.CompareNoCase(SZTOTAL))
{
continue;
}
mProcess[sKey] = sKey;
galpProcInfo[gdwProcInfo].dwPID = pdhFormattedValue.longValue;
if (sbuff.GetLength() < sizeof(galpProcInfo[dw].szProcName))
{
lstrcpy(galpProcInfo[gdwProcInfo].szProcName, (LPCTSTR)sbuff);
}
else
{
OutputDebugString("EnumAllProcesses():PROCINFO.szProcName size too small!\n");
}
gdwProcInfo++;
TRACE(" %s[0x%x] : 0x%x %u\n",
(LPCTSTR)sbuff,
ahCounters[dw],
pdhFormattedValue.longValue,
pdhFormattedValue.longValue);
}
The rest should be pretty straight forward. As for enumerating the threads for a particular process, it doesn't really do that. Rather, after enumerating all the threads in the
system, the code just filters out the ones that do not match the specified PID. It ain't magic, but it's simpler than making up a counter path as the core of the code is based on a
general query for all counters. It is done as such in EnumProcessThreadsEx()
memcpy((LPTHREADINFO)galpThreadInfo,
(LPTHREADINFO)lpThreadInfo,
(gdwThreadInfo * sizeof(THREADINFO)));
for (i = 0, j = 0; i < gdwThreadInfo; i++)
{
if (galpThreadInfo[i].dwPID != gdwCurrentProcessId)
{
continue;
}
j++;
}
if (cb < j)
{
*cbNeeded = j;
bRes = FALSE;
goto Leave;
}
And the even less magical part of returning the matched ones into the output buffer
*cbNeeded = j;
memset(lpThreadInfo, 0, (cb * sizeof(THREADINFO)));
for (i = 0, j = 0; i < gdwThreadInfo; i++)
{
if (galpThreadInfo[i].dwPID == gdwCurrentProcessId)
{
memcpy(&lpThreadInfo[j++], &galpThreadInfo[i], sizeof(THREADINFO));
}
}
Those who have been paying attention would have seen the goto
by now. Before you cringe and call me a schmuck, I'll justify that by saying that it makes the code
less cluttered with the goto instead of including the code for the cleanups at every point of exit. Hey, MFC uses it too. Not that it makes it more elegant, but less code and clutter.
Oh, do feel free to get rid of it if you like, though...
Oh, another thing is that I had to filter out the 'total' counter _Total
. The code is such
#define SZTOTAL "_Total"
...
sbuff = GetProcessNameFromCounter((CString)lpsz);
if (!sbuff.CompareNoCase(SZTOTAL))
{
TRACE("Ignoring %s\n", lpsz);
}
else
{
gdwThreadCounters++;
glsThreadCounters.AddTail((LPCTSTR)lpsz);
}
Wrapping Up
By the end of it, I wasn't too motivated to write any fancy test function, so you're gonna get the crappy one that comes in the sample console app that the PdhUtil
functions come with.
Conclusion
Well, that pretty much wraps up this article. Now I am happy that I can enumerate threads in Windows NT 4.0. This also works in Windows 2000, but has the quirks that I mentioned.
Even though Windows 2000 has ToolHelp32 to enumerate a process's threads, it cannot enumerate the threads of other processes by itself. You could if you write a little
'extraterrestrial' code... For the lack of a better word than that socially unacceptable 'H' word. Well, happy enumerating...
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.