Under The Hood

by Matt Pietrek

Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at 71774.362@compuserve.com.

Ihave a confession to make. For a long time, I resisted moving to Windows NT¨ as my primary operating system. I preferred Windows¨ 95 because I was more familiar with its architecture and knew how to extract much more in the way of system-level information from it. For instance, the Windows 95 TOOLHELP32 APIs, which let you easily obtain information on all processes, threads, and modules in the system, don’t appear (on the surface) to have any equivalent under Windows NT and the Win32 API.

If you dig a little deeper, though, Windows NT reveals a wealth of information. You just have to know where to look. In previous columns, I showed two methods for accessing system-level information in Windows NT. The first method, using the performance data from the Windows NT registry, was described in my April 1996 column. My August and November 1996 columns covered PSAPI.DLL, a redistributable DLL from the Windows NT 4.0 Win32 SDK. This month, I’ll dig a little deeper and examine a third way of accessing system-level information.

If you’re a curious type like me, somewhere in your Win32 travels you may have stumbled across a group of APIs exported from NTDLL.DLL that have names beginning with "NtQueryInformation," like NtQueryInformationFile and NtQueryInformationProcess. These APIs certainly sound like they could be useful. Alas, the Win32 SDK doesn’t mention these APIs. What’s up? First, looking in the SDK is barking up the wrong tree. And, when you do find mention of these APIs, there’s no formal documentation.

With no online help for these APIs, it’s up to you to piece together how to call these NtQueryInformation APIs. This month, I’ll show how to use one of them, NtQueryInformationProcess. Before I proceed, I’m obligated to point out that NtQueryInformationProcess isn’t part of the Win32 API. Microsoft could change or remove this API in the future. Likewise, this API isn’t available on Windows 95, so be aware of what you’re getting into if you want to use it. On the other hand, even if you don’t use NtQueryInformationProcess in your own code, it’s still interesting to know what it does, and who uses it.

To use NtQueryInformationProcess, you’ll need to put the Win32 SDK aside and turn to the Windows NT DDK. The DDK has many header files in an include directory that offer up information about the design and implementation of Windows NT. Among the most important of these files is NTDDK.H. There, you’ll find the following:

NTSYSAPI

NTSTATUS

NTAPI

NtQueryInformationProcess(

IN HANDLE ProcessHandle,

IN PROCESSINFOCLASS ProcessInformationClass,

OUT PVOID ProcessInformation,

IN ULONG ProcessInformationLength,

OUT PULONG ReturnLength OPTIONAL

);

This API doesn’t appear to be described anywhere in the DDK documentation either. But since Win32 APIs are usually called in a consistent manner, you can figure out what the parameters mean and how to correctly call the API. A few are fairly obvious. The first parameter should obviously be a process handle like you’d get back from CreateProcess or OpenProcess. The third parameter is a pointer to a buffer that the API fills in, and the fourth parameter tells the API how big the buffer is. The fifth parameter, ReturnLength, is a pointer to an unsigned long, but is labeled OPTIONAL. You can infer that this parameter will be filled with the length of the buffer needed to hold the information returned by the API.

The only tricky parameter to NtQueryInformationProcess is the second, ProcessInformationClass, which is of type PROCESSINFOCLASS. Luckily, elsewhere in NTDDK.H you’ll find that PROCESSINFOCLASS is just an enum. Figure 1 shows the enumeration values from NTDDK.H that NtQueryInformationProcess accepts. I’ll defer a description of what the enumerations mean until later.

Figure 1 PROCESSINFOCLASS Enums

typedef enum _PROCESSINFOCLASS {

ProcessBasicInformation,

ProcessQuotaLimits,

ProcessIoCounters,

ProcessVmCounters,

ProcessTimes,

ProcessBasePriority,

ProcessRaisePriority,

ProcessDebugPort,

ProcessExceptionPort,

ProcessAccessToken,

ProcessLdtInformation,

ProcessLdtSize,

ProcessDefaultHardErrorMode,

ProcessIoPortHandlers, // Note: this is kernel mode only

ProcessPooledUsageAndLimits,

ProcessWorkingSetWatch,

ProcessUserModeIOPL,

ProcessEnableAlignmentFaultFixup,

ProcessPriorityClass,

ProcessWx86Information,

ProcessHandleCount,

ProcessAffinityMask,

ProcessPriorityBoost,

MaxProcessInfoClass

} PROCESSINFOCLASS;

With all the parameters accounted for, it’s pretty easy to see what NtQueryInformationProcess does: the API fills in a buffer with the requested type of information for a specified process. Pretty simple, eh?

