SoftRAM 95 and PC Magazine's 1MBFORT

Last updated: December 29, 1995
by Andrew Schulman
Senior Editor, O'Reilly & Associates
andrew@ora.com


One piece of code in SoftRAM that actually does do something -- and one reason why some users can load more applications when SoftRAM is running under Windows 3.1 -- has nothing to do with RAM compression. The code fragments the lower 1MB of memory (so-called "conventional memory," or DOS memory), so that Windows 3.1 will not misuse the lower 1MB for fixed allocations; the result is that Windows leaves itself enough room to later on allocate the 256-byte DOS Program Segment Prefix (PSP) that is needed for each Windows application; as a consequence, you can generally start more Windows apps. (That Windows requires some conventional DOS memory for Windows applications -- even Win32 apps under Windows 95 -- is an interesting subject in its own right: see "Windows and PSPs" at the end of this article.)

So far so good. But the code, found in the Windows 3.1 version of SoftRAM's SR-START.EXE, is taken directly from copyrighted code that first appeared in PC Magazine! (This is a general pattern: SoftRAM's SOFTRAM1.386 is directly copied from Microsoft's PAGEFILE.386, SOFTRAM2.386 is directly copied from Microsoft's PAGESWAP.386, and SoftRAM's Windows 95 version of DYNAPAGE.VXD is copied directly from Microsoft's DYNAPAGE VxD.)

Here is the relevant code, disassembled from the Windows 3.1 version of SoftRAM's SR-START.EXE. I added the variable names such as _hBlks:

1.1FCC  sub_0057    proc    far
1.1FCC      enter   7D8h,0
1.1FD0      push    di
1.1FD1      push    si
1.1FD2      xor si,si                   ; _i
1.1FD4      push    si
1.1FD5      push    10272
1.1FD8      call    far ptr GlobalDosAlloc
1.1FDD      mov [_hBlks+2],ax
1.1FE1      mov [_hBlks],dx
1.1FE5  loc_0401:                   ;  xref 1.200C
1.1FE5      mov di,si
1.1FE7      shl di,2
1.1FEA      mov ax,[_hBlks+di]
1.1FEE      or  ax,[_hBlks+2+di]
1.1FF2      jz  short loc_0402
1.1FF4      push    0
1.1FF6      push    10272
1.1FF9      call    far ptr GlobalDosAlloc
1.1FFE      inc si
1.1FFF      mov di,si
1.2001      shl di,2
1.2004      mov [_hBlks+2+di],ax
1.2008      mov [_hBlks+di],dx
1.200C      jmp short loc_0401      ; (1FE5)
1.200E  loc_0402:                   ;  xref 1.1FF2
1.200E      xor di,di
1.2010      mov [_totBlks],si           ; or _nblocks
1.2013      jmp short loc_0405      ; (2052)
1.2015  loc_0403:                   ;  xref 1.2054
1.2015      mov bx,di
1.2017      shl bx,2
1.201A      lea ax,cs:[_hBlks+2]
1.201E      add bx,ax
1.2020      mov ax,[bx]
1.2022      mov dx,[bx+2]
1.2025      mov [_hb],ax
1.2028      mov [_hb-2],dx
1.202B      push    2
1.202D      lea ax,[_hb]    
1.2030      push    ax
1.2031      lea ax,[_bsel]  
1.2034      push    ax
1.2035      call    far ptr _memcpy
1.203A      add sp,6
1.203D      cmp word ptr [_bsel],0
1.2041      je  short loc_0404
1.2043      push    word ptr [_bsel]
1.2046      push    0
1.2048      push    1
1.204A      push    0
1.204C      call    far ptr GlobalReAlloc
1.2051  loc_0404:                   ;  xref 1.2041
1.2051      inc di
1.2052  loc_0405:                   ;  xref 1.2013
1.2052      cmp si,di
1.2054      jg  loc_0403
1.2056      mov ax,1
1.2059      pop si
1.205A      pop di
1.205B      leave
1.205C      retf
    sub_0057    endp
