Home > Articles > Operating Systems, Server > Microsoft Servers

  • Print
  • + Share This

Putting the Pieces Together

The remaining stuff needed to make an application out of the above code snippets is more or less boilerplate code. Once you have retrieved the SYSTEM_PROCESS_INFORMATION data, you can choose any output format that fits your needs. To keep things simple, I have written a simple Win32 console-mode application called w2k_pnt.exe that displays processes and threads in a console window. By the way, the suffix "pnt" stands for "Processes'n'Threads". Because the output lists can be rather lengthy, I have added a filter mechanism to match the process names against an optional pattern string that may contain an arbitrary number of the wildcard characters '*' and '?'. Note that the first process started by the system—the so-called Idle process—has no name. The usName member of its process structure contains a NULL pointer. The Windows 2000 Task Manager displays it as System Idle Process, whereas my demo application simply says Idle.

In Example 1, I have issued the command w2k_pnt/p/treal*, which lists all processes whose names start with "real", as well as the threads these processes are hosting. Obviously, the output list comprises the RealPlayer and the RealJukebox applications by RealNetworks, Inc. As you can see, I just love to listen to MP3s during work!

Example 1: Sample Output of w2k_pnt.exe

D:\Program Files\DevStudio\MyProjects\w2k_pnt\Release>w2k_pnt /p/t real*

// w2k_pnt.exe
// Windows NT/2000 Processes'n'Threads V1.00
// 06-18-2001 Sven B. Schreiber
// sbs@orgon.com

Windows 2000 mode

PROCESS INFO (Filter: 'real*')
==============================

 PID PPID KBytes PFaults Handles Threads Date & Time Name
-------------------------------------------------------------------------------
 980  868  3724  2793   94    8 06-18 18:07 realplay.exe
 308  868  13344  6724   166   15 06-18 19:25 realjbox.exe
-------------------------------------------------------------------------------
  2 processes, 23 threads

THREAD INFO (Filter: 'real*')
=============================

PROCESS #980 (realplay.exe)

 TID Prio Base  Switches Address State Date & Time Kernel(sec) User(sec)
-------------------------------------------------------------------------------
 968  9  8   926597 77E878C1 5/13 06-18 18:07    8.191   3.595
 1288  10  8     2 77E92C50 5/13 06-18 18:07    0.000   0.000
 1300  15  15    100 77E92C50 5/06 06-18 18:07    0.000   0.000
 1304  12  10     2 77E92C50 5/06 06-18 18:07    0.000   0.000
 1308  10  8    239 77E92C50 5/13 06-18 18:07    0.000   0.000
 1312  9  8   917053 77E92C50 5/13 06-18 18:07    0.320   0.270
 1060  10  8     2 77E92C50 5/06 06-18 18:07    0.000   0.000
 1320  9  8   917065 77E92C50 5/13 06-18 18:07    0.390   0.320
-------------------------------------------------------------------------------
  8 threads                         8.901   4.185

PROCESS #308 (realjbox.exe)

 TID Prio Base  Switches Address State Date & Time Kernel(sec) User(sec)
-------------------------------------------------------------------------------
 280  9  8  1586897 77E878C1 5/13 06-18 19:25    61.598   47.117
 716  8  8     1 77E92C50 5/06 06-18 19:25    0.000   0.000
 1336  8  8     1 77E92C50 5/06 06-18 19:25    0.000   0.000
 1296  15  15    123 77E92C50 5/06 06-18 19:25    0.000   0.000
 524  12  10    132 77E92C50 5/13 06-18 19:25    0.030   0.010
 404  8  8     1 77E92C50 5/13 06-18 19:25    0.000   0.000
 768  10  10     1 77E92C50 5/06 06-18 19:25    0.000   0.000
 1264  10  8    193 77E92C50 5/13 06-18 19:25    0.000   0.000
 356  9  8   866495 77E92C50 5/13 06-18 19:25    19.938  180.709
 1056  6  6    6872 77E92C50 5/06 06-18 19:25    0.040   0.000
 860  8  8     2 77E92C50 5/06 06-18 19:25    0.010   0.000
 848  10  8   143867 77E92C50 5/06 06-18 19:25    0.690   0.260
 1020  8  8     1 77E92C50 5/13 06-18 19:25    0.000   0.000
 780  10  8   114796 77E92C50 5/13 06-18 19:25    0.480   0.360
 1332  8  8     2 77E92C50 5/06 06-18 19:25    0.000   0.000
-------------------------------------------------------------------------------
  15 threads                         82.786  228.456
TOTAL: 2 processes, 23 threads

Listing 6 shows how w2k_pnt.exe walks down the list of processes, displaying some of the most useful members in a table. Please compare this data to the process view of the Windows 2000 Task Manager—it should match very closely. Note how the psp pointer is moved forward by adding the value of the dNext member of the SYSTEM_PROCESS structure. The end of the list is reached if dNext is zero.

Listing 6 Enumerating Processes

