Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Windows Toolhelp


SEP92: WINDOWS TOOLHELP

This article contains the following executables: UNLOAD.ARC

Mike is a Windows programming and development consultant. He has been programming in Windows since the advent of version 1.02 and can be reached via CompuServe at 75740, 1403.


ToolHelp is a dynamic link library (DLL) that provides functions to peek and poke into the internals of Windows 3.1. You can avoid reengineering your application every time a new version of Windows is released by using the documented ToolHelp API, instead of relying on undocumented Windows features. Microsoft intends to include a new version of TOOLHELP.DLL for every new release of Windows.

This article provides an overview of ToolHelp functions and how they can be used in your applications. I then present a programming utility that lets you remove DLLs and programs stuck in memory without having to restart Windows to recompile.

ToolHelp Functions

The first category of ToolHelp functions lets you obtain information about the local or global heap, and about current tasks, modules, and window classes in the system. These ToolHelp functions use a calling protocol similar to the DOS FindFirst and FindNext functions for getting a directory of files.

For example, to get a list of all currently loaded modules, you first call the ModuleFirst function with a long pointer to a ModuleEntry structure, which is declared in TOOLHELP.H. In addition to various fields that contain information about the module you retrieved, the ModuleEntry structure also contains a dwSize field.

It is important to initialize this field to sizeof(MODULENTRY) before calling GlobalFirst. ToolHelp checks the dwSize field to determine how much information about the module your GlobalEntry structure can contain. Future versions of ToolHelp will be able to support applications written for different versions of ToolHelp by examining the dwSize field.

If the ModuleFirst function returns True, you can call ModuleNext. You can keep calling ModuleNext with a long pointer to the same data structure as long as the return value is True; all ToolHelp enumeration functions work this way.

Although these enumeration functions don't let you modify the system, some interesting uses are nevertheless possible. For example, you can use the ToolHelp enumeration functions to verify certain parameters passed to a function. If a function in your program is passed an hDC parameter (a handle to a Windows device context), you can walk the local heap of the GDI to determine whether the hDC is valid.

The InterruptRegister and InterruptUnregister functions let you install a function to intercept special events: processor-generated events like division by 0; parity error; invalid opcode and protection faults; debug interrupts; and even a Ctrl+Alt+SysRq key press. Utilities like Microsoft's Dr. Watson and Borland's WinSpector use these ToolHelp functions to produce postmortem information when an application encounters a serious error. These utilities dump a stack trace and a disassembled listing of the code that produced the error.

Say you have an extensive beta program; you can incorporate your own interrupt handler to dump global (or even local) variables and other application-specific status information that general-purpose utilities like Dr. Watson or WinSpector don't provide. Installing such handlers on beta testers' PCs gives you invaluable information and saves hours of development time.

To intercept less critical events you can use the NotifyRegister and NotifyUnregister functions. Your notification handler can be called when a DLL or program is loaded or freed, when a segment is loaded or discarded, or when a task switch occurs. Windows can also call your notification handler function when it wants to show a debug message or get debug input from the programmer.

ToolHelp contains a number of functions that don't really fit into any category. These include MemoryRead() and MemoryWrite(), which let you write to any memory location, even if the memory doesn't belong to your program. The functions also work if you want to write to a code segment. The TimerCount function lets you retrieve information about timer resolution. Other, more specialized functions let you trace the stack of a sleeping task, instantly terminate an application, or give control directly to a certain task.

Unload

Unload is a programmer's utility that lets you remove any Windows program or DLL from memory. It is useful when developing a Windows application or DLL that has a problem getting unloaded. Instead of exiting Windows to remove the module from memory, you can just unload and recompile. Listing One (page 118) shows UNLOAD.C. The files UNLOAD.H, UNLOAD.ICO, UNLOAD.RC, and UNLOAD.EXE are available electronically.

