//==================================================
// ModuleList - Matt Pietrek 1998
// Microsoft Systems Journal, September 1998
// FILE: ModuleList.CPP
//==================================================
#include <windows.h>
#include <COMMCTRL.H>
#pragma hdrstop
#include "ModuleList.h"
#include "ModuleListClasses.h"
#include "ModuleListOSCode.h"
// Helper function prototypes
void Handle_WM_INITDIALOG(HWND hDlg);
void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam );
void Handle_WM_CLOSE( HWND hDlg );
void Handle_WM_SIZE(HWND hWndDlg, WPARAM wParam, LPARAM lParam );
BOOL CALLBACK ModuleListDlgProc(HWND,UINT,WPARAM,LPARAM);
void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt );
void PopulateTree( HWND hWndTree );
HTREEITEM AddTreeviewSubItem( HWND hWndTree,
HTREEITEM hTreeItem,
LPTSTR pszItemText,
BOOL fItemData = FALSE,
LPARAM itemData = 0 );
BOOL GetFileDescription( PSTR pszFileName, PSTR pszDesc, unsigned cbDesc );
// ======================= String literals ===============================
char gszRegistryKey[] = "Software\\WheatyProductions\\ModuleList";
char gszMBTitle[] = "ModuleList, by Matt Pietrek - MSJ September 1998" ;
char gszAboutText[] = "ModuleList shows all of the loaded DLLs in the "
"system. Each DLL node contains the filename, the "
"load address, the directory where the DLL was loaded "
"from, and the number of processes using the DLL.";
// =========================== Global Variables =============================
HWND g_hWndTree = 0; // HWND of the TreeView control
HWND g_hDlg = 0; // HWND of the dialog
ModuleList g_ModuleList; // List of all loaded modules
ProcessIdToNameMap g_ProcessIdToNameMap; // List of all process names & IDs
// ============================== Start of code ===============================
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
InitCommonControls(); // Needed for the TreeView control
// Bring up the user interface
DialogBox( hInstance, MAKEINTRESOURCE(IDD_MODULELIST),
0, (DLGPROC)ModuleListDlgProc );
return 0;
}
BOOL
CALLBACK ModuleListDlgProc( HWND hDlg,UINT msg,WPARAM wParam,LPARAM lParam )
{
//
// The dialog procedure for the main window
//
switch ( msg )
{
case WM_INITDIALOG:
Handle_WM_INITDIALOG( hDlg ); return TRUE;
case WM_CLOSE:
Handle_WM_CLOSE( hDlg ); break;
case WM_SIZE:
Handle_WM_SIZE( hDlg, wParam, lParam ); break;
case WM_COMMAND:
Handle_WM_COMMAND( hDlg, wParam, lParam ); break;
// let everything else fall through
}
return FALSE;
}
//============================================================================
// Walk through the list of objects, adding each object name to the root of
// the treeview
//============================================================================
void PopulateTree( HWND hWndTree )
{
// Empty the TreeView
TreeView_DeleteAllItems( hWndTree );
// Empty out the data structures (if necessary)
g_ModuleList.Clear();
g_ProcessIdToNameMap.Clear();
// Populate the data structures. Try using the Toolhelp32 APIs first
BOOL fModListOK;
fModListOK = PopulateModuleList_ToolHelp32(
g_ModuleList, g_ProcessIdToNameMap );
if ( !fModListOK )
{
// ToolHelp32 didn't work (probably wasn't present). Try PSAPI.DLL
fModListOK = PopulateModuleList_PSAPI(
g_ModuleList, g_ProcessIdToNameMap );
if ( !fModListOK ) // PSAPI.DLL probably wasn't found
return;
}
// Enumerate the module list, adding the info for each DLL to the TreeView
PModuleInstance pModInst = 0; // 0 begins the enumeration
while ( pModInst = g_ModuleList.Enumerate(pModInst) )
{
// Make a copy of the full DLL path that we can slice and dice
char szFullModuleName[MAX_PATH+128];
lstrcpy( szFullModuleName, pModInst->m_pszName );
PSTR pszBaseName = strrchr( szFullModuleName, '\\' );
*pszBaseName = 0; // Separate base name from path
pszBaseName++; // Advanced past the null separator we just added
strupr( pszBaseName );
// Create the top level string for the DLL, then add it to the TreeView
char szModuleDescription[MAX_PATH+128];
wsprintf( szModuleDescription, "%s (%s)",
pszBaseName, szFullModuleName );
HTREEITEM hTreeItem = AddTreeviewSubItem( g_hWndTree,
NULL,
szModuleDescription );
// Create and add a subitem string describing the load address and
// DLL reference count
char szOtherModuleInfo[MAX_PATH+128];
wsprintf( szOtherModuleInfo, "* Load address:%08X Reference count:%u",
pModInst->m_hModule,pModInst->GetNumberOfProcessReferences() );
AddTreeviewSubItem( g_hWndTree, hTreeItem, szOtherModuleInfo );
//
// Try to retrieve the file description from the file's version
// resource. If found, add it as another subitem
//
char szFileDesc[1024];
if ( GetFileDescription(pModInst->m_pszName,
szFileDesc, sizeof(szFileDesc)) )
{
char szBuffer[ sizeof(szFileDesc) + 64 ];
wsprintf( szBuffer, "* Description: %s", szFileDesc );
AddTreeviewSubItem( g_hWndTree, hTreeItem, szBuffer );
}
// Iterate through each process that references the DLL, and add it
// as a subitem
int enumHandle = 0; // Passing 0 begins the enumeration
DWORD pid; // -1 means end of pid list
while ( -1 != (pid = pModInst->EnumerateProcessReferences(enumHandle)))
{
char szProcessInfo[MAX_PATH+128];
wsprintf( szProcessInfo, "%s", g_ProcessIdToNameMap.Lookup(pid) );
AddTreeviewSubItem( g_hWndTree, hTreeItem, szProcessInfo );
}
}
}
void Handle_WM_INITDIALOG(HWND hDlg)
{
// Get the window coordinates where the program was last running,
// and move the window to that spot.
POINT pt;
GetSetPositionInfoFromRegistry( FALSE, &pt );
SetWindowPos(hDlg, 0, pt.x, pt.y, 0, 0,
SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER | SWP_NOACTIVATE);
g_hDlg = hDlg;
g_hWndTree = GetDlgItem(hDlg, IDC_TREE1);
PopulateTree( g_hWndTree );
}
void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam )
{
WORD wNotifyCode = HIWORD(wParam); // notification code
WORD wID = LOWORD(wParam); // item, control, or accelerator id
HWND hwndCtl = (HWND) lParam; // handle of control
if ( IDC_BUTTON_REFRESH == wID )
{
if ( BN_CLICKED == wNotifyCode )
PopulateTree( g_hWndTree );
}
else if ( IDC_BUTTON_ABOUT == wID )
{
if ( BN_CLICKED == wNotifyCode )
MessageBox( hWndDlg, gszAboutText, gszMBTitle, MB_OK );
}
}
void Handle_WM_CLOSE( HWND hDlg )
{
// Save off the window's X,Y coordinates for next time
RECT rect;
if ( GetWindowRect( hDlg, &rect ) )
GetSetPositionInfoFromRegistry( TRUE, (LPPOINT)&rect );
EndDialog(hDlg, 0);
}
void Handle_WM_SIZE(HWND hWndDlg, WPARAM wParam, LPARAM lParam )
{
RECT tvRect, dlgRect;
POINT pt;
WORD nClientWidth = LOWORD(lParam);
WORD nClientHeight= HIWORD(lParam);
GetClientRect( hWndDlg, &dlgRect ); // Get size of dialog
GetWindowRect( g_hWndTree, &tvRect ); // Get screen position of child
pt.x = tvRect.left; // Get the X,Y coordinates for the top left
pt.y = tvRect.top; // and reuse the M in the resized client
ScreenToClient( hWndDlg, &pt ); // Calculate screen X,Y of child window
WORD tvWidth = nClientWidth - ( pt.x * 2); // Equal spacing on all borders
WORD tvHeight= nClientHeight - (WORD)(pt.y + pt.x);
MoveWindow( g_hWndTree, pt.x, pt.y, tvWidth, tvHeight, TRUE );
}
void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt )
{
//
// Function that saves or restores the coordinates of a dialog box
// in the system registry. Handles the case where there's nothing there.
//
HKEY hKey;
DWORD dataSize, err, disposition;
char szKeyName[] = "DlgCoordinates";
if ( !fSave ) // In case the key's not there yet, we'll
lppt->x = lppt->y = 0; // return 0,0 for the coordinates
// Open the registry key (or create it if the first time being used)
err = RegCreateKeyEx( HKEY_CURRENT_USER, gszRegistryKey, 0, 0,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
0, &hKey, &disposition );
if ( ERROR_SUCCESS != err )
return;
if ( fSave ) // Save out coordinates
{
RegSetValueEx(hKey,szKeyName, 0, REG_BINARY,(PBYTE)lppt,sizeof(*lppt));
}
else // read in coordinates
{
dataSize = sizeof(*lppt);
RegQueryValueEx( hKey, szKeyName, 0, 0, (PBYTE)lppt, &dataSize );
}
RegCloseKey( hKey );
}
HTREEITEM AddTreeviewSubItem( HWND hWndTree,
HTREEITEM hTreeItem,
LPTSTR pszItemText,
BOOL fItemData,
LPARAM itemData )
{
TVINSERTSTRUCT tvi;
tvi.hParent = hTreeItem;
tvi.hInsertAfter = TVI_SORT;
tvi.item.mask = TVIF_TEXT;
tvi.item.pszText = pszItemText;
tvi.item.cchTextMax = lstrlen( pszItemText );
if ( fItemData )
{
tvi.item.mask |= TVIF_PARAM;
tvi.item.lParam = itemData;
}
return TreeView_InsertItem( hWndTree, &tvi );
}
BOOL GetFileDescription( PSTR pszFileName, PSTR pszDesc, unsigned cbDesc )
{
//
// Give a filename, tries to extract the FileDescription version resource
//
BYTE verInfo[8192];
DWORD cbVerInfo = sizeof(verInfo);
if ( !GetFileVersionInfo(pszFileName, 0, cbVerInfo, verInfo) )
return FALSE;
PSTR pszVerRetVal;
UINT cbReturn;
BOOL fFound;
// Try first with the 1252 codepage. To do so, we need to format a
// string with the 1252 codepage (Windows Multilingual)
char szQueryStr[0x100];
wsprintf( szQueryStr, "\\StringFileInfo\\%04X%04X\\FileDescription",
GetUserDefaultLangID(), 1252 );
fFound = VerQueryValue( verInfo, szQueryStr,
(LPVOID *)&pszVerRetVal, &cbReturn );
if ( !fFound )
{
// Hmm... 1252 wasn't found. Try the 1200 codepage
wsprintf( szQueryStr, "\\StringFileInfo\\%04X%04X\\FileDescription",
GetUserDefaultLangID(), 1200 );
fFound = VerQueryValue( verInfo, szQueryStr,
(LPVOID *)&pszVerRetVal, &cbReturn );
}
if ( fFound ) // If we found the string, copy it to the return buffer
{
lstrcpyn( pszDesc, pszVerRetVal, min(cbReturn+1, cbDesc) );
return TRUE;
}
return FALSE;
}
ModuleListClasses.H
class ModuleList;
//=============================================================================
// ModuleInstance class:
// Represents exactly one loaded module (DLL). DLLs with the same
// name, but in different directories are distinct. Likewise, if the
// the DLL is loaded at a different address is multiple processes, each
// distinct load address is represented by a unique ModuleInstance.
//
// Besides the HMODULE and name, the class also keeps a list of process
// IDs that have this module loaded.
//=============================================================================
class ModuleInstance
{
friend class ModuleList;
ModuleInstance * m_pNext;
DWORD m_nProcessReferences; // Number of referencing process IDs
PDWORD m_pProcessReferences; // Array of process IDs (initially empty)
public:
ModuleInstance( HMODULE hModule, PSTR pszName );
~ModuleInstance(void);
BOOL IsEqual( HMODULE hModule, PSTR pszName );
BOOL AddProcessReference( DWORD pid );
DWORD EnumerateProcessReferences( int & enumHandle );
DWORD GetNumberOfProcessReferences( void ){return m_nProcessReferences;}
PSTR m_pszName;
HMODULE m_hModule;
};
typedef ModuleInstance * PModuleInstance;
//=============================================================================
// ModuleList class:
// A simple linked list container for instances of the ModuleInstance
// class. Methods are provided to lookup, add, and enumerate the list.
//=============================================================================
class ModuleList
{
PModuleInstance m_pModuleInstanceList;
public:
ModuleList( void ){ m_pModuleInstanceList = 0; }
~ModuleList( void ){ Clear(); }
void Clear( void ); // Empty the list
PModuleInstance Lookup( HMODULE hModule, PSTR pszName );
PModuleInstance Add( HMODULE hModule, PSTR pszName );
PModuleInstance Enumerate( PModuleInstance pModInst );
};
//=============================================================================
// ProcessIdToNameMap class:
// A simple array based mapping between process IDs and the complete
// pathname to the EXE for the process.
//=============================================================================
class ProcessIdToNameMap
{
struct ProcessIdName // A private class. Each process has one instance
{
DWORD m_pid;
PSTR m_pszName;
};
ProcessIdName * m_array;
DWORD m_nEntries;
public:
ProcessIdToNameMap( void ){ m_array = 0; m_nEntries = 0; }
~ProcessIdToNameMap( void ){ Clear(); }
void Clear( void );
BOOL Add( DWORD pid, PSTR pszName );
PSTR Lookup( DWORD pid );
};
ModuleListOSCode.CPP
//==================================================
// ModuleList - Matt Pietrek 1998
// Microsoft Systems Journal, September 1998
// FILE: ModuleListOSCode.CPP
//==================================================
#include <windows.h>
#pragma hdrstop
#include "tlhelp32.h"
#include "ModuleListClasses.h"
#include "ModuleListOSCode.h"
//==================== typedefs for ToolHelp32 functions =====================
typedef HANDLE (WINAPI * PFNCREATETOOLHELP32SNAPSHOT)(
DWORD dwFlags, DWORD th32ProcessID);
typedef BOOL (WINAPI * PFNPROCESS32FIRST)(
HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
typedef BOOL (WINAPI * PFNPROCESS32NEXT)(
HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
typedef BOOL (WINAPI * PFNMODULE32FIRST)(
HANDLE hSnapshot, LPMODULEENTRY32 lpme);
typedef BOOL (WINAPI * PFNMODULE32NEXT)(
HANDLE hSnapshot, LPMODULEENTRY32 lpme);
//====================== Populate the module list using ToolHelp32 ============
BOOL
PopulateModuleList_ToolHelp32(
ModuleList & modList,
ProcessIdToNameMap &pidNameMap )
{
static HMODULE hModKERNEL32 = 0;
static PFNCREATETOOLHELP32SNAPSHOT pfnCreateToolhelp32Snapshot = 0;
static PFNPROCESS32FIRST pfnProcess32First = 0;
static PFNPROCESS32NEXT pfnProcess32Next = 0;
static PFNMODULE32FIRST pfnModule32First = 0;
static PFNMODULE32NEXT pfnModule32Next = 0;
//
// Hook up to the ToolHelp32 functions dynamically. We can't just call
// the functions implicitly, since that would make this program not run
// under Windows NT 3.X and Windows NT 4
//
if ( !hModKERNEL32 )
hModKERNEL32 = GetModuleHandle( "KERNEL32.DLL" );
pfnCreateToolhelp32Snapshot = (PFNCREATETOOLHELP32SNAPSHOT)
GetProcAddress( hModKERNEL32, "CreateToolhelp32Snapshot" );
pfnProcess32First = (PFNPROCESS32FIRST)
GetProcAddress( hModKERNEL32, "Process32First" );
pfnProcess32Next = (PFNPROCESS32NEXT)
GetProcAddress( hModKERNEL32, "Process32Next" );
pfnModule32First = (PFNMODULE32FIRST)
GetProcAddress( hModKERNEL32, "Module32First" );
pfnModule32Next = (PFNMODULE32NEXT)
GetProcAddress( hModKERNEL32, "Module32Next" );
if ( !pfnCreateToolhelp32Snapshot
|| !pfnProcess32First || !pfnProcess32Next
|| !pfnModule32First || !pfnModule32Next )
return FALSE;
//
// Create a ToolHelp32 snapshot containing the process list
//
HANDLE hSnapshotProcess;
hSnapshotProcess = pfnCreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if ( !hSnapshotProcess )
return FALSE;
// Iterate through each of the processes in the snapshot
PROCESSENTRY32 procEntry = { sizeof(PROCESSENTRY32) };
BOOL fProcessWalkContinue;
for (fProcessWalkContinue = pfnProcess32First(hSnapshotProcess,&procEntry);
fProcessWalkContinue;
fProcessWalkContinue = pfnProcess32Next(hSnapshotProcess,&procEntry) )
{
// Add the process name and pid to the mapping
pidNameMap.Add( procEntry.th32ProcessID, procEntry.szExeFile );
//
// Enumerate the module list for this process. Start by taking
// another ToolHelp32 snapshot, this time of the process's module list
//
HANDLE hSnapshotModule;
hSnapshotModule = pfnCreateToolhelp32Snapshot( TH32CS_SNAPMODULE,
procEntry.th32ProcessID );
if ( !hSnapshotModule )
continue;
// Iterate through each module in the snapshot
MODULEENTRY32 modEntry = { sizeof(MODULEENTRY32) };
BOOL fModWalkContinue;
for (fModWalkContinue = pfnModule32First(hSnapshotModule,&modEntry);
fModWalkContinue;
fModWalkContinue = pfnModule32Next(hSnapshotModule,&modEntry) )
{
// Hack! Cheezy way to figure out if this is EXE module itself
// If so, we don't want to add it to the module list
if ( 0 == stricmp( modEntry.szExePath, procEntry.szExeFile ) )
continue;
// Determine if this is a DLL we've already seen
PModuleInstance pModInst = modList.Lookup(modEntry.hModule,
modEntry.szExePath );
// If we haven't see it, add it to the list
if ( !pModInst )
pModInst = modList.Add( modEntry.hModule, modEntry.szExePath );
// Add this process to the list of processes using the DLL
pModInst->AddProcessReference( procEntry.th32ProcessID );
}
CloseHandle( hSnapshotModule ); // Done with module list snapshot
}
CloseHandle( hSnapshotProcess ); // Done with process list snapshot
return TRUE;
}
//====================== typedefs for PSAPI.DLL functions =====================
typedef BOOL (WINAPI * PFNENUMPROCESSES)(
DWORD * lpidProcess,
DWORD cb,
DWORD * cbNeeded
);
typedef BOOL (WINAPI * PFNENUMPROCESSMODULES)(
HANDLE hProcess,
HMODULE *lphModule,
DWORD cb,
LPDWORD lpcbNeeded
);
typedef DWORD (WINAPI * PFNGETMODULEFILENAMEEXA)(
HANDLE hProcess,
HMODULE hModule,
LPSTR lpFilename,
DWORD nSize
);
//=========================== Populate the module list using PSAPI ============
BOOL
PopulateModuleList_PSAPI(
ModuleList & modList,
ProcessIdToNameMap &pidNameMap )
{
static HMODULE hModPSAPI = 0;
static PFNENUMPROCESSES pfnEnumProcesses = 0;
static PFNENUMPROCESSMODULES pfnEnumProcessModules = 0;
static PFNGETMODULEFILENAMEEXA pfnGetModuleFileNameExA = 0;
//
// Hook up to the 3 functions in PSAPI.DLL dynamically. We can't
// just call the functions implicitly, since that would make this program
// require the presence of PSAPI.DLL
//
if ( !hModPSAPI )
hModPSAPI = LoadLibrary( "PSAPI.DLL" );
if ( !hModPSAPI )
return FALSE;
pfnEnumProcesses = (PFNENUMPROCESSES)
GetProcAddress( hModPSAPI,"EnumProcesses" );
pfnEnumProcessModules = (PFNENUMPROCESSMODULES)
GetProcAddress( hModPSAPI, "EnumProcessModules" );
pfnGetModuleFileNameExA = (PFNGETMODULEFILENAMEEXA)
GetProcAddress( hModPSAPI, "GetModuleFileNameExA" );
if ( !pfnEnumProcesses
|| !pfnEnumProcessModules
|| !pfnGetModuleFileNameExA )
return FALSE;
// If we get to this point, we've successfully hooked up to the PSAPI APIs
DWORD pidArray[1024];
DWORD cbNeeded;
DWORD nProcesses;
// EnumProcesses returns an array of process IDs
if ( !pfnEnumProcesses(pidArray, sizeof(pidArray), &cbNeeded) )
return FALSE;
nProcesses = cbNeeded / sizeof(DWORD); // Determine number of processes
// Iterate through each process in the array
for ( unsigned i = 0; i < nProcesses; i++ )
{
HMODULE hModuleArray[1024];
HANDLE hProcess;
DWORD pid = pidArray[i];
DWORD nModules;
// Using the process ID, open up a handle to the process
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, pid );
if ( !hProcess )
continue;
// EnumProcessModules returns an array of HMODULEs for the process
if ( !pfnEnumProcessModules(hProcess, hModuleArray,
sizeof(hModuleArray), &cbNeeded ) )
{
CloseHandle( hProcess );
continue;
}
// Calculate number of modules in the process
nModules = cbNeeded / sizeof(hModuleArray[0]);
// Iterate through each of the process's modules
for ( unsigned j=0; j < nModules; j++ )
{
HMODULE hModule = hModuleArray[j];
char szModuleName[MAX_PATH];
// GetModuleFileNameEx is like GetModuleFileName, but works
// in other process address spaces
pfnGetModuleFileNameExA(hProcess, hModule,
szModuleName, sizeof(szModuleName) );
if ( 0 == j ) // First module is the EXE. Just add it to the map
{
pidNameMap.Add( pid, szModuleName );
}
else // Not the first module. It's a DLL
{
// Determine if this is a DLL we've already seen
PModuleInstance pModInst = modList.Lookup(hModule,
szModuleName );
// If we haven't see it, add it to the list
if ( !pModInst )
pModInst = modList.Add( hModule, szModuleName );
// Add this process to the list of processes using the DLL
pModInst->AddProcessReference( pid );
}
}
CloseHandle( hProcess ); // We're done with this process handle
}
return TRUE;
}