VOID DisplayProcesses (PSYSTEM_PROCESS pspFirst,
            PWORD      pwFilter)
  {
  DWORD      dProcesses = 0;
  DWORD      dThreads  = 0;
  PSYSTEM_PROCESS psp    = pspFirst;
  WORD      awTime [] = L"mm-dd HH:MM";
  PFILETIME    pft;
  FILETIME    ft;
  SYSTEMTIME   st;
  PWORD      pwName;
  printf (L"\r\n"
      L" PID PPID KBytes PFaults Handles Threads "
      L"Date & Time Name\r\n"
      L"----------------------------------------"
      L"---------------------------------------\r\n");
  while (psp != NULL)
    {
    pwName = (psp->dUniqueProcessId
         ? psp->usName.Buffer
         : L"Idle");
    if (PatternMatcher (pwFilter, pwName, TRUE))
      {
      lstrcpy (awTime, L" ?   ? ");
      pft = &psp->ftCreateTime;
      if ((pft->dwLowDateTime || pft->dwHighDateTime) &&
        FileTimeToLocalFileTime (pft, &ft)     &&
        FileTimeToSystemTime  (&ft, &st))
        {
        sprintf (awTime, L"%02hu-%02hu %02hu:%02hu",
             st.wMonth, st.wDay,
             st.wHour, st.wMinute);
        }
      printf (L"%5lu %5lu %7lu %7lu %7lu %7lu "
          L"%-11s %s\r\n",
          psp->dUniqueProcessId,
          psp->dInheritedFromUniqueProcessId,
          psp->VmCounters.WorkingSetSize / 1024,
          psp->VmCounters.PageFaultCount,
          psp->dHandleCount,
          psp->dThreadCount,
          awTime,
          pwName);
      dProcesses += 1;
      dThreads  += psp->dThreadCount;
      }
    psp = (psp->dNext
        ? (PVOID) ((PBYTE) psp + psp->dNext)
        : NULL);
    }
  printf (L"%s----------------------------------------"
      L"---------------------------------------\r\n"
      L"%5lu process%s, %lu thread%s\r\n",
      (dProcesses ? L"" : L"+++ No matching names
+++\r\n"),
      dProcesses, (dProcesses == 1 ? L"" : L"es"),
      dThreads,  (dThreads  == 1 ? L"" : L"s" ));
  return;
  }

Once you have a pointer to the thread array of a process, walking down the list of threads is quite easy because the thread structures are of fixed size. Listing 7 shows the implementation used by w2k_pnt.exe. The only problem with the thread array is that it is located at a different offset within the process structures of Windows NT 4.0 and Windows 2000. Therefore, DisplayThreads() cannot be called safely unless you know which operating system your application is running on.

Listing 7 Enumerating Threads

VOID DisplayThreads (PSYSTEM_THREAD pstFirst,
           DWORD     dThreads)
  {
  PSYSTEM_THREAD pst      = pstFirst;
  WORD      awTime []   = L"mm-dd HH:MM";
  DWORDLONG   dlTotalKernel = 0;
  DWORDLONG   dlTotalUser  = 0;
  DWORDLONG   dlKernel, dlUser;
  PFILETIME   pft;
  FILETIME    ft;
  SYSTEMTIME   st;
  DWORD     i;
  printf (L"\r\n"
      L" TID Prio Base  Switches Address State "
      L"Date & Time Kernel(sec) User(sec)\r\n"
      L"----------------------------------------"
      L"---------------------------------------\r\n");
  for (i = 0; i < dThreads; i++)
    {
    lstrcpy (awTime, L" ?   ? ");
    pft = &pst->ftCreateTime;
    if ((pft->dwLowDateTime || pft->dwHighDateTime) &&
      FileTimeToLocalFileTime (pft, &ft)     &&
      FileTimeToSystemTime  (&ft, &st))
      {
      sprintf (awTime, L"%02hu-%02hu %02hu:%02hu",
           st.wMonth, st.wDay,
           st.wHour, st.wMinute);
      }
    dlKernel  = pst->ftKernelTime.dwHighDateTime;
    dlKernel <<= 32;
    dlKernel += pst->ftKernelTime.dwLowDateTime;
    dlKernel /= 10000;
    dlUser   = pst->ftUserTime.dwHighDateTime;
    dlUser  <<= 32;
    dlUser  += pst->ftUserTime.dwLowDateTime;
    dlUser  /= 10000;
    dlTotalKernel += dlKernel;
    dlTotalUser  += dlUser;
    printf (L"%5lu %4lu %4lu %10lu %08lX %2lu/%02lu "
        L"%-11s %8lu.%03lu %6lu.%03lu\r\n",
        pst->Cid.UniqueThread,
        pst->dPriority,
        pst->dBasePriority,
        pst->dContextSwitches,
        pst->pStartAddress,
        pst->dThreadState,
        pst->WaitReason,
        awTime,
        (DWORD) dlKernel / 1000,
        (DWORD) dlKernel % 1000,
        (DWORD) dlUser  / 1000,
        (DWORD) dlUser  % 1000);
    pst++;
    }
  printf (L"----------------------------------------"
      L"---------------------------------------\r\n"
      L"%5lu thread%s"
      L"                      "
      L"%8lu.%03lu %6lu.%03lu\r\n",
      dThreads, (dThreads == 1 ? L" " : L"s"),
      (DWORD) dlTotalKernel / 1000,
      (DWORD) dlTotalKernel % 1000,
      (DWORD) dlTotalUser  / 1000,
      (DWORD) dlTotalUser  % 1000);
  return;
  }

If you choose your application to be compatible to both Windows 2000 and NT 4.0, an operating system check is inevitable. The SystemVersion() function defined in Listing 8 does this job by calling GetVersionEx() and testing for platform VER_PLATFORM_WIN32_NT. SystemVersion() returns the major version number only; that is, 4 for Windows NT 4.0 or 5 for Windows 2000. The minor version number is irrelevant here.

Listing 8 Identifying the Operating System Version

DWORD SystemVersion (VOID)
  {
  OSVERSIONINFO ovi;
  DWORD     dVersion = 0;
  ovi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
  if (GetVersionEx (&ovi) &&
    (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT))
    {
    dVersion = ovi.dwMajorVersion;
    }
  return dVersion;
  }
  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.