You only need Unload when you can't remove a library or program using "standard" methods like selecting the End Task option in the task list or closing the application's window. This can occur if a library's usage count is higher than 0, but the application using it was terminated because of an unrecoverable application error (UAE), or if an application does not have any visible windows.

Unload displays a combo box of all modules (programs and DLLs) loaded in your system. Since fonts and drivers are also DLLs, they are in the list. Programs are displayed in upper case, DLLs in lower case. To fill up this combo box, Unload calls the ToolHelp ModuleFirst and ModuleNext functions (see FillUpComboBox() in Listing One) and sends a CB_ADDSTRING message to add the module name of every module to the combo box. Immediately after each module name, CB_SETITEMDATA attaches the module handle and usage count.

When you select a module from that combo box, Unload displays information about it: the usage count, whether it's a library or a program, the module's filename, and the module handle. As soon as the combo box drops down or selects an item, it sends a WM_COMMAND notification message to its parent, the dialog box. When the dialog function has been notified that the combo box's list box is dropped down, we refresh the combo box's contents just before it's displayed. When a new item is selected, we display the corresponding information.

Almost all the information displayed about the module--usage count, module filename, and module name--is available in the ToolHelp MODULEENTRY data structure, which is filled up with a call to ModuleFindName. To get the module handle of the currently selected item, we send a CB_GETITEM_DATA message to the combo box. When filling up the combo box, we use CB_SETITEMDATA to attach this value to every item.

The only information we need that is not in the MODULEENTRY structure is the type of the module: library or program. How can we check this? A module handle is really the selector of a block of memory that contains the module file header; the header is in the "new EXE" format and has been documented by Microsoft. If you take the word at offset 0xC and you AND it with 0x8000, you have the bit which indicates the type of module. If this bit is on, the module is a DLL; otherwise, it's a progam.

Although Windows 3 lets you get away with creating a long pointer from the selector and the offset and reading the bit directly, future versions of Windows might impose a stricter separation between different applications. Therefore, always use ToolHelp's MemoryRead function to read any part in memory.

After selecting a module, you can press the Unload button to remove it from memory. If you've selected a DLL, there is only one way to unload it: Decrease the reference count using the Windows FreeLibrary function.

In the case of a program, the situation is a little more complicated. There can be several reasons why your application won't terminate: The application's main window might be hidden so you can't close it; your message loop might continue forever because your program didn't process a WM_QUIT message; or you forgot to call PostQuitMessage() when your main window was closed.

To accommodate these cases, Unload offers three methods to remove an application: You can post a WM_CLOSE message to the application's main windows, even if they are hidden; post a WM_QUIT message to the application (this is the equivalent of calling PostQuitMessage() inside your application); or instantly terminate the application, as if a UAE had occurred.

Conclusion

Unload is one of those utilities that you won't need very often, but when you need it, you need it badly. Writing a utility like Unload without ToolHelp would require a lot of inside information and a very clear understanding of Windows internals. Thanks to ToolHelp, creating your own tools has become easier and safer.

[LISTING ONE]

<a name="0200_0008">

///////////////////////////////////////////////////////////////////////////
// UNLOAD.C     Copyright (c) 1992 by Mike Sax
// Unload is a small programmer's utility that lets you remove any program
// or DLL that is stuck in memory.
///////////////////////////////////////////////////////////////////////////
#define STRICT 1
#include <windows.h>
#include <string.h>
#include <toolhelp.h>
#include "unload.h"

// Global variables:
static HINSTANCE ghInstance;

// Exported functions:
BOOL FAR PASCAL _export MainDlgProc(HWND hDlg, unsigned message, WORD wParam,
                                    LONG lParam);
BOOL FAR PASCAL _export WarningDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
                                    LONG lParam);
BOOL FAR PASCAL _export EnumTaskWindowsFunc(HWND hWnd, DWORD lParam);

