/***********************************************

Name: gdistat.c

Description:

This tool is designed to retrieve information about
GDI and USER objects from their handle managers. It runs as
an user application.

Won't work with 3.51 and earlier releases

It can retrieve the number of objects associated with each
process, as well as the total number of objects in the system
and public objects. On checked builds it can also retrieve
the same data as displayed with the !gdiexts.dumphmgr function.

Future Changes:

The following information describes the changes necessary to
add new queries in future (for example memory information):

  -Add a new entry in the indexlist array in gdistats.c
  -increment the value of NUM_INDEX_VALUES in gdistats.h
  -add a new #define GS_xxxxx to ntgdi.h
  -add code to NtGdiGetStats in hmgrapi.cxx to deal with
       the new item. Ensure that the buffer size specified in the
       indexlist array is large enough to contain this information.
   OR add code to HMGetStats in handtabl.c to add user queries
  -add code to DisplayResults() in gdistats.c to present the
       results correctly. The default is a raw hex dump.

Changes to the organization of the handle tables, or the
addition of new handle types would require changes to the
aszUserObjType or aszGdiObjType structures.

Uses Unicode. Access must be enabled in the registry
through the Session Manager/global flag/FLG_POOL_ENABLE_TAGGING bit

Other files:
    gsysqry.c

History:
  01-August-1995 -by- Andrew Skowronski [t-andsko]
Added user query, new interface, sum calculated
  14-June-1995 -by- Andrew Skowronski [t-andsko]
Wrote it

************************************************/


#include <windows.h>
#include <tchar.h>
#include "gs_dlg.h"              //dialog box child window values
#include "gdistats.h"

//These values must match with those used by NtGdiGetStats
#define GS_NUM_OBJS_ALL    0  //Gdi queries
#define GS_HANDOBJ_CURRENT 1
#define GS_HANDOBJ_MAX     2
#define GS_HANDOBJ_ALLOC   3
#define GS_LOOKASIDE_INFO  4
#define GU_NUM_OBJS_ALL    5  //User information

/***************************** GLOBALS ****************************/

//This structure gives information about all the queries that are allowed
struct
{
    UINT nIndex;       //Index value
    LPTSTR szLabel;    //String to describe function
    UINT nSize;        //Size of the buffer to store the results
    BOOL bPublic;      //These mark determine whether the radio buttons should
                       //be enabled to select Pubic Objects or a specific process
    BOOL bOneProcess;
}
indexlist [] =
{
    //Gdi object information

    GS_NUM_OBJS_ALL,   _TEXT("Gdi objects per type"),
                       sizeof(DWORD) * NUM_GDI_OBJS,TRUE,TRUE,

    //These queries get information from structures that
    //are only kept in checked builds. It fails elegantly if
    //run on the free build

    GS_HANDOBJ_CURRENT,_TEXT("Gdi Handles/Objects current"),
                       sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE,
    GS_HANDOBJ_MAX,    _TEXT("Gdi Handles/Objects maximum"),
                       sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE,
    GS_HANDOBJ_ALLOC,  _TEXT("Gdi Handles/Objects allocated"),
                       sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE,

    //This information is also only for checked builds.
    //It compares the number of lookaside hits with the total
    //number of objects allocated

    GS_LOOKASIDE_INFO, _TEXT("Gdi Lookaside hit rate"),
                       sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE,

    //The user handle table information

    GU_NUM_OBJS_ALL,   _TEXT("User objects per type"),
                       sizeof(DWORD) * NUM_USER_OBJS,FALSE,TRUE
};

