Copyright © Microsoft Corporation. This document is an archived reproduction of a version originally published by Microsoft. It may have slight formatting modifications for consistency and to improve readability. |
//================================================== // 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 them 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; }