// Internal functions:
void static FillupComboBox(HANDLE hComboBox);
void static ShowItemInfo(HWND hDlg, HWND hComboBox);
void static KillTask(HTASK hTask, int nMethod);
BOOL static IsDLL(HMODULE hModule);

// The WinMain function is called at the beginning of our program
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
    {
    ghInstance = hInstance;
    (void)lpCmdLine;
    if (!hPrevInstance)
        {
        WNDCLASS  wc;
        // Register private dialog class
        wc.style = 0l;
        wc.lpfnWndProc = DefDlgProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = DLGWINDOWEXTRA;
        wc.hInstance = ghInstance;
        wc.hIcon = LoadIcon(hInstance, "Unload");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  NULL;
        wc.lpszClassName = "Unload";
        if (!RegisterClass(&wc))
            return -1;
        }
    // Use a dialog box as our main window and pass the nCmdShow parameter
    // in lParam of MainDlgProc's WM_INITDIALOG.
    return DialogBoxParam(ghInstance, "Unload", NULL, (DLGPROC)
                MakeProcInstance((FARPROC)MainDlgProc, ghInstance),
                (LONG)nCmdShow);
    // Proc Instance will be automatically cleaned up by Windows.
    }
// MainDlgProc handles all messages for our main window.
BOOL FAR PASCAL _export MainDlgProc(HWND hDlg, unsigned message, WORD wParam,
                                    LONG lParam)
    {
    switch (message)
        {
        case WM_INITDIALOG:
            // We passed the nCmdShow parameter of WinMain in lParam
            ShowWindow(hDlg, LOWORD(lParam));
            break;
        case WM_CLOSE:
            EndDialog(hDlg, FALSE);
            break;
        case WM_COMMAND:
            switch(wParam)
                {
                // Combobox notification message
                case IDD_COMBOBOX:
                    if (HIWORD(lParam) == CBN_DROPDOWN)
                        FillupComboBox((HANDLE)LOWORD(lParam));
                    else if (HIWORD(lParam) == CBN_SELCHANGE)
                        {
                        static BOOL bFirst = TRUE;
                        if (bFirst)
                            {
                   SetDlgItemText(hDlg, IDD_MESSAGE, "Compuserve: 75470,1403");
                            bFirst = FALSE;
                            }
                        ShowItemInfo(hDlg, (HANDLE)LOWORD(lParam));
                        }
                    break;
                // User pressed the "Unload" button
                case IDD_UNLOAD:
                    {
                    FARPROC lpProc;
                    int nCurSel = (int)SendDlgItemMessage(hDlg, IDD_COMBOBOX,
                                    CB_GETCURSEL, 0, 0l);
                    // If no item selected, do nothing
                    if (nCurSel == CB_ERR)
                        {
                        MessageBeep(0);
                        break;
                        }
                    lpProc = MakeProcInstance((FARPROC)WarningDlgProc,
                             ghInstance);
                    // Call "Unload" dialog box and pass the module handle
                    // in lParam of WarningDlgProc's WM_INITDIALOG
                    if (DialogBoxParam(ghInstance, "WARNING", hDlg,
                        (DLGPROC)lpProc, SendDlgItemMessage(hDlg,
                            IDD_COMBOBOX, CB_GETITEMDATA, nCurSel, 0l)))
                        {
                        // Give Windows a chance to process the WM_QUIT
                        // or WM_CLOSE messages we might have posted
                        Yield();
                        FillupComboBox(GetDlgItem(hDlg, IDD_COMBOBOX));
                        }
                    FreeProcInstance(lpProc);
                    }
                }
            break;
        default:
            return FALSE;       // We did not process the message
        }
    return TRUE;                // We processed the message
    }
