Explore Previously Uncharted Areas of Windows[TM] Using the ToolHelp Library

Paul Yao

Paul Yao is president of International Systems Design, a firm specializing in product development and programmer training for Windows. He is the creator of ISD's Windows Power Progamming Workshop.

{ewc navigate.dll, ewbutton, /Bcodeview /T"Click to open or copy the code samples from this article." /C"samples_3}

There are times, after a hard day of writing code for the MicrosoftÒ WindowsÔ operating system, when programmers sit back and think about the halcyon days of programming in the MS-DOSÒ operating system. They miss being able to do things like access video RAM directly and read and write in the operating system’s data areas. If you are such a programmer, ToolHelp is for you. ToolHelp gives you an easy way to delve into the guts of Windows1.

ToolHelp isn’t an application program. It is a library of functions and data structures contained in three files: a dynamic-link library (TOOLHELP.DLL), an import library (TOOLHELP.LIB), and an include file (TOOLHELP.H). All three files are shipped with the Microsoft Windows Software Development Kit (SDK) version 3.1, and TOOLHELP.DLL is shipped with the Windows 3.1 retail. ToolHelp can also run with Windows version 3.0. In fact, Microsoft is granting royalty-free licenses to distribute TOOLHELP.DLL with applications so that tools based on this DLL will run in Windows 3.0.

In this article, I explain some of the different types of handles in Windows to clarify what ToolHelp lets you access. I’ll also provide an overview of ToolHelp’s features and the complete source code to TaskMstr, a program I’ve written using ToolHelp. TaskMstr shows you how much memory is used by every task in the system.

Figure 1 lists the nine categories of ToolHelp routines. Most categories let you explore aspects of Windows managed by the Windows KERNEL. None deals directly with GDI data (except the local heap walking functions), and one deals with a USER object--the window class enumeration routines.

Figure 1 Categories of Toolhelp Routines

Category Explanation Associated Data Structures

System Memory Information Enumerate the objects on the global heap, query specific details about the global heap, query available physical RAM, and query the state of the virtual memory manager (enhanced mode only). GLOBALINFO

GLOBALENTRY

MEMMANINFO

SYSHEAPINFO

Local Heap Walking Enumerate objects on any local heap in the system. LOCALINFO

LOCALENTRY

Stack Trace Information Query the owner of return addresses on the stack. STACKTRACEENTRY
Module Information Enumerate modules and query the name of the associated file and the usage count. MODULEENTRY
Task Information Enumerate tasks and query various task-specific fields, including the stack, message queue, and PSP. TASKENTRY
Window Class Enumeration Enumerate registered window classes and query owner information. CLASSENTRY
Interrupt Handling (INT 0H) Set up a handler for various software interrupts, including divide by zero, invalid opcode (INT 6H), and General Protection Fault (INT 13H). N/A (Interrupt information passed as parameters to a notification callback routine.)
Notification Callbacks Set up a notification procedure. This callback lets you know about various system initialization events, including segment loading and freeing, task switches, task and task termination. The callback also reports on various DLL events, including the start and termination of a DLL. NFYSTARTDLL

NFYLOADSEG

NFYRIP

Timer Function A 1-millisecond resolution clock to help profiling. TIMERINFO

Why ToolHelp?

All of ToolHelp’s services could be obtained by peering into the system’s own data areas. But these areas may change from one version of Windows to the next. Also, accessing some structures might be impossible or might cause a General Protection Fault. With ToolHelp, Microsoft provides a standard programming interface that helps minimize the impact of new versions on tool developers. However, certain aspects of ToolHelp are dependent on the segmented architecture of Windows 3.x and thus won’t be a straightforward port to the Windows NTÔ operating system.

The Module Handle

Experienced Windows programmers spend a lot of their time working with different types of handles: window handles, DC handles, memory handles, module handles, task handles, and instance handles. Since these handles play an important role in the way ToolHelp reports its information, I’m going to describe each in some detail.

To understand the module handle, you must first understand the role of a module. A module is the memory image of an executable file (EXE, DLL, and so on). Windows has two types of modules: applications and dynamic-link libraries. Modules are created when an executable file is opened and read into memory. The first memory object created for a module is known as the module database. The Windows KERNEL creates module databases from data in the executable file’s header. The module handle is the memory handle to this object.

Most programs never use a module handle explicitly. Yet the module database plays many key roles in the operation of the Windows KERNEL. First, the KERNEL refers to the module database to load segments from an executable file. Executable files can contain three types of objects: code segments, a module’s automatic data segment, and resources (menu templates, dialogs, and so on).

The second role of the module database has to do with segment ownership and cleanup. When segments are allocated, they are assigned to a module database. In some cases, ownership passes through to a third party--a task database (described in the next section). The KERNEL cleans up the segments left behind when a module terminates. It makes its decision based on the associated module database.

Figure 2 shows the hierarchical relationship between module databases and the segments created and used by applications and DLLs. When an application terminates, KERNEL frees the task database, the associated automatic data segments, and any dynamically allocated segments. When every instance of an application has terminated, the usage count of the module decrements to zero. This is what signals the KERNEL that a module database and its associated global heap objects need to be cleaned up.

Figure 2 The Relationship Between Module Databases and the Segments Owned by Applications and DLLs

This automatic cleanup appears to eliminate any worry about application cleanup. It’s a good coding practice, however, to take care of cleaning up any objects you create. The code you write today may be used long into the future and may have to be maintained by programmers who come after you. Cleaning up after yourself will help make their jobs easier.

When you use ToolHelp to walk the global heap, keep in mind that the view of the heap depicted in Figure 2 is from the point of view of an application or a DLL. ToolHelp, on the other hand, reports segment ownership as seen by the Windows KERNEL. To the KERNEL, a task database owns itself and the dynamically allocated segments that the application has allocated. All other objects are owned by the module database.

The Task Handle

Like a module handle, a task handle identifies a global heap object, a task database (see Figure 3). The task database contains task-specific values such as the current drive and directory, a handle to the segment containing the task’s private message queue, the associated module handle, the associated instance handle, information about the stack, and the MS-DOS Program Segment Prefix (PSP). Whereas a module database is created for every executable used by the Windows KERNEL, a task database is created for every instance of every application that runs. For example, two instances of Clock and one instance of Notepad result in three task databases.

Although a Windows programmer rarely works with a module handle, there are times when the task handle is useful. You’ll most likely use the task handle if you’re writing a DLL and need to distinguish one task from another. You don’t need ToolHelp to retrieve this identifier. GetCurrentTask does the job nicely. Also, when you need to know the number of tasks in the system, the standard Windows GetNumTasks function gives the current count.

Windows programmers can get along nicely without bothering to think about either the task handle or the module handle. However, they typically rely on a third type of handle when their applications need to identify themselves to the system: the instance handle.

Figure 3 The Task Database Contains an Instance Handle that Identifies the Automatic Data Segment

The Instance Handle

As you already know, two instance handles are passed to a Windows program at its WinMain entry point. Most programmers treat these handles as "magic cookies." When needed, an instance handle gets passed to the Windows library routines to make things work correctly. But what exactly does this handle refer to?

Simply stated, the instance handle is a global memory handle that identifies an instance’s automatic data segment. This segment, after all, is the single item that makes one instance unique from every other instance. But these statements don’t reveal why this handle is so important to the Windows KERNEL. The Windows KERNEL uses the instance handle for three purposes: to access an instance’s data segment, to access a module’s resources, and to assign ownership to a particular task.

The first and most obvious use of an instance handle is to access an application’s automatic data segment. That’s the primary reason CreateWindow accepts an instance handle as its tenth parameter. The message delivery mechanism uses this to ensure that a window procedure has its data segment (DS) register set up properly. For callback procedures other than the window procedure, the MakeProcInstance routine uses the instance handle to create a tiny piece of code called an instance thunk. An instance thunk helps install the correct data segment value into the AX register before a callback procedure gets called.

The second use of an instance handle is to access a module’s resources. The clearest example of this occurs with the Load routines. For example, to load a menu that has been stored as a resource, call LoadMenu. The first parameter is an instance handle. The Windows KERNEL uses the instance handle to locate the module database for the requested resources. This is done because, as I mentioned earlier, the module database holds the location of each and every object in the EXE file for an application or the DLL file for a dynamic-link library.

Although the resource-loading routines take an instance handle, you can also pass a module handle. Do this when you’re loading a resource from a dynamic-link library. Instead of passing your program’s hInstance, substitute the library handle returned by the LoadLibrary function. Do this to load any type of resource stored in a DLL, such as menus, dialog boxes, fonts, and bitmaps.

USER uses the instance handle to assign ownership of an object to a particular task or, in the case of a DLL, to a particular module. For example, when you register a window class or create a window, ownership of those objects is tied to the data segment identified by the hInstance for tasks or to the related module database for DLLs.

Window classes, which can be shared between multiple instances of an application, are owned by the module. When you pass RegisterClass an instance handle (embedded in a WNDCLASS structure), USER gets the module handle associated with the instance handle. When every instance of an application has terminated, USER frees the class data.

Understanding these three key handles and their associated data structures will help you as you explore the ToolHelp library.

ToolHelp Rules of the Road

The developers of ToolHelp adopted a distinct programming style. Understanding this style will simplify your use of ToolHelp and help you avoid some basic mistakes. I’ve noticed three patterns.

First, though ToolHelp lets you peer into the system’s private data structures, you don’t get a pointer to directly access these structures. Instead, when calling a ToolHelp routine, you provide a buffer to be filled with the requested data.

Second, ToolHelp has structures that describe the buffers; the names of the data structures are listed in Figure 1. The definitions are in TOOLHELP.H. As an example, MODULEENTRY describes the buffer that gets filled when you ask for module information.

typedef struct tagMODULEENTRY

{

DWORD dwSize;

char szModule[MAX_MODULE_NAME + 1];

HANDLE hModule;

WORD wcUsage;

char szExePath[MAX_PATH + 1];

WORD wNext;

} MODULEENTRY;

Third, every ToolHelp structure shares a common first element: dwSize. Before calling any ToolHelp routine, you must initialize this value to reflect the size of the structure. For example, before calling ModuleFirst, here’s what you must do:

MODULEENTRY me;

me.dwSize = sizeof(MODULEENTRY);

ModuleFirst(&me);

If you forget to initialize the size field, every call to a ToolHelp routine will fail.

Why does every ToolHelp routine require size information? Shouldn’t each routine know the size of its output buffer? Obviously, the answer is yes. There are two advantages to asking callers to provide size information: more robust operation and version control. More robust operation occurs because ToolHelp won’t fill in a buffer if the size information is incorrect. Like a swimmer testing the water temperature before diving in, ToolHelp makes sure the calling application is truly ready to receive the data before writing into the application’s data area. If there is any doubt, ToolHelp fails the call instead of making a potentially bad situation worse.

As future versions of ToolHelp are created, the size parameter also serves as a version counter for each routine. When new capabilities are created for a particular routine, its return buffer will grow accordingly. By referring to the size field, the routine itself can distinguish callers who expect the old buffer from callers who expect the new buffer. Both types of callers can quickly and easily be assisted with a minimum of overhead.

With this introduction to the ToolHelp library behind us, we’re ready to examine the nine types of system information (see Figure 4). We’ll start with system memory information, which provides details on the available system memory and use of the USER and GDI heaps and the Windows global heap.

Figure 4 The ToolHelp APIs

System Memory Information

GlobalEntryHandle
GlobalEntryModule
GlobalFirst
GlobalHandleToSel
GlobalInfo
GlobalNext
MemManInfo
MemoryRead
MemoryWrite
SystemHeapInfo
Local Heap Information

LocalFirst
LocalInfo
LocalNext
Stack Information

StackTraceCSIPFirst
StackTraceFirst
StackTraceNext
Module Information

ModuleFindHandle
ModuleFindName
ModuleFirst
ModuleNext
Task Information

TaskFindHandle
TaskFirst
TaskGetCSIP
TaskNext
TaskSetCSIP
TaskSwitch
TerminateApp
Window Class Information

ClassFirst
ClassNext
Interrupt Handling

InterruptRegister
InterruptUnRegister
Notification Callback

NotifyRegister
NotifyUnRegister
Timer Function

TimerCount

System Memory Information

Figure 4 lists the ToolHelp routines that provide systemwide information about objects on the global heap, objects in the GDI and USER local heaps, and memory managed by the virtual memory manager.

If you write Windows debugging programs, you will need to work with the global heap routines in ToolHelp. A debugger, for example, can call GlobalEntryModule to track down the code segments in a particular application or DLL. The MemoryRead and MemoryWrite routines can be used by a debugger to read machine instructions and data locations and to create breakpoints.

These routines can also help you even if you don’t build debuggers. You could, for example, use the memory routines to create a test tool that, on a timer tick, checks the validity of an application’s global memory objects or to create other custom development tools. Or you could check the free space in the GDI and USER heaps when your program starts and terminates. The USER heap stores the data for the various user-interface objects, such as windows, menus, and cursors. The GDI heap stores drawing object information such as DCs, pens, brushes, and fonts. If the amount of free memory changes (assuming no interference from other programs), you know that somewhere you’ve forgotten to free a GDI or USER object. Here is a piece of code that queries the free space in these heaps:

char ach[120];

SYSHEAPINFO sysheap;

sysheap.dwSize = sizeof(SYSHEAPINFO);

SystemHeapInfo(&sysheap);

wsprintf(ach, "Start: USER (%u%%), GDI (%u%%)",

sysheap.wUserFreePercent,

sysheap.wGDIFreePercent);

MessageBox(NULL, ach, "YourApplication", 0);

ToolHelp can also report on the activity of the virtual memory manager. When you call MemManInfo, ToolHelp reports things such as the page size (4KB in Windows 3.1), the number of available pages, the number of allocated pages, and the largest block of contiguous free memory. This assumes that you’re running in enhanced mode. In standard mode, only the last item, the largest free block, is reported.

Local Heap Walking

Just about every Windows-based application will have one local heap. The normal startup code for an application creates a heap in the application’s default data segment. A DLL can have a local heap if it calls LocalInit. This routine allows applications and DLLs to build local heaps in any dynamically allocated segment. In each local heap, you could then perform subsegment allocation by calling the Windows local heap allocation routines LocalAlloc and LocalReAlloc.

In the same way ToolHelp walks the system global heap, it lets you walk local heaps (see Figure 4). You can find all of the local heaps in the system by first walking the global heap. Segments that contain a local heap will have a special flag set, wHeapPresent, in the GLOBALENTRY structure.

When walking a local heap, ToolHelp fills in a LOCALENTRY buffer. This structure has the following format:

typedef struct tagLOCALENTRY

{

DWORD dwSize;

HANDLE hHandle;

WORD wAddress;

WORD wSize;

WORD wFlags;

WORD wcLock;

WORD wType;

WORD hHeap;

WORD wHeapType;

WORD wNext;

} LOCALENTRY;

As you might expect, ToolHelp gives you the handle of the object (hHandle), the size (wSize), and tells you whether it is fixed, moveable, or free (wFlags). Oddly enough, there does not seem to be a flag indicating whether an object is discardable or not. You can call LocalFlags with the LMEM_DISCARDABLE parameter, which will give you this information.

Perhaps the most interesting aspect of ToolHelp’s local heap-walking is being able to report on specific objects in the USER and GDI local heaps. When the wHeapType field is USER_HEAP or GDI_HEAP, ToolHelp uses the wType field to report on the type of object stored in a USER or GDI heap object. For example, when walking the GDI heap and the object is a display context (DC), the value of wType will be LT_GDI_DC (see TOOLHELP.H in Figure 5). Or when walking the USER heap and a local heap object contains a window data structure, wType has a value of LT_USER_WND.

Stack Tracing

Unless you’re an assembly language programmer or a compiler writer, you won’t spend much time worrying about the stack. You will, however, have to make sure your stack is large enough. You set the size of the application’s stack with the STACKSIZE statement in your application’s module definition (DEF) file. The compiler uses the stack for four things: to pass parameters to called functions, to save return addresses, for local (automatic) variables, and to save register states. When an application calls a DLL, the DLL uses the application’s stack. That’s the reason you don’t see a STACKSIZE statement in a DLL’s DEF file.

Figure 4 shows the APIs ToolHelp provides for tracing the stack. These functions enable a debugger to show who has called whom. They provide stack trace information in several forms: the module handle and segment number identify which code segment was involved and the instruction pointer (the CS and IP register values) tells you the actual machine address. Both are useful information to a debugger.

The stack trace information will help you check for stack overflows. Every application’s automatic data segment contains information about the boundaries of the stack segment. Unfortunately, compiler developers and library writers haven’t included Windows-compatible stack-checking routines. When they do, you’ll need to enable stack checking when you compile.

Module Information

Although there are two types of modules, applications and DLLs, the files from which they are created have the exact same format. On disk, a single bit differentiates one from the other.

Figure 4 lists the four ToolHelp functions that give module information. You can query module data by providing a module handle (ModuleFindHandle) or the name of the module (ModuleFindName). As with other ToolHelp functions, module query routines let you scan the system’s entire list of modules. Start by calling ModuleFirst; then query subsequent modules by calling ModuleNext. My sample program, TaskMstr (see Figure 6), demonstrates how to build a module list by calling these two routines.

In your zeal for finding module information, don’t limit your search to ToolHelp. The regular Windows libraries provide three useful routines: GetModuleFileName returns the name of the file that created the module, GetModuleHandle lets you convert a module name into its corresponding module handle, and GetModuleUsage returns a module’s usage count.

Figure 6 TaskMstr.

Task Information

Windows schedules program execution around tasks. In the same way you build a list of modules, ToolHelp lets you build a list of tasks. You do this by calling TaskFirst and TaskNext until you exhaust the task list.

To connect windows with tasks, call the regular Windows API routine GetWindowTask. Building a list of windows takes a little more effort. You call EnumWindows, providing it with the address (generated by a call to MakeProcInstance) of a callback function. Or you can use GetWindow, which does the same job as EnumWindows but doesn’t require a callback.

Whatever your specific needs, the list created by TaskFirst and TaskNext will allow you to query for other important system values. Here’s the structure of the buffer filled in by these routines:

typedef struct tagTASKENTRY

{

DWORD dwSize; // sizeof TASKENTRY

HANDLE hTask; // Our task handle.

HANDLE hTaskParent; // hTask of parent task.

HANDLE hInst; // Instance handle.

HANDLE hModule; // Module handle.

WORD wSS; // Stack segment register.

WORD wSP; // Stack pointer.

WORD wStackTop; // Stack top.

WORD wStackMinimum; // Stack minimum.

WORD wStackBottom; // Stack bottom.

WORD wcEvents; // Count of pending messages.

HANDLE hQueue; // Memory handle of message

// queue.

char szModule[MAX_MODULE_NAME + 1]; // Module name

WORD wPSPOffset; // Offset of PSP in task

// database.

HANDLE hNext;

} TASKENTRY;

This structure provides the task’s module handle and instance handle. You also get stack information, the module name, a handle to the segment with the application’s message queue, and the count of events pending in the queue.

Some ToolHelp routines give task information that is, again, clearly intended for debugger writers. For example, TaskGetCSIP will tell a debugger about the next instruction that will be executed. A debugger will probably want to use this routine with the various stack-tracing routines to change the state of the application’s stack. TaskSetCSIP can then be used to start the application at a particular machine address. The TaskSwitch and TerminateApp routines are useful for applications that want to jump in and change the state of the system. These are things you should not do lightly, but when you need them you can use ToolHelp.

Window Class Enumeration

The Windows API lets you enumerate windows (EnumTaskWindows and EnumWindows), child windows (EnumChildWindows), clipboard formats (EnumClipboardFormats), fonts (EnumFonts), metafile records (EnumMetaFile), GDI objects (EnumObjects), and window properties (EnumProps).

Only through ToolHelp, however, does Windows allow you to enumerate the classes that have been registered. Figure 4 shows the two functions that allow you to do this: you call ClassFirst to start the process and ClassNext for each subsequent class. You end up with an array of CLASSENTRY values:

typedef struct tagCLASSENTRY

{

DWORD dwSize;

HANDLE hInst;

char szClassName[MAX_CLASSNAME + 1];

WORD wNext;

} CLASSENTRY;

This structure only provides the name of the class and the instance that registered the class. For more details about the class (as defined in the WNDCLASS structure), call GetClassInfo. For example, here’s how to get information about the list box class:

WNDCLASS wc;

GetClassInfo(NULL, "listbox", &wc);

Window classes are owned by the instance handle you provide when you register the class. Internally, Windows converts the instance handle to the associated module handle. That’s the reason you register an application’s window handle only once.

This can lead to trouble when a DLL registers a window class. If you provide an application’s instance handle, the class will disappear when all the instances of the application terminate. If other applications were using the class when this happens, there will be a problem. You’ll most likely want to provide your DLL’s module handle when you register a class. That will ensure that the class stays around as long as the library needs it.

Interrupt Handling

When Microsoft moved Windows to protected mode, Windows gained access to extended memory. Windows also opened the door for protection violations, the dreaded Unrecoverable Application Error (UAE). Debuggers that wish to trap these errors can do so by registering an interrupt-handling routine. This routine is called when any of the interrupts in Figure 7 are generated.

When a debugger’s interrupt-handling routine is called, the debugger can either respond to it or pass it on to the next handler in the interrupt-handling chain (Figure 4). When an interrupt-handling routine is registered, it will be called for an exception generated by any program. A debugger would choose to ignore an exception when, for example, it was generated by a program other than the program being debugged.

Figure 7 ToolHelp Interrupts

Interrupt Number Description

INT 0H Divide by zero
INT 1H Debugger exception
INT 3H Debugger breakpoint
INT 6H Invalid opcode
INT 12H Stack exception
INT 13H General Protection (GP) Fault
INT 14H Page fault
INT 256H Ctrl-Alt-SysReq

Notification Callbacks

Part of a debugger’s job is to keep track of the state of the system. If a DLL gets loaded or freed, for example, a debugger might want to know. ToolHelp’s notification callback support can simplify this process for debugger developers. Figure 4 lists the two routines provided by ToolHelp. One routine connects a notification routine into the system; the other disconnects the routine. Any application that cares to listen to ToolHelp’s broadcasts can learn what other applications and their DLLs are up to.

Figure 8 lists 14 notification codes that ToolHelp sends you. These fall into four groups: task-related, module-related, segment-related, and those geared toward a debugger developer.

Figure 8 ToolHelp Notifications

Category Notification Code Description

Task NFY_STARTTASK An application has started
  NFY_EXITTASK A program is exiting
  NFY_TASKIN Context switch is occurring into a task
  NFY_TASKOUT Context switch is occurring out of a task
Module NFY_STARTDLL A DLL is loading
  NFY_DELMODULE A module is being freed (both application and DLL modules)
Segment NFY_LOADSEG A code or resource segment is being loaded from an EXE file
  NFY_FREESEG A segment is being freed (but not discarded)
Debugger NFY_INCHAR Used to obtain a reply to the "Abort, Retry, Ignore?" question; needs to return with the ASCII character
  NFY_LOGERROR Results from a call to the KERNEL LogError function
  NFY_LOGPARAMERROR Results from a call to the KERNEL LogParamError function
  NFY_OUTSTR OutputDebugString has been called by an application or a DLL to request that a string be displayed
  NFY_RIP The Windows debug libraries have generated a fatal exit "rest in peace" (RIP) message
Misc NFY_UNKNOWN Unknown type of notification, which you should ignore

The first four notification codes are task-related. These codes will tell you about tasks being created (NFY_STARTTASK) and tasks being destroyed (NFY_EXITTASK). By passing a special code to the NotifyRegister routine, NF_TASKSWITCH, you’ll receive NFY_TASKIN and NFY_TASKOUT messages when task switches occur. The API documentation recommends you avoid this type of notification since it can cause performance problems. You may have noticed that similar warnings are posted with the SetWindowsHook function, but that hasn’t stopped many utility vendors from using it.

The two module-related notifications signal changes in the available modules in the system. When a DLL is created, the notification function is called with the NFY_STARTDLL code. This NFY_STARTDLL code is analogous to the NFY_STARTTASK code. When either type of module, application or DLL, terminates, the NFY_DELMODULE code is sent.

You may have noticed that something seems to be missing: there isn’t a module notification when an application starts. If you need to update a list of modules continually, you’ll have to rely on the task startup notification, NFY_STARTTASK, to know when to add an application’s module information to your list of modules.

You must limit the work you do in a notification procedure. The reason is simple: Windows calls your notification procedure while rearranging its internal state. If you execute lengthy procedures such as creating windows, opening files, allocating memory, or sending output to a printer, you run the risk of a race condition. A safer approach is to place a message in your application’s queue by calling PostMessage. With this approach, your notification routine is reentrant. When your window procedure receives this message, as part of the system’s normal nonpreemptive scheduling, you can respond to the notification.

There is one more thing. Just like dialog box procedures, a notification procedure should be tagged as an entry point. You need to export your notification procedure and create an instance thunk. A procedure is exported when the _export pragma is included as part of its declaration.

BOOL FAR PASCAL _export trToolHelpNotify(WORD wID,

DWORD dwData)

Call MakeProcInstance to create the instance thunk. This ensures that your notification procedure can access your program’s static data.

Timer Functions

The timer function group contains a single function: TimerCount. TimerCount functions like a stopwatch. It lets tool developers measure the passage of time with a resolution of 1 millisecond. A call to TimerCount returns the amount of time since the current task awakened as well as the total time since the current virtual machine was created.

ToolHelp provides this service to help you create profiling tools. Profiling tools let you measure how much time is spent in different sections of your application. With this information, you can make informed judgments about where to spend your time improving your application’s performance.

Before ToolHelp, developers of profiling tools had to do some fairly nasty things to measure time accurately, such as steal the INT 8H system timer, reprogram the hardware timer, and write virtual drivers. ToolHelp’s timer services greatly simplify the task of measuring time.

ToolHelp’s timer services also provide uniform timer service in both standard and enhanced modes. In enhanced mode, ToolHelp calls the virtual timer device (VTD.386) to guarantee 1ms resolution. In standard mode, virtual timer services are not available. To bridge the gap between the two modes, ToolHelp reprograms the system clock. While providing profilers with the timer services they need, it worries about servicing the CPU’s regular 55ms heartbeat.

Timer services can also be useful if your program does background processing. By background processing, I mean any housekeeping chore that doesn’t directly result from a user action. For example, a database program might write new data to disk, a word processing program might repaginate, and a spreadsheet program might perform background calculations.

Any time a program does anything in the background, it risks getting in the way of the user. Your goal is to schedule background tasks during times when the user is relatively inactive. A problem in achieving this goal is that it is difficult to predict when a user will be inactive; it depends on the type of application, the habits of the user, and the tasks the user wishes to perform. One solution is to profile the user’s actions. Background tasks can then be performed during periods of relative inactivity. This type of smart scheduling can continue benefiting a program even when it’s running in a multithreaded programming environment such as that in Windows NT2. After all, with or without preemptive multitasking, a computer has only a finite amount of processor power.

The Task Master

To demonstrate ToolHelp’s capabilities, I wrote TaskMstr (see Figure 6), which lists the current tasks in the system and graphically displays the memory use of each. Before writing TaskMstr, I used ToolHelp to create a global heap walking utility similar to HeapWalker. Since the goal of ToolHelp is to enable developers to write these kind of utilities, I wanted to see for myself whether this goal had been met.

TaskMstr displays two lists. The left list shows all the tasks that are present in the system. The user selects a task with a mouse click. In response, TaskMstr shows a second list, on the right, that summarizes the task’s module memory use. The summary reflects the seven types of memory objects a program can own (see Figure 9). Every 5 seconds, TaskMstr scans the global heap to ensure that its list is up to date.

Figure 9 Memory Objects Monitored by TaskMstr

Type Description

Dynamically Allocated Segments Segments allocated by calling GlobalAlloc. In some cases, such as the MS-DOS command prompt, you’ll notice the task list total is different from the module total. The task list summarizes the memory owned by tasks. The module total shows the objects owned by the task and by the task’s module.
DGroup Automatic data segments. A task’s DGroup typically holds its static data, its stack, and its local heap. A DLL does not have to have a DGroup. When it does, a DLL’s DGroup will hold its static data and an optional local heap. It won’t have a stack, since DLLs use the stack of the calling task.
Other Data Data segments other than the automatic data segment.
Code Code segments.
Task Database The storehouse for instance-specific data, such as register states, a program’s PSP, and a reference to the task’s message queue.
Resources Read-only data objects that are bound into the EXE file at program creation time and used to support user-interface objects (like menus, dialogs, cursors, and icons), graphical objects (like fonts and bitmaps) and other discardable memory objects (like string tables and custom resources).
Module Database RAM-resident copy of an executable file’s header. Used by the KERNEL to bring objects from the EXE files and to perform dynamic linking fixups.

For a display, TaskMstr creates two list boxes. These aren’t ordinary list boxes, as you may have noticed; they are owner-draw list boxes. I chose list boxes because they hide the messiness of maintaining the list and do nice things such as managing scrolling and painting. When an owner-draw list box needs something, it sends its parent window one of several messages: a WM_MEASUREITEM message asks the height of a list box item; a WM_DRAWITEM message asks that individual items be drawn.

TaskMstr has three C source files: LISTS.C, INIT.C, and TASKMSTR.C (see Figure 10). The first file, LISTS.C, contains two functions that use ToolHelp functions to create the primary lists used by TaskMstr: trBuildSegmentList creates a list of global segments, and trBuildTaskList creates a list of tasks. To help you differentiate TaskMstr’s functions from the functions in the Windows and ToolHelp APIs, all of TaskMstr’s routines have a tr prefix (with the single exception of the window procedure, TaskMstrWndProc). The second file, INIT.C, contains two initialization routines that set up metric and color constants used throughout the program. The third file, TASKMSTR.C, contains the program’s WinMain entry point and its single window procedure, TaskMstrWndProc. Except for the ToolHelp notification procedure, most of the functions in this file maintain the various windows on the screen.

Building a Segment List

TaskMstr’s trBuildSegmentList routine creates a list of segments in the system, which it stores in a global memory object. Even before this routine is called, TaskMstr’s main window procedure has preallocated a buffer to hold the segment list. I wanted to avoid changing the global heap while trying to create a segment list.

The code that allocates this object is shown here:

// Create empty segment list.

//

lpheList = (GLOBALENTRY _huge *) GlobalAllocPtr(GMEM_MOVEABLE,

SEG_INIT_COUNT * sizeof(GLOBALENTRY));

Although GlobalAllocPtr looks like a Windows function call, it is a macro Microsoft introduced with Windows 3.1. Microsoft also dropped support for real mode in this version of Windows. With this change, application programs don’t have to follow the allocation and locking semantics of Windows 1.x, 2.x, and 3.0. You can lock a global memory object immediately after you allocate it and only unlock it before freeing it. The GlobalAllocPtr macro combines the calls to allocate and lock a global memory object, GlobalAlloc and GlobalLock. The result is a pointer to the newly allocated segment.

One complication in building a segment list is that the list itself is stored in a global heap object. Allocating this buffer can cause the size of the global heap to grow. In a low-memory situation, the Windows KERNEL might have to discard segments to satisfy the allocation request. My segment list creation routine, trBuildSegmentList, responds to this problem in two ways. It tries to allocate its buffer three times, and it allocates two extra locations to make sure it can accommodate segments that get added to the global heap by the very process of asking about them.

Another complication faced by trBuildSegmentList is that the size of the segment list could exceed the maximum size of a segment. For this reason, TaskMstr uses a huge pointer for the segment buffer instead of a far pointer. Here’s the static (global) variable TaskMstr uses to hold a pointer to this buffer:

GLOBALENTRY _huge * lpheList;

In general, huge pointers are expensive. To get an idea of what’s involved, Figure 11 compares the cost of loading and incrementing three types of pointers: near, far, and huge. Clearly, the near pointer loads and increments the fastest. But because the segment list can grow longer than a single segment, you must use either a far pointer or a near pointer. Both take the same number of cycles to load, but notice that the huge pointer takes three times as many instructions to increment. The reason huge pointer arithmetic is more work is because a segment boundary might be crossed.

Figure 11 The Cost of Near, Far, and Huge Pointers

Three pointers and one static string are defined as follows:

char * p;

char far * lp;

char _huge * hp;

staticchar achString[] = "String";

Here is the cost in machine cycles (on a 386) of assigning the address of the static string to the pointer:

C instruction Machine instructions   Cycles

p = ach;

mov WORD PTR [bp-4],OFFSET XXXX

  2
    Total 2

lp = ach;

mov WORD PTR [bp-12],OFFSET XXXX

  2
 

mov WORD PTR [bp-10],ds

  2
    Total 4

hp = ach;

mov WORD PTR [bp-8],OFFSET XXXX

  2
 

mov WORD PTR [bp-6],ds

  2
    Total 4

Here is the cost in machine cycles (on a 386) of incrementing the pointers:

C instruction Machine instructions   Cycles

p++;

inc WORD PTR [bp-4]

  6
    Total 6

lp++;

inc WORD PTR [bp-12]

  6
    Total 6

hp++;

add WORD PTR [bp-8],1

  7
 

sbb ax,ax

  2
 

and ax,OFFSET __AHINCR

  2
 

add WORD PTR [bp-6],ax

  7
    Total 18

How could I make this more efficient? There is no single right answer for every situation. TaskMstr uses several loops to walk the segment list, incrementing a pointer from the beginning to the end of the list. To make this execute faster, I could create two loops--an outer loop to handle intersegment walking and an inner loop to run from the beginning to the end of each segment. I would use far pointers instead of huge pointers in the inner loop. Segment arithmetic would only be done in the outer loop when a segment boundary was crossed. For example, GLOBALENTRY records are a fixed length, each is 36 bytes. My outer loop would run for the number of segments. My inner loop would cycle through a maximum of 1820 cycles, which is how many 36-byte records fit in a segment.

To figure out the number of global heap objects, I call ToolHelp’s GlobalInfo routine, which takes a pointer to a GLOBALINFO buffer:

GLOBALINFO gi;

o

o

o

gi.dwSize = sizeof(GLOBALINFO);

bOk = GlobalInfo(&gi);

The heap walking process starts with a call to GlobalFirst, which fills a GLOBALENTRY structure with data about the first object in the global heap.

globalentry.dwSize = sizeof(GLOBALENTRY);

GlobalFirst(&globalentry, GLOBAL_ALL);

The walk continues with calls to GlobalNext until the end of the heap is encountered:

globalentry.dwSize = sizeof(GLOBALENTRY);

GlobalFirst(&globalentry, GLOBAL_ALL);

My routine, trBuildSegmentList, loops until all of the segment data has been copied to my permanent buffer:

for (;wcItems > 0; wcItems--)

{

if (GlobalNext(&globalentry, GLOBAL_ALL))

{

lpheTemp++;

_fmemcpy(lpheTemp, &globalentry,

sizeof(GLOBALENTRY));

}

else

{

cSegments -= wcItems;

break;

}

}

Even though the loop counter, wcItems, has been initialized with the number of objects in the global heap, I check the return value of GlobalNext to make sure I’m not wandering off the end of the heap. After all, Windows supports the dynamic linking of code and the dynamic loading of resources. The list of segments in the global heap is bound to be the most volatile part of the system.

Building a Task List

TaskMstr also creates a task list. One way to build this list is to walk the segment heap, looking for task databases. After all, a task database is allocated for each task, and the task handle is simply the handle of the memory object that contains the task database. But the relationship between tasks, task handles, and task databases may change in the future. For this reason, the most reliable way to create a task list is to call ToolHelp’s task information functions.

Building a task list is just like building a segment list. The TaskMstr’s trBuildTaskList routine starts by querying the system for the number of tasks. It is faster to call the GetNumTasks routine in the regular Windows libraries than to walk the task list. While working with the ToolHelp library, it’s easy to forget to look for help from existing Windows function calls, but that’s a mistake.

Once I’ve got the correct count of tasks, I allocate a buffer to hold the task data. Actually, I resize the existing buffer by calling a close cousin of the GlobalAllocPtr macro, GlobalReAllocPtr:

lptlTemp = (LPTASKLIST)GlobalReAllocPtr(lptlList,

cTasks * sizeof(TASKLIST), 0);

Allocating a buffer for task data is simpler than allocating a segment buffer: the number of tasks is not likely to change while you’re reading the data. That would be a risk in the preemptive world of Windows NT but not in the nonpreemptive world of Windows 3.x. I’ll need to fine-tune the utility when I move it to the new operating system.

Maintaining the task list is made easier by ToolHelp’s notification service. TaskMstr needs to read the segment table only every 5 seconds, since the notification services report only segment loading and segment freeing. ToolHelp does not report segment discarding activity or segment allocation. But since ToolHelp keeps TaskMstr current on changes to the system’s task list, TaskMstr rebuilds the task list only when ToolHelp says that it’s necessary.

The Notification Procedure

ToolHelp’s notification service will call you for important task-, module-, and segment-related events. The only requirement is that you provide the address of a callback procedure. TaskMstr’s WinMain function attaches the notification procedure by calling NotifyRegister after first calling MakeProcInstance, as shown here:

lpfnNotify = MakeProcInstance(trToolHelpNotify, hInst);

NotifyRegister(NULL, lpfnNotify, NF_NORMAL);

Once installed, the notification procedure will be invoked when events that you specified in your call to the NotifyRegisterRoutine occur. TaskMstr’s notification is interested in only two events: the start and the termination of tasks.

Here’s the entire notification procedure that TaskMstr uses:

BOOL FAR PASCAL _export trToolHelpNotify(WORD wID,

DWORD dwData)

{

if (wID = = NFY_STARTTASK ||

wID = = NFY_EXITTASK)

{

PostMessage(hwndMain, AM_BUILDTASKLIST, 0, 0L);

}

return FALSE;

}

As recommended by Microsoft, TaskMstr's notification procedure calls PostMessage to transmit a message to its main window. Inside the window procedure, TaskMstr rebuilds its task lists when it receives the application specific AM_BUILDTASKLIST message. There isn't a lot to the notification procedure, but it does provide a convenient means for tracking changes in the system.

You must remember to detach the Notification procedure before your program exits. This is how TaskMstr’s WinMain function does this:

NotifyUnRegister(NULL);

FreeProcInstance(lpfnNotify);

Windows 3.1 Message Crackers

TaskMstrWndProc demonstrates how to use message crackers, a group of macros shipping with the Windows SDK 3.1. These simplify the task of writing code for both the 16-bit Windows 3.x API ("Win16") and Win32Ô API and minimize the impact of location changes in message parameter values in both APIs. (The functionality that ToolHelp will provide under Win323 has not been finalized.)

Consider, for example, the WM_COMMAND message that dialog box controls send their parents. As shown in Figure 12, the placement of parameters in Win16 and Win32 is different. The message cracker macros let you hide these differences in your source code. When a program is compiled for the Win16 API, one set of macros is used. Another set allows the same program to compile properly for the Win32 API.

Figure 12 Reorganizing Parameters in 32-Bit-Wide Messages

Here, for example, is how TaskMstr’s main window procedure responds to the WM_COMMAND message sent by the program’s two list boxes:

case WM_COMMAND:

HANDLE_WM_COMMAND(hwnd,wParam,lParam,trMain_OnCommand);

break;

The macro in this line of code, HANDLE_WM_COMMAND, is defined in WINDOWSX.H as follows:

#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \

((fn)((hwnd), (int)(wParam), \

(HWND)LOWORD(lParam), \

(UINT)HIWORD(lParam)), 0L)

Notice that this macro expects the first parameter, fn, to be a function name. When the macro is expanded, here is the code produced:

trMain_OnCommand (hwnd,

(int)wParam,

(HWND)LOWORD(lParam),

(UINT)HIWORD(lParam));

This function is prototyped in TASKMSTR.H as

VOID NEAR PASCAL

trMain_OnCommand(HWND hwnd, int id, HWND hwndCtl,

UINT codeNotify);

From this function declaration you can see how the message cracker parses the window procedure’s wParam and lParam parameter into their constituent parts. This same function declaration should compile smoothly under the Win32 API. To bridge the gap between Win16 and Win32, a 32-bit message cracker macro like the following would be required:

#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \

((fn)((hwnd),(int)LOWORD(wParam), \

(HWND)lParam,(UINT)HIWORD(wParam)), 0L)

How important are message crackers to achieving Win16 and Win32 portability? According to the latest Win32 specification (based on the second prerelease version of the Microsoft Win32Ô Software Development Kit--Ed.), only 20 messages of 200 Windows messages have parameter differences. But message crackers are worth the effort for new application developments. Even without Win16-to-Win32 portability, message crackers perform a useful function. For one thing, parsing wParam and lParam into message-specific types lets your compiler perform additional type checking. Also, you don’t have to re-create the same lines of parsing code in every window and dialog box procedure you write. After all, any time you re-create existing code, you’re wasting your time and increasing the chance for errors. Many dialog box procedures contain the following lines of code:

{

WORD wID;

WORD wNotify;

HWND hwndControl;

wID = wParam;

wNotify = HIWORD (lParam);

hwndControl = LOWORD (lParam);

o

o

o

The message crackers handle this parsing, and provide the prototype for a function that can properly receive the parameters.

If you like what you’ve seen of ToolHelp, it’s time to begin coding your own set of tools. I certainly have more ideas for useful programs I’d like to write using ToolHelp. In addition, there are a number of enhancements that could be made to TaskMstr. First, I’d like to speed up the task-walking function. Next, I’d like TaskMstr to list all the DLLs in the system and summarize the memory used by each. I’d also allow TaskMstr to show a detailed list of segments and display the contents of each segment.

If you’ve found yourself yearning for the good old days of programming in MS-DOS, when you could shanghai the system clock, wrestle an interrupt table, and peer into the system’s data areas, ToolHelp will transport you to a world where Windows secrets are wide open.

1For ease of reading, "Windows" refers to the Microsoft Windows operating system. "Windows" is a trademark that refers only to this Microsoft product.

2For ease of reading, "Windows NT" and "NT" refer to the Microsoft Windows NT operating system. "NT" is a trademark that refers only to this Microsoft product.

3For ease of reading, "Win32" refers to the Win32 API. "Win32" is a trademark that refers only to this Microsoft product.