Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / MFC
Article

Enumerate Threads For Windows NT 4.0

Rate me:
Please Sign up or sign in to vote.
4.83/5 (3 votes)
8 Nov 20017 min read 91K   1.1K   15   10
Enumerate threads for processes in Windows NT 4.0
<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;
                }
    
                // store the pointer
                ((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;
                        // *** dw now has the required length ***
                    }
                    else
                    {
                         // *** don't know what else to do ***
                         bRes = FALSE;
    
                         goto Leave;
                     }
                 }
                 else
                 {
                     break;
                 }
            }	// end while
        }	// end for

    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;
        }
    
        // I don't know why it returns more counters than there are processes,
        // so I'll have to weed them out.
        // just to be sure
        sbuff = GetProcessNameFromCounter(asbuff[dw]);
        sKey.Format("%s:%u", sbuff, pdhFormattedValue.longValue);
    
        if (mProcess.Lookup((LPCTSTR)sKey, stemp))
        {
            // exists so skip
            continue;
        }
    
        // *** ignore the total ***
        if (!sbuff.CompareNoCase(SZTOTAL))
        {
            continue;
        }
    
        mProcess[sKey] = sKey;
    
        // store the PID's
        galpProcInfo[gdwProcInfo].dwPID = pdhFormattedValue.longValue;
    
        if (sbuff.GetLength() < sizeof(galpProcInfo[dw].szProcName))
        {
            lstrcpy(galpProcInfo[gdwProcInfo].szProcName, (LPCTSTR)sbuff);
        }
        else
        {
          // say something
          OutputDebugString("EnumAllProcesses():PROCINFO.szProcName size too small!\n");
        }
    
        // increment now!
        gdwProcInfo++;
    
        TRACE("   %s[0x%x] : 0x%x %u\n", 
              (LPCTSTR)sbuff, 
              ahCounters[dw], 
              pdhFormattedValue.longValue, 
              pdhFormattedValue.longValue);
              
    }	// end for

    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)));
    
    // filter now
    for (i = 0, j = 0; i < gdwThreadInfo; i++)
    {
        if (galpThreadInfo[i].dwPID != gdwCurrentProcessId)
        {
            continue;
        }
    
        j++;
    }	// end for
    
    if (cb < j)
    {
        *cbNeeded = j;
        bRes = FALSE;
    
        goto Leave;
    }

    And the even less magical part of returning the matched ones into the output buffer

    // *** must do this before initializing the globals!!! ***
    // return the number of thread info items
    *cbNeeded = j;
    
    // copy them now
    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));
        }
    }	// end for

    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"
    ...
        // *** ignore 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...

  • License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here


    Written By
    United States United States
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    Generalcounter path elements Pin
    ferhatmantar11-Aug-05 3:53
    ferhatmantar11-Aug-05 3:53 
    GeneralAbout Windpws CE Enumerate threads Pin
    Member 39424122-Dec-03 21:41
    Member 39424122-Dec-03 21:41 
    Generalerror when executing on Win XP Pin
    Audalio_Jr30-May-03 9:06
    Audalio_Jr30-May-03 9:06 
    GeneralRe: error when executing on Win XP Pin
    Anonymous30-May-03 23:51
    Anonymous30-May-03 23:51 
    GeneralRe: error when executing on Win XP Pin
    Artemis31-May-03 0:09
    Artemis31-May-03 0:09 
    GeneralRe: error when executing on Win XP Pin
    Antony M Kancidrowski5-Feb-04 4:01
    Antony M Kancidrowski5-Feb-04 4:01 
    Generalenumerate threads in xp enviroment Pin
    evilmog22-Jan-03 4:52
    evilmog22-Jan-03 4:52 
    GeneralRe: enumerate threads in xp enviroment Pin
    Artemis22-Jan-03 16:16
    Artemis22-Jan-03 16:16 
    GeneralToolHelp32 library Pin
    8-Feb-02 8:13
    suss8-Feb-02 8:13 
    GeneralRe: ToolHelp32 library Pin
    tbaskan20-Nov-02 9:17
    tbaskan20-Nov-02 9:17 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.