// FillupComboBox fills up the combo box with a list of all modules
// that are currently loaded.  Every item in the list box also contains
// a "long" data item (attached using CB_SETITEMDATA) that is a combination
// of the module handle and the usage count.
void static FillupComboBox(HANDLE hComboBox)
    {
    int nIndex;
    BOOL bSucces;
    HMODULE hSelectedModule;
    MODULEENTRY ModuleEntry;
    // Keep the module handle of the item that is currently selected
    nIndex = SendMessage(hComboBox, CB_GETCURSEL, 0, 0l);
    hSelectedModule = (HMODULE) ((CB_ERR == nIndex) ? -1 :
            HIWORD(SendMessage(hComboBox, CB_GETITEMDATA, nIndex, 0l)));
    SendMessage(hComboBox, CB_RESETCONTENT, 0, 0l);
    ModuleEntry.dwSize = sizeof (MODULEENTRY);
    bSucces = ModuleFirst(&ModuleEntry);
    while(bSucces)
        {
        if (IsDLL(ModuleEntry.hModule))
            AnsiLower(ModuleEntry.szModule);
        nIndex = (int)SendMessage(hComboBox, CB_ADDSTRING, 0,
                                  (LONG) (LPSTR) ModuleEntry.szModule);
        if ((nIndex != CB_ERR) && (nIndex != CB_ERRSPACE))
            {
            SendMessage(hComboBox, CB_SETITEMDATA, nIndex,
                        MAKELONG(ModuleEntry.wcUsage, ModuleEntry.hModule));
            bSucces = ModuleNext(&ModuleEntry);
            }
        else
            bSucces = FALSE;
        }
    // Check if the previously selected module is still in the list and
    // if so, reselect it.
    for (nIndex = SendMessage(hComboBox, CB_GETCOUNT, 0, 0l) - 1;
         nIndex >= 0 ; --nIndex)
         if ((HMODULE) HIWORD(SendMessage(hComboBox, CB_GETITEMDATA,
             nIndex, 0)) == hSelectedModule)
             {
             SendMessage(hComboBox, CB_SETCURSEL, nIndex, 0l);
             break;
             }
    ShowItemInfo(GetParent(hComboBox), hComboBox);
    }
// Show information about the currently selected item in the combobox.
void static ShowItemInfo(HWND hDlg, HWND hComboBox)
    {
    int nCurSel;
    nCurSel = (int)SendMessage(hComboBox, CB_GETCURSEL, 0, 0l);
    if (CB_ERR == nCurSel)
        {
        SetDlgItemText(hDlg, IDD_FILENAME, "");
        SetDlgItemText(hDlg, IDD_MODULE, "");
        SetDlgItemText(hDlg, IDD_KIND, "");
        SetDlgItemText(hDlg, IDD_USAGE, "");
        EnableWindow(GetDlgItem(hDlg, IDD_UNLOAD), FALSE);
        }
    else
        {
        char szScrap[MAX_PATH + 1];
        char *pcFilename;
        DWORD dwData;
        dwData = SendMessage(hComboBox, CB_GETITEMDATA, nCurSel, 0l);
        GetModuleFileName((HMODULE)HIWORD(dwData), szScrap, MAX_PATH);
        // Remove the path from the filename
        pcFilename = strrchr(szScrap, '\\');
        pcFilename = (pcFilename == NULL) ? szScrap : pcFilename + 1;
        SetDlgItemText(hDlg, IDD_KIND, (IsDLL((HMODULE)HIWORD(dwData)) ?
                                        "Library" : "Program"));
        SetDlgItemText(hDlg, IDD_FILENAME, pcFilename);
        SetDlgItemInt(hDlg, IDD_USAGE, LOWORD(dwData), FALSE);
        wsprintf(szScrap, "%04x", HIWORD(dwData));
        SetDlgItemText(hDlg, IDD_MODULE, (LPSTR) szScrap);
        EnableWindow(GetDlgItem(hDlg, IDD_UNLOAD), LOWORD(dwData));
        }
    }