//Each object type for Gdi Objects
LPTSTR aszGdiObjType[] =
{
    _TEXT("DEF             "),   // 0
    _TEXT("DC              "),   // 1
    _TEXT("LDB             "),   // 2
    _TEXT("PDB             "),   // 3
    _TEXT("RGN             "),   // 4
    _TEXT("SURF            "),   // 5
    _TEXT("XFORM           "),   // 6
    _TEXT("PATH            "),   // 7
    _TEXT("PAL             "),   // 8
    _TEXT("FD              "),   // 9
    _TEXT("LFONT           "),   // 10
    _TEXT("RFONT           "),   // 11
    _TEXT("PFE             "),   // 12
    _TEXT("PFT             "),   // 13
    _TEXT("IDB             "),   // 14
    _TEXT("XLATE           "),   // 15
    _TEXT("BRUSH           "),   // 16
    _TEXT("PFF             "),   // 17
    _TEXT("CACHE           "),   // 18
    _TEXT("SPACE           "),   // 19
    _TEXT("DBRUSH          "),   // 20
    _TEXT("META            "),   // 21
    _TEXT("EFSTA           "),   // 22
    _TEXT("BMFD            "),   // 23
    _TEXT("VTFD            "),   // 24
    _TEXT("TTFD            "),   // 25
    _TEXT("RC              "),   // 26
    _TEXT("TEMP            "),   // 27
    _TEXT("DRVOBJ          "),   // 28
    _TEXT("DCIOBJ          "),   // 29
    _TEXT("SPOOL           ")    // 30
};

//Each object type for User objects
LPTSTR aszUserObjType[] =
{
    _TEXT("Free            "),   // 0
    _TEXT("Window          "),   // 1
    _TEXT("Menu            "),   // 2
    _TEXT("Icon/Cursor     "),   // 3
    _TEXT("SWP Structure   "),   // 4
    _TEXT("Hook            "),   // 5
    _TEXT("ClipData        "),   // 6
    _TEXT("CallProcData    "),   // 7
    _TEXT("Accelerator     "),   // 8
    _TEXT("DDE Access      "),   // 9
    _TEXT("DDE Conv        "),   // 10
    _TEXT("DDE Transaction "),   // 11
    _TEXT("Monitor         "),   // 12
    _TEXT("Keyboard layout "),   // 13
    _TEXT("Keyboard File   "),   // 14
    _TEXT("Winevent        "),   // 15
    _TEXT("Timer           "),   // 16
    _TEXT("InputContext    "),   // 17
    _TEXT("Undefined       ")    // Should never occur
};

// This ones replace HMGetStats and are private in user32.dll
#define QUC_PID_TOTAL           0xffffffff
#define QUERYUSER_HANDLES       0x1

BOOL (WINAPI *QueryUserCounters)(DWORD, LPVOID, DWORD, LPVOID, DWORD );
int NtGdiGetStats(HANDLE,int,int,PVOID,UINT);

/***********************************************

Main Procedure

***********************************************/

int __cdecl main ()

{
    HANDLE hWndDialog;
    MSG    msg;

    TCHAR   szUser32DllPath[MAX_PATH+15];
    HMODULE hUser32Module;

    HANDLE hInstance;
    hInstance = GetModuleHandle(NULL);

    if (!GetSystemDirectory(szUser32DllPath, MAX_PATH+1)) {
        return 0;
    }
    wcscat( szUser32DllPath, L"\\user32.dll");

    hUser32Module = GetModuleHandle(szUser32DllPath);
    if (!hUser32Module) {
        return 0;
    }
    QueryUserCounters = (BOOL (WINAPI  *)(DWORD, LPVOID, DWORD, LPVOID, DWORD ))
                    GetProcAddress(hUser32Module, "QueryUserCounters");
    if (!QueryUserCounters) {
        return 0;
    }

    hWndDialog = CreateDialogParam (hInstance,
                                    MAKEINTRESOURCE (GD_DIALOG),
                                    NULL,
                                    (DLGPROC) GSDlgProc,
                                    (LONG) 0);

    while(GetMessage (&msg, NULL, 0, 0))
        if (!IsDialogMessage (hWndDialog, &msg))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }

    DestroyWindow (hWndDialog);
    return 0;

}


/*************************************************

Dialog Procedure

*************************************************/