If everything I’ve described so far is correct, it should be relatively easy to construct a very small sample program that calls NtQueryInformationProcess and spits out the results of a query. Having been there and done that, I can tell you of some pitfalls that you’d encounter along the way. The first of these stumbling blocks is that NTDDK.H and WINDOWS.H do not get along. If you try to #include both WINDOWS.H and NTDDK.H in the same source file, you’ll get numerous compiler errors.

The reason for the compiler errors is that many of the include files in the DDK overlap with files in the Win32 SDK (and by implication, the Visual C++¨ version of those files). These overlapping files (WINNT.H, for instance) aren’t identical to their SDK counterparts. I tried for quite some time to get some combination of DDK files and SDK files to work together, but finally gave up. Instead, I extracted just enough of NTDDK.H to get the necessary definitions and created the NTQUERYINFORMATIONPROCESS.H file. The demo program that I’ll describe later on uses NTQUERYINFORMATIONPROCESS.H rather than NTDDK.H.

Once you successfully compile a call to NtQueryInformationProcess, the second pitfall comes at the link stage. You see, NtQueryInformationProcess is exported by NTDLL.DLL. Neither Visual C++ nor the Win32 SDK comes with an import library for NTDLL.DLL. One way to bypass this problem is to not call NtQueryInformationProcess directly, and instead use GetProcAddress. A simpler solution is to find an import library for NTDLL.DLL. Where can you find one? In the Windows NT DDK, of course! It’s in the \LIB\processor directory (for instance, \LIB\I386).

Having gotten past the twin hurdles of compiling and linking a call to NtQueryInformationProcess, you should be styling. But there are still a few flies in the ointment; not all of the possible values for the PROCESSINFOCLASS enum will work with the API. This is hinted at in the comment next to the line for ProcessIoPortHandlers, which reads "// Note: this is kernel mode only." So which PROCESSINFOCLASS values will NtQueryInformationProcess succeed with? Through rigorous tests involving cheese and hula hoops, the MSJ labs have determined that the following PROCESSINFOCLASS enums are worthwhile to send to NtQueryInformationProcess:

ProcessBasicInformation

ProcessQuotaLimits

ProcessVmCounters

ProcessTimes

ProcessDebugPort

ProcessLdtInformation

ProcessDefaultHardErrorMode

ProcessPooledUsageAndLimits

ProcessWorkingSetWatch

ProcessHandleCount

ProcessPriorityBoost

In general, when calling NtQueryInformationProcess, the buffer size (parameter 4) must exactly match the length of the information to be returned. Let’s go through the usable PROCESSINFOCLASSes and see what information they return, and hence, what the buffer size needs to be.

ProcessBasicInformation NtQueryInformationProcess fills in a PROCESS_BASIC_INFORMATION structure, which is defined in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. A particularly interesting field in this structure is the InheritedFromUniqueProcessId DWORD. This DWORD is the parent process ID that created the process being queried. Other useful fields include the exit status and base priority level for the process. The GetExitCodeProcess and GetProcessAffinityMask APIs use NtQueryInformationProcess and this enum.

ProcessQuotaLimits NtQueryInformationProcess fills in a QUOTA_LIMITS structure, which is defined in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. The fields of this structure define the upper boundaries of various system resources that the process is allowed to consume. In this case, resources means memory from the kernel mode, paged and nonpaged pools, the process working set, the pagefile, and the amount of processor time used.

ProcessVmCounters NtQueryInformationProcess fills in a VM_COUNTERS structure, defined in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. Most of the fields in the VM_COUNTERS structure are closely related to the fields in the QUOTA_LIMITS structure. The key difference is that the VM_COUNTERS information shows what the process has actually used, rather than how much it would be allowed to use. There are also a few unrelated fields that may be of interest, such as the page fault count. The GetProcessWorkingSetSize API is based on this enum.

ProcessTimes NtQueryInformationProcess fills in a KERNEL_USER_TIMES structure, also defined in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. This structure’s fields tell you the start and exit times of a particular process, as well as how much time has been spent in user mode and kernel mode. Since most processes in the system are alive, the ExitTime member is usually zero. The GetProcessTimes API is really just a wrapper around a call to NtQueryInformationProcess with this enum.

ProcessDebugPort NtQueryInformationProcess fills in a DWORD with the port number of the debugger for the process being queried. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to sizeof(DWORD). The debug port is a value that’s useless to ring 3 code. However, you can infer that a nonzero debug port means that the process is being run under the control of a ring 3 debugger such as the Visual C++ IDE or Turbo Debugger.