// When the user pressed the Unload button, the "warning dialog" appears
BOOL FAR PASCAL _export WarningDlgProc(HWND hDlg, WORD wMessage, WORD wParam,
                                       LONG lParam)
    {
    static HMODULE hModule; // Only one dialog can be active!
    switch(wMessage)
        {
        case WM_INITDIALOG:
            // The handle of the module to be freed is in the hiword of iParam,
            // passed on using DialogBoxParam. Since we use same dialog box for
            // both programs and libraries, we have to adjust our dialog a
            // a little, depending on the type of dialog.
            if (IsDLL((HMODULE)HIWORD(lParam)))
                {
                EnableWindow(GetDlgItem(hDlg, IDD_TERMINATE), FALSE);
                EnableWindow(GetDlgItem(hDlg, IDD_DESTROY), FALSE);
                }
            else
               SetDlgItemText(hDlg, IDD_REFERENCEZERO, "Post WM_QUIT message");
            CheckDlgButton(hDlg, IDD_REFERENCEZERO, 1);
            hModule = (HMODULE)HIWORD(lParam);
            break;
        case WM_COMMAND:
            switch(wParam)
                {
                case IDOK:
                    {
                    if (IsDLL(hModule))
                        {
                        int nUsage = GetModuleUsage(hModule);
                        while (nUsage--)
                            FreeLibrary(hModule);
                        }
                    else
                        {
                        BOOL bSucces;
                        TASKENTRY TaskEntry;
                        int nMethod =
                        (IsDlgButtonChecked(hDlg, IDD_REFERENCEZERO)) ? 0 :
                        (IsDlgButtonChecked(hDlg, IDD_TERMINATE)) ? 1 : 2;
                        TaskEntry.dwSize = sizeof(TASKENTRY);
                        bSucces = TaskFirst(&TaskEntry);
                        while(bSucces)
                            {
                            if (TaskEntry.hModule == hModule)
                                KillTask(TaskEntry.hTask, nMethod);
                            bSucces = TaskNext(&TaskEntry);
                            }
                        }
                    EndDialog(hDlg, TRUE);
                    }
                    break;
                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;
                }
            break;
        case WM_CLOSE:
            EndDialog(hDlg, FALSE);
            break;
        default:
            return FALSE;
        }
    return TRUE;
    }
// KillTask kills a task using a method of your choice.  It is called
// from the "Warning" dialog box when the user presses Ok.
void static KillTask(HTASK hTask, int nMethod)
    {
    switch(nMethod)
        {
        case 0:     // Post WM_QUIT message
            PostAppMessage(hTask, WM_QUIT, 0, 0l);
            break;
        case 1:     // Terminate application
            TerminateApp(hTask, NO_UAE_BOX);
            break;
        case 2:     // Close all the task's windows
            {
            FARPROC lpProc = MakeProcInstance((FARPROC)EnumTaskWindowsFunc,
                                              ghInstance);
            EnumTaskWindows(hTask,(WNDENUMPROC)lpProc, 0l);
            FreeProcInstance(lpProc);
            }
            break;
        }
    }
// EnumTaskWindowsFunc is called for every toplevel window that belongs to
// a task. It simply posts a WM_CLOSE message to this window.
BOOL FAR PASCAL _export EnumTaskWindowsFunc(HWND hWnd, DWORD lParam)
    {
    (void)lParam;                       // Avoid compiler warnings
    if (GetParent(hWnd) == NULL)
        PostMessage(hWnd, WM_CLOSE, 0, 0l);
    return TRUE;
    }
// IsDLL returns TRUE if the specified module is a Dynamic Link Library, or
// FALSE if it is a program.
BOOL static IsDLL(HMODULE hModule)
    {
    int i;
    // The module handle is really the selector of a far pointer to
    // the new-style .EXE header of the module.  The bit at 0x8000 of
    // the word at offset 0xC in this structure is set if it's a DLL.
    MemoryRead((WORD)hModule, 0xCl, &i, sizeof(i));
    return (i & 0x8000) ? TRUE : FALSE;
    }











Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.