BOOL CALLBACK GSDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{

    switch (wMsg)
    {
        //Initialize everything
        case WM_INITDIALOG :
            SetClassLongPtr(hWnd,
                         GCLP_HICON,
                         (INT_PTR)LoadIcon((HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
                                         _TEXT("GSICON")));

            if (!UpdateProcessList(hWnd))
                PostQuitMessage(0);
                UpdateIndexList(hWnd);
                UpdateRadioButtons(hWnd);
                break;


        case WM_CLOSE :
            PostQuitMessage (0);
            break;

        case WM_COMMAND:
            switch (LOWORD (wParam))
            {

                //The list box selection has been changed
                case GD_INDEX :
                    if (HIWORD(wParam) == CBN_SELCHANGE)
                    {
                        UpdateRadioButtons(hWnd);
                        DoQuery(hWnd);
                    }
                    else
                        return FALSE;
                    break;

                //Re-run the query when we double click
                //on the process
                case GD_PROCESS :
                    if (HIWORD(wParam) == CBN_DBLCLK)
                        DoQuery(hWnd);

                    //Refresh the process list every time
                    //the list is displayed to avoid
                    //the need for a refresh button
                    else if (HIWORD(wParam) == CBN_DROPDOWN)
                        UpdateProcessList(hWnd);
                    else
                        return FALSE;
                    break;

                //Exit button
                case GD_EXIT :
                    PostQuitMessage (0);
                    break;

                //Run button
                case GD_GO :
                    DoQuery(hWnd);
                    break;


                default :
                    return FALSE;
                }


        default :
            return FALSE;
    }

    return TRUE;

}



/********************************************************

UpdateProcessList

This function fills in the process combo box with the list of processes
running at the time of this call

It returns FALSE if not successful.

*********************************************************/


BOOL UpdateProcessList(HWND hWnd)
{
    HWND hProcessList;       //Handle to process list box
    PVOID pProcessInfo;      //pointer to process information
    TCHAR  szProcStr[40];    //Buffer for procedure name strings
    DWORD dwStatus;
    LONG lNotLast;
    LRESULT nIndex;          //Position of new string in list
    LRESULT lSavedPos;       //Saved position in list

    HANDLE hProc;            //Handle to the process

    hProcessList = GetDlgItem (hWnd, GD_PROCESS);

    //Remember currently selected item
    lSavedPos = SendMessage(hProcessList, CB_GETCURSEL, 0 ,0);

    SendMessage(hProcessList, WM_SETREDRAW, FALSE, 0);
    SendMessage(hProcessList, CB_RESETCONTENT, 0, 0);


    //Fill in the process information
    dwStatus = FindProcessList(&pProcessInfo);

    if (!dwStatus)
    {
        //Print the process information to screen
        do
        {
            lNotLast = GetNextProcString(&pProcessInfo,szProcStr,&hProc);
            nIndex = SendMessage(hProcessList,CB_ADDSTRING,0,(LPARAM)szProcStr);

            //Associate the handle value with the list box item
            SendMessage(hProcessList,CB_SETITEMDATA,(WPARAM) nIndex,(LPARAM) hProc);
        } while (lNotLast);
    }
    else
    {
        //Unrecoverable error - could not get process information
        MessageBox( hWnd,_TEXT("Unable to create process listing"),_TEXT("ERROR"),MB_OK );
        return FALSE;
    }

    LocalFree (pProcessInfo);

    //Attempt to restore the cursor position
    if (lSavedPos != CB_ERR)
       SendMessage(hProcessList, CB_SETCURSEL, (WPARAM) lSavedPos, 0);
    else
       SendMessage(hProcessList, CB_SETCURSEL, 0, 0);

    SendMessage(hProcessList, WM_SETREDRAW, TRUE, 0);

    return TRUE;

}

/********************************************************
UpdateIndexList

This function fills in the index list with the list of
possible strings associated with each index.

The information comes from the index list array

*********************************************************/

VOID UpdateIndexList(HWND hWnd)
{
    HWND hIndexCombo;
    LONG iCnt;


    hIndexCombo = GetDlgItem (hWnd, GD_INDEX);


    SendMessage(hIndexCombo, WM_SETREDRAW, FALSE, 0);
    SendMessage(hIndexCombo, CB_RESETCONTENT, 0, 0);


    //Fill in the names of the various predefined index values from the indexlist structure
    for (iCnt = 0; iCnt < NUM_INDEX_VALUES; iCnt++)
    {
        SendMessage(hIndexCombo, CB_ADDSTRING, 0, (LPARAM)indexlist[iCnt].szLabel);
    }

    SendMessage(hIndexCombo, CB_SETCURSEL, 0, 0); //Set the first item to be default

    SendMessage(hIndexCombo, WM_SETREDRAW, TRUE, 0);
}



/*******************************************************\
ValidateProcessSelection


Returns the handle to the process selected,
SEL_PUBLIC, SEL_ALL if PUBLIC or ALL are selected,
and 0 otherwise
\*******************************************************/

HANDLE ValidateProcessSelection (HWND hMainWindow, HWND hErrorWindow)
{

    HWND hProcessList;
    HANDLE hSelProcHandle = 0;
    LRESULT nProcIndex;

    hProcessList = GetDlgItem (hMainWindow, GD_PROCESS);

    //Check currently seleted process value
    nProcIndex = SendMessage (hProcessList, CB_GETCURSEL, 0, 0);

    if (nProcIndex != CB_ERR)
    {
        //Get the client handle
        hSelProcHandle = (PVOID) SendMessage(hProcessList,CB_GETITEMDATA,(WPARAM) nProcIndex,0L);
    }
    else
    {
        ErrorString(hErrorWindow, _TEXT("ERROR: Must select a process"));
    }

    return hSelProcHandle;

}

/*******************************************************\
ValidateIndexSelection

Takes the current selection of the index combo box, and
verifies that it is a valid item

Returns the value of the index or -1 if error
\*******************************************************/

LONG ValidateIndexSelection (HWND hMainWindow, HWND hErrorWindow)
{

    HWND hIndexCombo;
    LRESULT nListIndex;
    LONG nQueryIndex = -1;
    TCHAR IndexStr[40];  //the actual data entered in the edit box

    hIndexCombo  = GetDlgItem (hMainWindow, GD_INDEX);

    //Check currently selected Index value
    nListIndex = SendMessage (hIndexCombo, CB_GETCURSEL, 0, 0);
    if (nListIndex != CB_ERR)
    {
        //We don't use the index as the value but the actual constant
        //value from the structure
        nQueryIndex = indexlist[nListIndex].nIndex;
    }
    else  //value was not selected out of the list
    {
        //See if any string has been entered
       if (!SendMessage (hIndexCombo, WM_GETTEXT, 40, (LPARAM)IndexStr))
       {
           ErrorString(hErrorWindow, _TEXT("ERROR: Need to Select Index"));
       }
       else
       {
           //The string should be a valid number (but not zero)
           nQueryIndex = _ttoi(IndexStr);

           if (nQueryIndex == 0)
           {
               nQueryIndex = -1;
               ErrorString(hErrorWindow,_TEXT("ERROR: Index must be from list or a number"));
           }
       }
    }

    return nQueryIndex;
}


/*******************************************************
UpdateRadioButtons

Depending on the currently selected index value,
set which radio buttons are enabled. Handles
the case where nothing is set, and the case
where a selected button becomes disabled.

Assumes the "All Processes" button will never be
disabled
\*******************************************************/

VOID UpdateRadioButtons(HWND hWnd)
{
    HWND hResultList;
    HWND hRadio_All;
    HWND hRadio_One;
    HWND hRadio_Public;
    HWND hProcessList;
    LONG nQueryIndex;
    int  iCurrentChecked;

    hResultList   = GetDlgItem(hWnd, GD_RESULT);
    hRadio_All    = GetDlgItem(hWnd, GD_RADIO_ALL);
    hRadio_Public = GetDlgItem(hWnd, GD_RADIO_PUBLIC);
    hRadio_One    = GetDlgItem(hWnd, GD_RADIO_ONE);
    hProcessList  = GetDlgItem(hWnd, GD_PROCESS);

    nQueryIndex = ValidateIndexSelection(hWnd, hResultList);

    //Save current setting of radio buttons, if any
    iCurrentChecked = WhichRadioButton(hWnd);

    if (nQueryIndex != -1)
    {
        EnableWindow(hRadio_Public,indexlist[nQueryIndex].bPublic);
        EnableWindow(hRadio_One,indexlist[nQueryIndex].bOneProcess);

        //Also disable/enable process list
        EnableWindow(hProcessList,indexlist[nQueryIndex].bOneProcess);
    }

    //Return the setting if the item is not now disabled
    if ((iCurrentChecked == SEL_PUBLIC) && indexlist[nQueryIndex].bPublic)
        CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_PUBLIC);
    else if ((iCurrentChecked == SEL_ONE) && indexlist[nQueryIndex].bOneProcess)
        CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_ONE);
    else
        CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_ALL);
}