ProcessLdtInformation Based on the name, this enum should cause NtQueryInformationProcess to return information about LDT entries for the specified process. Since most Win32 processes don’t use the LDT, this enum is of limited use. In my testing, I was unable to get the API to return any LDT-related information for any process (even NTVDM).

ProcessDefaultHardErrorMode NtQueryInformationProcess fills in a DWORD with the error mode of the process being queried. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to sizeof(DWORD). "Error mode" in this case means the flags defined by the GetErrorMode API, and GetErrorMode is really just a wrapper around a call to NtQueryInformationProcess with this enum.

ProcessPooledUsageAndLimits NtQueryInformationProcess fills in a POOLED_USAGE_AND_LIMITS structure, defined in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. Many of the fields in this structure are related to information returned via the ProcessQuotaLimits enum and the QUOTA_LIMITS structure.

ProcessWorkingSetWatch NtQueryInformationProcess fills in a PROCESS_WS_WATCH_INFORMATION structure, defined as usual in NTDDK.H. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to the size of this structure. The fields in this structure describe the address of each page fault that brought a new page into memory and the address of the instruction that caused the fault. The GetWsChanges API in PSAPI.DLL is really just a wrapper around a call to NtQueryInformationProcess with this enum. I described the GetWsChanges function in my November 1996 column, so I’ll omit a more complete description here.

ProcessHandleCount NtQueryInformationProcess fills in a DWORD with the number of open handles in use by the process being queried. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to sizeof(DWORD).

ProcessPriorityBoost NtQueryInformationProcess fills in a DWORD with the priority boost of the process being queried. The ProcessInformationLength parameter to NtQueryInformationProcess should be set to sizeof(DWORD). The GetProcessPriorityBoost API is really just a wrapper around a call to NtQueryInformationProcess with this enum.

Most of the information available through NtQueryInformationProcess is obtainable through more formally documented means. Between the documented Win32 APIs (such as GetProcessTimes), the Windows NT registry performance data, and the PSAPI.DLL APIs, you can get at most of the information I’ve described above. If possible, try to use these other techniques. On the other hand, there’s something to be said for NtQueryInformationProcess. It’s simple to use (unlike the registry performance data), and doesn’t require redistributing a supporting DLL (PSAPI.DLL).

Putting NtQueryInformationProcess to Work

To show off the capabilities of NtQueryInformationProcess, I’ve written the NTQUERYINFORMATIONPROCESSDEMO program, winner of this year’s "Longest Sample App Name" award and shown in Figure 2. The listbox in the upper left contains a list of all running processes and their IDs. The process list doesn’t automatically update as processes start up and exit, so you might find the Refresh Processes button handy.

Figure 2 NTQUERYINFORMATIONPROCESSDEMO

Upon clicking on a process in the process list, all of the other listboxes fill up with the information specific to that process, which is obtained via a call to NtQueryInformationProcess. The headers at the top of each listbox correspond to the particular PROCESSINFOCLASS enum that the information was retrieved by. The one exception is the Miscellaneous list, where I lumped all items that are only a single DWORD (for example, ProcessHandleCount). The only PROCESSINFOCLASS enum that’s supported, but which the program doesn’t report, is ProcessWorkingSetWatch. The information returned requires too much interpretation for a small sample, and I’ve already covered the working set information in my November 1996 column.

The code for NTQUERYINFORMATIONPROCESSDEMO is all found within NTQUERYINFORMATIONPROCESSDEMO.CPP in Figure 3. The essential function is QueryProcessInformation, a nice wrapper around the NtQueryInformationProcess API. My wrapper function takes a process ID as one of its parameters rather than a process handle. This spares the rest of the code from being littered with calls to OpenProcess and CloseHandle. My QueryProcessInformation wrapper function also zeros out the input buffer before calling NtQueryInformationProcess, and checks the return value from NtQueryInformationProcess. Thus, my QueryProcessInformation can return a simple BOOL indicating the success or failure of NtQueryInformationProcess.

Figure 3 NTQUERYINFORMATIONPROCESSDEMO

//==================================================

// NTQUERYINFORMATIONPROCESSDEMO - Matt Pietrek 1997

// Microsoft Systems Journal, January 1997

// FILE: NTQUERYINFORMATIONPROCESSDEMO.CPP

//==================================================

#include <windows.h>

#include <stdio.h>

#pragma hdrstop

#include "psapi.h"

#include "ntqueryinformationprocess.h"