Here is a C version of this code, prepared by someone (not me) who had not yet seen the corresponding PC Magazine code:
BOOL AllocDosMem()
{
   unsigned nBlockCount;      // [bp-2]
   unsigned selector;         // [bp-4]
   DWORD temp;                // [bp-6]/[bp-8]

   DWORD DosMemBlocks[500];
   int i = 0;                 // in SI

   DosMemBlocks[0] = GlobalDosAlloc( 10272 ); // Why 10272?  Why ask Why?

   while (DosMemBlocks[i])
      DosMemBlocks[++i] = GlobalDosAlloc( 10272 );

   nBlockCount = i;

   for ( i=0; i<nBlockCount; i++ )
   {
      temp = DosMemBlocks[i];
      memcpy( &selector, &temp, 2 );

      if (selector)
         GlobalReAlloc( selector, 1, 0 );
   }
   return 1;
}
Now, here is the original code, from John McSorley's article, "1MBFort Protects Low Memory," PC Magazine, March 28, 1995 (see 1mbfort.zip):
/****************************************************************************
    1MBFort Version 1.0 Copyright (c) 1995 John McSorley.
    First Published in PC Magazine, March 28, 1995, US Edition

    PROGRAM: 1MBFort.c

    PURPOSE: 1MBFort low order memory fragmenter helps prevent
             "Insufficient memory to start application" errors.

// ......

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

// ......

void HandleWMCreate(hWnd, message, wParam, lParam)
HWND hWnd;                      /* window handle                 */
UINT message;                   /* type of message               */
WPARAM wParam;                  /* additional information        */
LPARAM lParam;                  /* additional information        */
{

    int i;
    int totBlks;
    DWORD hBlks[500];
    DWORD hb;
    unsigned int hbret;
    unsigned int bsel;
    unsigned int isize;
    unsigned int nblocks;
    unsigned int iret;
    char sIniFile[255];
    
    char appTitle[80];

    /* Get users Windows Directory */
    i=GetWindowsDirectory(sIniFile,254);

    /* Add INI filename */
    strcat(sIniFile,"\\1MBFort.INI");

    /* Get blocksize or default to -1 */
    isize=GetPrivateProfileInt("1MBFort","BLOCKSIZE",-1 ,sIniFile);

    if (isize==-1) {

        /* No INI setting so create INI file with default value 10272 */
        iret=WritePrivateProfileString("1MBFort", "INSTRUCTIONS", "Valid BlockSize values 5000 to 20000 no commas.", sIniFile);
        iret=WritePrivateProfileString("1MBFort", "BLOCKSIZE", "10272", sIniFile);

        /* Use default blocksize */
        isize=10272;
    }
    else
    { 
        /* Range check user supplied block size */
        if (isize == 0) isize=10272;
        if (isize < 5000) isize=5000;
        if (isize > 20000) isize=20000;
    }           

    /* Allocate as many memory blocks as possible */
    i=0;
    hBlks[i]=GlobalDosAlloc(isize);            
    while (hBlks[i]!=0) {
        i++;
        hBlks[i]=GlobalDosAlloc(isize);
    }  

    totBlks=i;

    /* save number of blocks allocated for later display */
    nblocks=totBlks;

    /* display resulting size/blocks */
    sprintf(appTitle,"1MBFort %d/%d",isize - 32,nblocks);

    /* set text for task manager display */
    SetWindowText(hWnd, appTitle);

    /* shrink blocks so that low order memory is fragmented */
    for (i=0;i<=totBlks;i++) {
        hb=hBlks[i];
        memcpy(&bsel,&hb,2);
        if (bsel!=0) {
            /* reduce blocks to as small as possible */
            /* request 1 byte but will get 32 bytes due to overhead */
            hbret=GlobalReAlloc(bsel,1,0);
        }
    }
}
In SR-START, Syncronys has merely dropped the check that the PC Magazine code does for .INI file settings, and the code that in 1MBFORT sets the window title. The rest is the same, down to the rather silly use of the C memcpy( ) function to copy two bytes!

While plagiarism is the sincerest form of flattery, the fact is that the PC Magazine code is, with all due respect, actually not all that good -- better solutions to the Windows 3.1 conventional-memory problem have been published in Microsoft Systems Journal (Matt Pietrek's BELOW1MB and FIX1MB programs). FIX1MB is available online. Here is the help text:

Fix1MB is a tool to help with the dreaded "out of memory" errors in Windows that cause new programs to be unable to start. This particular problem is caused by DLLs that inadvertantly suck up all the memory below 1 megabyte in the Windows address space. Windows needs a certain amount of memory below 1 megabyte to start a new task.
Fix1MB not only shows you which DLLs and programs are using this precious memory, it also acts to prevent them from grabbing the memory below 1 megabyte.
Fix1MB can either be run from within Windows, or loaded at startup time. The latter allows Fix1MB to preserve even more memory below 1 megabyte. The "Add FIX1MB to SYSTEM.INI" button will put Fix1MB in your SYSTEM.INI if you desire (the FIX1MB.EXE, FX1MBDLL.DLL and PROCHOOK.DLL files must be in the same directory for this to work.)
Fix1MB was written by Matt Pietrek (CIS: 71774,362), and is from his May 1995 Questions and Answers column in the Microsoft Systems Journal. Please refer to that column for additional information.
(Also see Matt Pietrek's new book, Windows 95 System Programming Secrets.)

Windows and PSPs

Andrew Schulman et al., Undocumented DOS, 2nd edition (1993), pp. 151-155: "Windows and the PSP".
dosmem.c
win32psp.c
(Back to top)
NOTE: I have done some paid consulting for Connectix, whose RAM Doubler product competes directly with SoftRAM.

SoftRAM 95: Does it do Anything?

The O'Reilly Windows Center

Unauthorized Windows 95 Update