/*******************************************************
int WhichRadioButton(HWND hWnd)

Returns the selected radio button
(and returns SEL_ALL if nothing is selected)
\*******************************************************/
int WhichRadioButton(HWND hWnd)
{
    HWND hRadio_One;
    HWND hRadio_Public;
    hRadio_Public = GetDlgItem(hWnd, GD_RADIO_PUBLIC);
    hRadio_One    = GetDlgItem(hWnd, GD_RADIO_ONE);

    if (SendMessage(hRadio_Public, BM_GETCHECK, 0, 0))
        return SEL_PUBLIC;
    else if (SendMessage(hRadio_One, BM_GETCHECK, 0, 0))
        return SEL_ONE;
    else
        return SEL_ALL;
}



/********************************************************

DoQuery

This function does error checking on the user's parameter choices,
then calls the GetGdiStats function.

In the case of an error the error message is displayed in the
result window

*********************************************************/

VOID DoQuery(HWND hWnd)
{
    HWND hResultList;
    BOOL bOk = TRUE;
    UINT nQueryResult = 0;

    //Parameters for the call
    LONG nQueryIndex;
    HANDLE hSelProcHandle = NULL; //the handle of the process selected
    UINT cjResultSize;
    PVOID pvResultBuf;
    LONG lPidType;
    TCHAR szOutputStr[40];


    //Get handles
    hResultList  = GetDlgItem (hWnd, GD_RESULT);

    //Erase results window
    SendMessage(hResultList, WM_SETREDRAW, FALSE, 0);
    SendMessage(hResultList, LB_RESETCONTENT, 0, 0);

    nQueryIndex    = ValidateIndexSelection(hWnd, hResultList);
    cjResultSize   = indexlist[nQueryIndex].nSize;

    switch (WhichRadioButton(hWnd))
    {
        case (SEL_PUBLIC) :
            lPidType = OBJS_PUBLIC;
            break;
        case (SEL_ALL) :
            lPidType = OBJS_ALL;
            break;
        default :
            lPidType = OBJS_CURRENT;
            hSelProcHandle = ValidateProcessSelection(hWnd, hResultList);
            if (!hSelProcHandle)
                bOk = FALSE;
            break;
    }


    bOk = bOk &&
          (nQueryIndex != -1) &&
          (cjResultSize);

    //Allocate memory for results
    if (bOk)
    {
        pvResultBuf = (PVOID) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, cjResultSize);
        if (pvResultBuf == NULL)
        {
            bOk = FALSE;
            _stprintf(szOutputStr,
                     _TEXT("ERROR: Memory Allocation failed (0x%lx)"),
                     GetLastError());
            ErrorString(hResultList, szOutputStr);
        }
    }

    //Now we can make the call
    if (bOk)
    {
        if (nQueryIndex != GU_NUM_OBJS_ALL)
        {
            nQueryResult = NtGdiGetStats(hSelProcHandle,nQueryIndex,lPidType,pvResultBuf,cjResultSize);
        }
        else
        {
            DWORD pid;
            pid = (lPidType == OBJS_CURRENT) ? HandleToLong(hSelProcHandle) : QUC_PID_TOTAL;
            nQueryResult = QueryUserCounters(QUERYUSER_HANDLES,
                              &pid,
                              sizeof(DWORD),
                              pvResultBuf,
                              sizeof(DWORD) * NUM_USER_OBJS);
        }

        if (!nQueryResult)
            DisplayResults(hResultList,pvResultBuf,cjResultSize,nQueryIndex);
        else
        {
            _stprintf(szOutputStr,_TEXT("ERROR: Query failed (code 0x%lx)"),nQueryResult);
            ErrorString(hResultList, szOutputStr);
        }
    }

    LocalFree(pvResultBuf);

    //Now show what has been written onto the results list box
    SendMessage(hResultList, WM_SETREDRAW, TRUE, 0);
}