#include "ntqueryinformationprocessdemo.h"

 

// Helper function prototypes

void Handle_WM_COMMAND(HWND hDlg, WPARAM wParam, LPARAM lParam);

void Handle_WM_INITDIALOG(HWND hDlg);

void Handle_WM_CLOSE( HWND hDlg );

BOOL CALLBACK NtQueryInformationProcessDemoDlgProc(HWND,UINT,WPARAM,LPARAM);

int lbprintf(HWND hWnd, char * format, ...);

void StrFromLONGLONG( LONGLONG val , PSTR szOut );

DWORD GetDlgSelectedItemData( HWND hDlg, int ctlID );

void FakeFirstLbItemSelection( HWND hDlg, int listboxID );

void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt );

 

// ======================= String literals ===============================

char gszRegistryKey[]

= "Software\\WheatyProductions\\NtQueryInformationProcessDemo";

 

char g_AboutMsgTxt[] =

"NtQueryInformationProcessDemo shows processes information obtained "

"from the NtQueryInformationProcess function";

 

char g_AboutTitleTxt[]

= "NtQueryInformationProcessDemo - Matt Pietrek 1997, for MSJ";

 

// ======================= Start of code ===============================

 

//

// Wrapper function around NtQueryInformationProcess. Lets you pass

// in a PID, and takes care of creating and closing a process handle.

// Also takes care of other housecleaning work.

//

BOOL QueryProcessInformation( DWORD pid,

PROCESSINFOCLASS infoEnum,

void * pBuffer,

unsigned cbBuffer )

{

HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pid );

if ( !hProcess )

return FALSE;

 

// Zero the input buffer that NtQueryInformationProcess sees

memset( pBuffer, 0, cbBuffer );

 

DWORD retLen;

int retValue;

 

retValue = NtQueryInformationProcess( hProcess, infoEnum,

pBuffer, cbBuffer, &retLen );

 

CloseHandle( hProcess );

 

if ( retValue < 0 ) // NtQueryInformationProcess returns a negative

return FALSE; // value if it fails

 

return TRUE;

}

 

//

// Using functions from PSAPI.DLL, update the listbox in the upper left

// corner, and store the associated PID with each entry.

//

void UpdateProcessList( HWND hDlg )

{

// Get the treeview's HWND, then clear it

HWND hWnd = GetDlgItem( hDlg,IDC_LB_PROCESSES );

 

// Get the list of process IDs

DWORD aProcesses[1024], cbNeeded;

if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )

return;

// Calculate how many process IDs were returned

DWORD cProcesses = cbNeeded / sizeof(DWORD);

 

// Spit out the information for each ID

for ( unsigned i = 0; i < cProcesses; i++ )

{

char szProcessName[MAX_PATH] = "unknown";

DWORD pid = aProcesses[i];

HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |

The remainder of the NTQUERYINFORMATIONPROCESSDEMO code is boilerplate dialog box management. If you want to get a handle on where the hardcore code starts, check out the UpdateProcessInfoFields function. This function takes a process ID and is invoked whenever the user selects a new process in the process list. When called, the function in turn calls half a dozen other functions to fill in the various listboxes with process-specific information.

For example, the first thing UpdateProcessInfoFields does is call UpdateProcessBasicInformation, passing it the process ID. UpdateProcessBasicInformation declares a variable of type PROCESS_BASIC_INFORMATION, and passes a pointer to that structure, along with the process ID, to the QueryProcessInformation wrapper function. As described earlier, QueryProcessInformation is a wrapper around NtQueryInformationProcess. Assuming the call to NtQueryInformationProcess goes well, the UpdateProcessBasicInformation function formats and adds
information about each member of the PROCESS__INFORMATION structure to the relevant listbox.

While NtQueryInformationProcess isn’t a full-fledged Win32 API, enough of the information needed to use it can be gleaned from the NTDDK.H file. Some of the information it returns isn’t available in any other way (at least not from user-mode code). On the other hand, much of the system information it retrieves can be obtained via other means. While you may not ever need to use NtQueryInformationProcess, it’s comforting to know that it’s there if needed. Likewise, realizing that higher-level functions like GetProcessTimes are built atop it leads to an overall better understanding of the Windows NT architecture.

To obtain complete source code listings, see Editor's page.

 

Have a question about programming in Windows? Send it to Matt at cmailto:71774.362@compuserve.com.

This article is reproduced from Microsoft Systems Journal. Copyright © 1997 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S. and Canada, or (303) 678-0439 in all other countries. For other inquiries, call (415) 905-2200.