home *** CD-ROM | disk | FTP | other *** search
/ Troubleshooting Netware Systems / CSTRIAL0196.BIN / attach / msj / v10n05 / wqa0595.exe / FIX1MB.C < prev    next >
C/C++ Source or Header  |  1995-05-01  |  19KB  |  665 lines

  1. //==================================
  2. // Fix1MB.C - Matt Pietrek 1995
  3. //==================================
  4. #include <windows.h>
  5. #include <windowsx.h>
  6. #include <string.h>
  7. #include <stdlib.h>
  8. #include <toolhelp.h>
  9. #pragma hdrstop
  10. #include "Fix1MB.h"
  11. #include "prochook.h"
  12.  
  13. HANDLE HInstance;
  14. HWND HWndUsedLb;
  15. HWND HWndStatus;
  16. HWND HWndDlg;
  17.  
  18. BOOL FWorstCaseMode;
  19.  
  20. DWORD EndOfLastFixedBlock;  // linear address of end of last fixed block
  21. DWORD FreeBlocksTotalSize;  // Byte count of free regions < 1Mb
  22.  
  23. LRESULT CALLBACK _export Fix1MBDlgProc( HWND hWndDlg, UINT msg,
  24.                                         WPARAM wParam, LPARAM lParam );
  25. void GetModuleNameFromHandle(HANDLE handle, char *owner);
  26. void GetModuleFilenameFromHandle(HANDLE handle, char *owner);
  27. void AddHeapEntryToListbox(GLOBALENTRY *ge);
  28. void WalkHeap( void );
  29. int HandleSort(const void far *a, const void far *b);
  30. BOOL IsPotentialSpaceHog( GLOBALENTRY *ge );
  31. void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam);
  32. void Handle_WM_INITDIALOG(HWND hWndDlg);
  33. void InitFilteredModules(void);
  34. BOOL IsFilteredModule(HMODULE hModule);
  35. BOOL HookLoadModule(void);
  36. BOOL UnhookLoadModule(void);
  37. BOOL AllocTiledBelow1MbMemory(void);
  38. BOOL FreeTiledBelow1MbMemory(void);
  39. void AddToSystemIni(void);
  40. void HelpDialog( HWND hWndOnwer );
  41. VOID CenterWindow(HWND hWnd);
  42.  
  43. int PASCAL WinMain( HANDLE hInstance, HANDLE hPrevInstance,
  44.                     LPSTR lpszCmdLine, int nCmdShow )
  45. {
  46.     WNDCLASS wndclass;
  47.     char szWorstCase[32];
  48.     
  49.     if ( hPrevInstance )
  50.         return 0;
  51.  
  52.     HInstance = hInstance;
  53.  
  54.     if ( !GetClassInfo( 0, MAKEINTRESOURCE(32770), &wndclass ) )
  55.         return 0;
  56.  
  57.     wndclass.hInstance = HInstance;
  58.     wndclass.hIcon = LoadIcon(HInstance,"Fix1MBIcon");
  59.     wndclass.lpszClassName = "FIX1MBDLG";
  60.     if ( !RegisterClass(&wndclass) )
  61.         return 0;
  62.  
  63.     InitFilteredModules();
  64.  
  65.     FWorstCaseMode = GetProfileInt( "Fix1MB", "WorstCaseMode", FALSE );
  66.  
  67.     if ( !HookLoadModule() )
  68.     {
  69.         MessageBox( 0, "Couldn't hook WinExec and LoadModule", 0, MB_OK );
  70.         return 0;
  71.     }
  72.         
  73.     DialogBox( HInstance, "FIX1MBDLG", 0, (DLGPROC)Fix1MBDlgProc );
  74.         
  75.     UnhookLoadModule();
  76.  
  77.     // Write out the current WorstCase flag value
  78.     wsprintf( szWorstCase,"%u", FWorstCaseMode );
  79.     WriteProfileString( "Fix1MB", "WorstCaseMode", szWorstCase );
  80.     
  81.     return 0;
  82. }
  83.  
  84. //########################################################################
  85. // This section deals with watching calls to LoadModule and sucking up
  86. // all the low DOS memory beforehand.
  87. //########################################################################
  88.  
  89. NPHOOKCHILD npHookLoadModule;
  90.  
  91. HINSTANCE WINAPI __loadds Fix1MBLoadModule( LPCSTR lpszModuleName,
  92.                                             LPVOID lpvParameterBlock )
  93. {
  94.     HINSTANCE retValue;
  95.     
  96.     #ifdef __BORLANDC__
  97.     __asm   push    DS
  98.     __asm   mov     ax, seg HInstance   // BC++ 4.5 !!! STILL !!! doesn't
  99.     __asm   mov     ds, ax              // do __loadds properly in EXEs!
  100.     #endif
  101.  
  102.     ProcUnhook( npHookLoadModule );
  103.     
  104.     AllocTiledBelow1MbMemory();
  105.     
  106.     retValue = LoadModule( lpszModuleName, lpvParameterBlock );
  107.  
  108.     FreeTiledBelow1MbMemory();
  109.     
  110.     ProcHook( npHookLoadModule );
  111.     
  112.     #ifdef __BORLANDC__
  113.     __asm   pop     ds
  114.     #endif
  115.  
  116.     return retValue;
  117. }
  118.  
  119. BOOL HookLoadModule(void)
  120. {
  121.     npHookLoadModule = SetProcAddress(  (FARPROC)LoadModule,
  122.                                         (FARPROC)Fix1MBLoadModule,
  123.                                         FALSE );
  124.     
  125.     return (BOOL)npHookLoadModule;
  126. }
  127.  
  128. BOOL UnhookLoadModule(void)
  129. {
  130.     SetProcRelease( npHookLoadModule );
  131.     
  132.     return TRUE;
  133. }
  134.  
  135.  
  136. HANDLE ListHead = 0;
  137.     
  138. BOOL AllocTiledBelow1MbMemory(void)
  139. {
  140.     HANDLE h;
  141.     DWORD blockSize;
  142.     
  143.     if ( FWorstCaseMode )
  144.         blockSize = 0x200;
  145.     else
  146.         blockSize = 0x8000;  
  147.  
  148.     // Allocate it all!
  149.         
  150. top:
  151.     while ( h = (HANDLE)GlobalDosAlloc(blockSize) )
  152.     {
  153.         #if 0
  154.         char szTemp[128];
  155.         wsprintf( szTemp, "handle %04X = %08lX (size=%04X)\r\n",
  156.                     h, GetSelectorBase(h), blockSize );
  157.         OutputDebugString( szTemp );
  158.         #endif
  159.  
  160.         *(LPDWORD)GlobalLock(h) = ListHead;
  161.         ListHead = h;
  162.     }
  163.  
  164.     if ( FWorstCaseMode )
  165.     {
  166.         // We have a whole mess of 0x200 byte blocks.  Free every other one
  167.         h = ListHead;
  168.  
  169.         while ( h )
  170.         {
  171.             HANDLE hNext, hNext2;
  172.  
  173.             // Get the next block handle in the list
  174.             hNext = *(LPHANDLE)GlobalLock( h );
  175.  
  176.             if ( !hNext )   // If it's NULL, we're done
  177.                 break;
  178.  
  179.             // Get the next block of the next block ( h->next->next )
  180.             hNext2 = *(LPHANDLE)GlobalLock( hNext );
  181.  
  182.             // Point the "next" field of the curr block to the next, next block
  183.             *(LPHANDLE)GlobalLock( h ) = hNext2;
  184.  
  185.             // Delete the next block
  186.             GlobalDosFree( hNext );
  187.  
  188.             // Advance to the next, next block
  189.             h = hNext2;
  190.         }
  191.     }
  192.     else
  193.     {
  194.         blockSize = blockSize >> 1;
  195.         if ( blockSize > 0x200 )    // Grab all blocks > 0x200
  196.             goto top;
  197.  
  198.         // Free the first block in the list, and readjust the ListHead
  199.         if ( ListHead )
  200.         {
  201.             h = *(LPWORD)GlobalLock( ListHead );
  202.             GlobalDosFree( ListHead );
  203.             ListHead = h;
  204.         }
  205.     }
  206.  
  207.     return TRUE;
  208. }
  209.  
  210. BOOL FreeTiledBelow1MbMemory(void)
  211. {
  212.     HANDLE h, h2;
  213.  
  214.     for ( h2 = h = ListHead; h2; h = h2 )
  215.     {
  216.         h2 = *(LPWORD)GlobalLock(h);
  217.         GlobalDosFree( h );
  218.     }
  219.  
  220.     ListHead = 0;
  221.     
  222.     return TRUE;
  223. }
  224.  
  225. //########################################################################
  226. // This section walks the low heap and determines how much frees space
  227. // there is and identifies potential low mem space hogs.
  228. //########################################################################
  229.  
  230. typedef struct
  231. {
  232.     HGLOBAL     hGlobal;
  233.     DWORD       size;
  234. } LOW_1MB_BLOCK, far *LPLOW_1MB_BLOCK;
  235.  
  236. //
  237. // Walk the heap below one megabyte.  The command parameter indicates
  238. // whether the listboxes should be updated, or if the memory allocated
  239. // by the "Allocate All" button should be freed.
  240. //
  241. void WalkHeap( void )
  242. {
  243.     GLOBALENTRY ge;
  244.     BOOL moreToGo;
  245.     char buffer[128];
  246.     DWORD lastBelowOneMeg = 0;
  247.     LPLOW_1MB_BLOCK lpBlockArray;
  248.     unsigned i, cHandles;
  249.  
  250.     // Allocate memory for an array of handles that we'll need to sort
  251.     lpBlockArray = (LPLOW_1MB_BLOCK)GlobalAllocPtr( GMEM_MOVEABLE,
  252.                                             sizeof(LOW_1MB_BLOCK) * 0x2000 );
  253.     if ( !lpBlockArray )
  254.         return;
  255.     cHandles = 0;
  256.     
  257.     ge.dwSize = sizeof(ge);
  258.     moreToGo = GlobalFirst( &ge, GLOBAL_ALL );
  259.     EndOfLastFixedBlock = ge.dwAddress;
  260.     FreeBlocksTotalSize = 0;
  261.     
  262.     while ( moreToGo )
  263.     {
  264.         // Break out of the loop when we encounter a block above 1 Meg
  265.         if ( ge.dwAddress>=0x100000LU )
  266.         {
  267.             ge.dwAddress = lastBelowOneMeg;
  268.             ge.hBlock = 0xFFFF;     // A pseudo FIXED handle
  269.             ge.wType = GT_SENTINEL;
  270.             IsPotentialSpaceHog( &ge );
  271.             break;  // Break out of loop
  272.         }
  273.         
  274.         if ( IsPotentialSpaceHog( &ge ) )
  275.         {
  276.             lpBlockArray[cHandles].size = ge.dwBlockSize;
  277.             lpBlockArray[cHandles++].hGlobal = ge.hBlock;
  278.         }
  279.         
  280.         lastBelowOneMeg = ge.dwAddress + ge.dwBlockSize;
  281.         moreToGo = GlobalNext(&ge, GLOBAL_ALL);
  282.     }
  283.  
  284.     qsort( lpBlockArray, cHandles, sizeof(LOW_1MB_BLOCK), HandleSort );
  285.  
  286.     for ( i=0; i < cHandles; i++ )
  287.     {
  288.         GLOBALENTRY ge;
  289.         
  290.         ge.dwSize = sizeof(ge);
  291.         
  292.         if ( GlobalEntryHandle( &ge, lpBlockArray[i].hGlobal ) )
  293.             AddHeapEntryToListbox( &ge );
  294.     }
  295.  
  296.     GlobalFreePtr( lpBlockArray );
  297.  
  298.     // update the Total Free Space field
  299.     wsprintf(buffer, "Potential Free Space: %lu bytes (%luK)",
  300.         FreeBlocksTotalSize, FreeBlocksTotalSize/1024);
  301.     SetWindowText(HWndStatus, buffer);
  302. }
  303.  
  304. int HandleSort(const void far *a, const void far *b)
  305. {
  306.     DWORD sizeA, sizeB;
  307.  
  308.     sizeA = ((LPLOW_1MB_BLOCK)a)->size;
  309.     sizeB = ((LPLOW_1MB_BLOCK)b)->size;
  310.  
  311.     if ( sizeA == sizeB )
  312.         return 0;
  313.     return (sizeB > sizeA) ? 1 : -1;
  314. }
  315.  
  316. //
  317. // Given a global handle, get the module name of its owner
  318. //
  319. void GetModuleNameFromHandle(HANDLE handle, char *pszOwner)
  320. {
  321.     MODULEENTRY me;
  322.     TASKENTRY   te;
  323.  
  324.     te.dwSize = sizeof(te);
  325.     if ( TaskFindHandle(&te, handle) )  // First see if a task owns it
  326.     {
  327.         lstrcpy(pszOwner, te.szModule);
  328.         return;
  329.     }
  330.  
  331.     me.dwSize = sizeof(me);
  332.     if (ModuleFindHandle(&me, handle))  // Nope?  Try a module owner
  333.     {
  334.         lstrcpy(pszOwner, me.szModule);
  335.         return;
  336.     }
  337.     
  338.     pszOwner[0] = 0;   // No owner found.  Return empty string
  339. }
  340.  
  341. //
  342. // Given a global handle, get the file name of its owner
  343. //
  344. void GetModuleFilenameFromHandle(HANDLE handle, char *pszOwner)
  345. {
  346.     MODULEENTRY me;
  347.     TASKENTRY   te;
  348.  
  349.     te.dwSize = sizeof(te);
  350.     if ( TaskFindHandle(&te, handle) )  // First see if a task owns it
  351.     {
  352.         me.dwSize = sizeof(me);
  353.         ModuleFindHandle( &me, te.hModule );
  354.  
  355.         lstrcpy( pszOwner, me.szExePath );
  356.         return;
  357.     }
  358.  
  359.     me.dwSize = sizeof(me);
  360.     if (ModuleFindHandle(&me, handle))  // Nope?  Try a module owner
  361.     {
  362.         lstrcpy(pszOwner, me.szExePath);
  363.         return;
  364.     }
  365.     
  366.     pszOwner[0] = 0;   // No owner found.  Return empty string
  367. }
  368.  
  369. // More descriptive strings for the TOOLHELP GT_xxx #define's
  370. char *BlockTypes[] = {
  371. "ALLOC", "DGROUP", "DATA", "CODE", "TDB", "RESOURCE", "MODULE",
  372. "FREE", "INTERNAL", "SENTINEL", "BURGERMASTER" };
  373.  
  374. //
  375. // Given a new memory block, see if is fixed or locked.  If so, it
  376. // may indicate the end of a free region.  If so, add the info
  377. // about the free region to the left listbox.  Then add the block's
  378. // info to the right listbox.
  379. //
  380. void AddHeapEntryToListbox(GLOBALENTRY *ge)
  381. {
  382.     char buffer[128];
  383.     char szFileName[260];
  384.     char owner[32];
  385.  
  386.     #if 1
  387.     GetModuleFilenameFromHandle( ge->hOwner, szFileName );
  388.     wsprintf( buffer,"%05lu bytes (%s %s) %s",
  389.             ge->dwBlockSize, BlockTypes[ge->wType], 
  390.             (ge->hBlock & 1) ? "Fixed" : "Locked",
  391.             szFileName );
  392.     #else
  393.     // Show the segment's handle and module name
  394.     GetModuleNameFromHandle(ge->hOwner, owner);
  395.     wsprintf( buffer,"%05lu bytes (%04X) (%s %s %s)",
  396.             ge->dwBlockSize, ge->hBlock, owner, BlockTypes[ge->wType], 
  397.             (ge->hBlock & 1) ? "Fixed" : "Locked" );
  398.     #endif
  399.  
  400.     SendMessage(HWndUsedLb,LB_ADDSTRING,0,(LPARAM)(LPSTR)buffer);
  401. }
  402.  
  403. BOOL IsPotentialSpaceHog( GLOBALENTRY *ge )
  404. {
  405.     if ( ge->wType == GT_FREE )     // If not a free block...
  406.         return FALSE;
  407.     
  408.     if ( !(ge->hBlock & 1) )        // Is it a moveable block (low bit is off)
  409.     {                               // Yes?  Then check for locks.
  410.         if ( !(ge->wcLock) && !(ge->wcPageLock) )
  411.             return FALSE;
  412.     }
  413.     
  414.     // At this point, we should only have immoveable blocks.
  415.     // Was there a gap between the end of the last fixed block
  416.     // and the start of this one?  If so, there was a free region.
  417.     // Add the relevant information to the left listbox
  418.     if ( ge->dwAddress > EndOfLastFixedBlock )
  419.     {
  420.         DWORD freeBlockLen = ge->dwAddress-EndOfLastFixedBlock;
  421.         FreeBlocksTotalSize += freeBlockLen;
  422.     }
  423.             
  424.     EndOfLastFixedBlock = ge->dwAddress + ge->dwBlockSize;
  425.  
  426.     // Now throw out blocks that shouldn't appear in the space hog list
  427.     if ( !ge->hBlock )              // Throw out sentinel blocks
  428.         return FALSE;               // with no handles
  429.     if ( ge->wType == GT_TASK )
  430.         return FALSE;
  431.     if ( (ge->hBlock == 0xFFFF) && (ge->wType == GT_SENTINEL) )
  432.         return FALSE;
  433.     if ( IsFilteredModule(ge->hOwner) )
  434.         return FALSE;
  435.  
  436.     return TRUE;
  437. }
  438.  
  439. //########################################################################
  440. // This section contains helps us filter out modules that the user can't
  441. // do anything about (i.e., system DLLs)
  442. //########################################################################
  443.  
  444. typedef struct
  445. {
  446.     LPSTR   lpszModName;
  447.     HMODULE hModule;
  448. } FILTERED_MODULE, FAR * LPFILTERED_MODULE;
  449.  
  450. FILTERED_MODULE FilteredModules[] = 
  451. {
  452. { "KERNEL",     0 },
  453. { "SYSTEM",     0 },
  454. { "KEYBOARD",   0 },
  455. { "MOUSE",      0 },
  456. { "DISPLAY",    0 },
  457. { "SOUND",      0 },
  458. { "COMM",       0 },
  459. { "FONTS",      0 },
  460. { "OEMFONTS",   0 },
  461. { "GDI",        0 },
  462. { "USER",       0 },
  463. { "TOOLHELP",   0 },
  464. { "WIN87EM",    0 },
  465. { "MMSYSTEM",   0 },
  466. { "TIMER",      0 },
  467. { "WINOLDAP",   0 },
  468. };
  469.  
  470. unsigned cFilteredModules = sizeof(FilteredModules) / sizeof(FILTERED_MODULE);
  471.  
  472. void InitFilteredModules(void)
  473. {
  474.     unsigned i;
  475.     
  476.     for ( i=0; i < cFilteredModules; i++ )
  477.     {
  478.         FilteredModules[i].hModule
  479.             = (HMODULE)GetModuleHandle( FilteredModules[i].lpszModName );
  480.     }
  481. }
  482.  
  483. BOOL IsFilteredModule(HMODULE hModule)
  484. {
  485.     unsigned i;
  486.     
  487.     for ( i=0; i < cFilteredModules; i++ )
  488.     {
  489.         if ( hModule == FilteredModules[i].hModule )
  490.             return TRUE;
  491.     }
  492.     
  493.     return FALSE;
  494. }
  495.  
  496.  
  497. //########################################################################
  498. // This section contains the DlgProc code and message handlers.
  499. //########################################################################
  500.  
  501. //
  502. // Dialog proc for the main dialog
  503. //
  504. LRESULT CALLBACK _export Fix1MBDlgProc(HWND hWndDlg, UINT msg,
  505.                                        WPARAM wParam, LPARAM lParam)
  506. {
  507.     switch ( msg )
  508.     {
  509.         case WM_COMMAND:
  510.             Handle_WM_COMMAND(hWndDlg, wParam, lParam);
  511.             break;
  512.         case WM_INITDIALOG:
  513.             Handle_WM_INITDIALOG(hWndDlg);
  514.             break;
  515.         case WM_SIZE:
  516.             if ( wParam == SIZE_RESTORED )
  517.                 PostMessage( hWndDlg, WM_COMMAND, IDC_BUTTON_REWALK, 0 );
  518.             break;
  519.         case WM_CLOSE:
  520.             EndDialog( hWndDlg, 0 );
  521.             break;
  522.     }    
  523.  
  524.     return 0;
  525. }
  526.  
  527. //
  528. // Handle the pressing of any of the 3 buttons.
  529. //
  530. void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam)
  531. {
  532.     BOOL updateDisplay = FALSE;
  533.     HCURSOR oldCursor = 0;      // Some operations take awhile...
  534.     
  535.     switch ( wParam )
  536.     {   
  537.         case IDC_BUTTON_REWALK:
  538.             oldCursor = SetCursor( LoadCursor(0, IDC_WAIT) );
  539.             updateDisplay = TRUE;
  540.             break;
  541.         case IDC_HELP_FIX1MB:
  542.             HelpDialog( hWndDlg );
  543.             break;
  544.         case IDC_ADD_TO_SYS_INI:
  545.             AddToSystemIni();
  546.             break;
  547.         case IDC_WORST_CASE_MODE:
  548.             if ( HIWORD(lParam) == BN_CLICKED )
  549.                 FWorstCaseMode
  550.                     = IsDlgButtonChecked( hWndDlg, IDC_WORST_CASE_MODE );
  551.             break;
  552.     }
  553.  
  554.     //
  555.     // Clear out the listboxes, set immediate updating to false,
  556.     // and walk the heap.  Afterwards, turn listbox updating back on.
  557.     //
  558.     if ( updateDisplay )
  559.     {
  560.         SendMessage(HWndUsedLb, LB_RESETCONTENT, 0, 0);         
  561.         SendMessage(HWndUsedLb, WM_SETREDRAW, FALSE, NULL);
  562.         WalkHeap();
  563.         SendMessage(HWndUsedLb, WM_SETREDRAW, TRUE, NULL);
  564.     }
  565.     if ( oldCursor )            // Switch back to the normal cursor
  566.         SetCursor(oldCursor);
  567. }
  568.  
  569. void Handle_WM_INITDIALOG(HWND hWndDlg)
  570. {
  571.     HWndDlg = hWndDlg;
  572.  
  573.     CenterWindow( hWndDlg );
  574.  
  575.     // Get the HWND's of the commonly modified controls
  576.     HWndUsedLb = GetDlgItem(hWndDlg, IDC_LISTBOX_USED);
  577.     HWndStatus = GetDlgItem(hWndDlg, IDC_TEXT_STATUS);
  578.  
  579.     CheckDlgButton( HWndDlg, IDC_WORST_CASE_MODE, FWorstCaseMode );
  580.  
  581.     ShowWindow( HWndDlg, SW_MINIMIZE );
  582.     // Fake a "Re-Walk" button event
  583.     Handle_WM_COMMAND(hWndDlg, IDC_BUTTON_REWALK, 0); 
  584. }
  585.  
  586. void AddToSystemIni(void)
  587. {
  588.     char szPathToDll[ 768 ];
  589.     LPSTR p;
  590.     
  591.     GetModuleFileName( HInstance, szPathToDll, sizeof(szPathToDll) );
  592.     p = strrchr( szPathToDll, '\\' );
  593.     
  594.     if ( p )
  595.     {
  596.         LPSTR lpszRemainder;
  597.         
  598.         // first build the path to FX1MBDLL.DLL
  599.         lstrcpy( p+1, "FX1MBDLL.DLL " );
  600.         
  601.         lpszRemainder = szPathToDll + lstrlen(szPathToDll);
  602.         
  603.         // Then tack on the existing entries from the "drivers=" line
  604.         // from the [boot] section of SYSTEM.INI
  605.         GetPrivateProfileString("boot", "drivers", "", lpszRemainder,
  606.                                 512, "SYSTEM.INI" );
  607.  
  608.         if ( 0 == strstr(lpszRemainder, "FX1MBDLL") )
  609.         {
  610.             // Write out the revised string
  611.             WritePrivateProfileString("boot", "drivers", szPathToDll,
  612.                                         "SYSTEM.INI");
  613.         }
  614.         else
  615.         {
  616.             MessageBox(0, "Fix1MB is already in the SYSTEM.INI file", 0,MB_OK);
  617.         }
  618.     }
  619.     else
  620.     {
  621.         MessageBox( 0, "Error adding FX1MBDLL.DLL to SYSTEM.INI", 0, MB_OK );
  622.     }
  623. }
  624.  
  625. void HelpDialog( HWND hWndOnwer )
  626. {
  627.     MessageBox( hWndOnwer,
  628.     "Fix1MB is a tool to help with the dreaded \"out of memory\" errors"
  629.     " in Windows that cause new programs to be unable to start.  This "
  630.     "particular problem is caused by DLLs that inadvertantly suck up "
  631.     "all the memory below 1 megabyte in the Windows address space.  Windows "
  632.     "needs a certain amount of memory below 1 megabyte to start a new "
  633.     "task.\r\n\r\n"
  634.     "Fix1MB not only shows you which DLLs and programs are using this "
  635.     "precious memory, it also acts to prevent them from grabbing the "
  636.     "memory below 1 megabyte.\r\n\r\n"
  637.     "Fix1MB can either be run from within Windows, or loaded at startup "
  638.     "time.  The latter allows Fix1MB to preserve even more memory below "
  639.     "1 megabyte.  The \"Add FIX1MB to SYSTEM.INI\" button will put Fix1MB in "
  640.     "your SYSTEM.INI if you desire (the FIX1MB.EXE, FX1MBDLL.DLL and "
  641.     "PROCHOOK.DLL files must be in the same directory for this to "
  642.     "work.)\r\n\r\n"
  643.     "Fix1MB was written by Matt Pietrek (CIS: 71774,362), and is from his "
  644.     "May 1995 Questions and Answers column in the Microsoft Systems "
  645.     "Journal.  Please refer to that column for additional information.",
  646.     "Fix1MB Help", MB_OK );
  647. }
  648.  
  649. VOID CenterWindow(HWND hWnd)
  650. {
  651.     RECT rect;
  652.     WORD wWidth, wHeight;
  653.  
  654.     GetWindowRect(hWnd,&rect);
  655.  
  656.     wWidth =GetSystemMetrics(SM_CXSCREEN);
  657.     wHeight=GetSystemMetrics(SM_CYSCREEN);
  658.  
  659.     MoveWindow( hWnd,   (wWidth/2) - ((rect.right -  rect.left)/2),
  660.                         (wHeight/2) - ((rect.bottom - rect.top) /2),
  661.                         rect.right - rect.left,
  662.                         rect.bottom - rect.top,
  663.                         FALSE );
  664. }
  665.