/***************************************************************\
DisplayResults

This function displays the results of the query in the
Result Window
\***************************************************************/

VOID DisplayResults(HWND hResultWindow, PVOID pResults, UINT cjResultSize, LONG nIndex)
{
    DWORD * pdwResults;
    TCHAR   szResultStr[60];
    LONG    cObjSum        = 0;
    LONG    cObjPos        = 0;

    pdwResults = (DWORD *) pResults;

    //The output format is based on the type of query we are running
    switch(nIndex)
    {
        case (GS_NUM_OBJS_ALL) :
        case (GU_NUM_OBJS_ALL) :
            while (cjResultSize)
            {
                //Only print out non-zero results
                if (*pdwResults)
                {
                    //Print out the results with the associated object's name
                    if (nIndex == GS_NUM_OBJS_ALL)
                        {
                        _stprintf(szResultStr, _TEXT("%s%6d"), aszGdiObjType[cObjPos], *pdwResults);
                        }
                    else
                        {
                        _stprintf(szResultStr, _TEXT("%s%6d"), aszUserObjType[cObjPos], *pdwResults);
                        }

                    SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr);

                    cObjSum += *pdwResults;
                }
                pdwResults++;
                cObjPos++;
                cjResultSize -= sizeof(DWORD);
            }

            //print out the sum information
            if (!cObjSum)
                _stprintf(szResultStr, _TEXT("No gdi objects"));
            else
                _stprintf(szResultStr, _TEXT("TOTAL           %6d"),cObjSum);

            SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr);
            break;

        case (GS_HANDOBJ_CURRENT) :
        case (GS_HANDOBJ_MAX)     :
        case (GS_HANDOBJ_ALLOC)   :
        case (GS_LOOKASIDE_INFO)  :
            for (cObjPos = 0; cObjPos < NUM_GDI_OBJS; cObjPos++)
            {
                 if (pdwResults[cObjPos] || pdwResults[cObjPos + NUM_GDI_OBJS])
                 {
                     _stprintf(szResultStr, _TEXT("%s  %6u %6u"),
                               aszGdiObjType[cObjPos],
                               pdwResults[cObjPos],
                               pdwResults[cObjPos + NUM_GDI_OBJS]);
                     SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr);

                     //just want to know if there is at least one entry
                     cObjSum = 1;
                 }
            }

            if (cObjSum)
            {
                if (nIndex != GS_LOOKASIDE_INFO)
                {
                    _stprintf(szResultStr, _TEXT("(Column 1 is handle count,"));
                    SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr);
                    _stprintf(szResultStr, _TEXT("Column 2 is object count)"));
                    SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr);
                }
                else
                {
                     _stprintf(szResultStr, _TEXT("(Column 1 is number of hits,"));
                     SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr);
                     _stprintf(szResultStr, _TEXT("Column 2 is total allocations)"));
                     SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr);
                }
            }
            else
            {
                //All zero results indicate that the system is not
                //checked
                _stprintf(szResultStr, _TEXT("Valid for Checked builds only"));
                SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr);
            }
            break;

        default :
            //Raw dump
            while (cjResultSize)
            {
                _stprintf(szResultStr, _TEXT("Result: 0x%lx"), *pdwResults);
                SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr);

                pdwResults++;
                cjResultSize -= sizeof(DWORD);
            }
            break;
    }

}

/*************************************************
ErrorString

This function outputs a string into the error window.
It does not erase the entire window in case more
than one line of errorstrings are to be displayed

***************************************************/

VOID ErrorString(HWND hErrorList, LPTSTR szErrorString)
{
    SendMessage(hErrorList, LB_ADDSTRING, 0, (LPARAM) szErrorString);
}