Programming at Large

Dale Rogerson
Microsoft Developer Network Technology Group

Created: April 13, 1992

Abstract

MicrosoftÒ WindowsÔ version 3.1 signals the death of Windows real mode. With the release of Windows version 3.1, only standard and enhanced modes are supported. The end of real mode is the beginning of new programming freedoms, such as writing large-model applications.

This article explains why the large model is valid for protected mode applications and discusses the solutions for single instances and the Windows version 3.0 page-locking bug, limitations of large-model applications.

The Large Memory Model and Protected Mode

For large-model applications running under real mode, the MicrosoftÒ WindowsÔ version 3.0 graphical environment fixed the data segments. Fixed segments cannot move, reducing the ability of Windows to manage memory effectively. Under protected mode, Windows can move fixed data segments. Therefore, protected mode does not suffer the performance degradation that real mode does.

The difference between real mode's inability to move memory and protected mode's ability to move memory lies in the way the two modes address memory. Large-model data pointers default to 32-bit far pointers. In real mode, a far pointer consisted of a segment address and a segment offset, both 16 bits in length. If Windows moved the segment, the segment address would change. Windows had no efficient method for tracking and updating all pointers to a segment.

In protected mode, the processor provides a mechanism, the segment selector, that removes the need to track and update individual pointers. All far pointers in protected mode consist of a 16-bit segment selector and a 16-bit segment offset. The segment selector does not refer directly to a physical address; instead, it indexes into a table. The value in this table is a segment address. When a segment moves, the segment selector does not change, but the value in the table is updated. The maintenance of the segment selector and the selector tables is supported directly by the IntelÒ 80x86 microprocessor.

While the segment selector solves many of the old problems caused by using the large model, it does not resolve two limitations. One limitation requires applications with multiple data segments to have only a single instance. The other limitation is a bug in Windows version 3.0 that caused multiple data segments to be page-locked in memory. These limitations do not affect dynamic-link libraries.

Single Instances

Windows version 3.1 cannot run multiple instances of applications with multiple read-write data segments. If a large-model application has a single read-write data segment, it can run multiple instances. A read-only segment can also be safely shared by multiple instances because the instances cannot change the segment. Most large-model applications, however, have multiple data segments and, therefore, cannot run multiple instances.

While there are several methods for getting only one data segment in a large-model program, one must remember that the application can have only 64 kilobytes (K) of static data, local heap, and stack combined. This is the same as a medium-model program. For this reason, when porting from a flat model 32-bit environment, it is probably best to use a compiler that supports development of 32-bit applications under Windows. These compilers, such as Watcom C 9.0, MetaWare 32-Bit Windows Application Development Kit, or MicroWay NPD C-386, use WINMEM32.DLL to get a full 32-bit flat memory model.

The Reason

In a multiple-instance application, all instances share the same code segments but have unique default data segments. Small- and medium-model applications have only one data segment. Most large-model applications have multiple data segments, but the current Windows kernel cannot resolve fixups to multiple data segments. Consider the following code fragment found in large-model applications that establishes the DS register:

mov ax,_data_01

mov ds,ax

This code is shared by all instances of the application. When the code is loaded, _data_01 can hold only one value. Windows has no way to associate other data segments with a given instance of an application.

The program loader determines if only one instance is allowed after examining the .EXE header. If it discovers more than one data segment, it limits an application to one instance. If an application has less than 64K of data, stack, and local heap, it is possible to collapse the data into one data segment.

Gaining Multiple Instances

To get multiple instances, there must be only one read-write data segment. Under Microsoft C/C++ version 7.0, follow these guidelines to allow for multiple instances:

Do not use /ND to name extra data segments unless the segment is READONLY.

Use the .DEF file to mark extra data segments READONLY.

Do not use __far or FAR to mark data items.

Use /PACKDATA to combine data segments.

Use /Gt65500 /Gx to force all data into the default data segment.

All of the above guidelines apply to Microsoft C version 6.0, except for the last one. Microsoft C version 6.0 and C/C++ version 7.0 will usually generate two read-write data segments. One is for initialized static data (DATA). The other one (FAR_BSS) is for uninitialized static data. The BorlandÒ C compilers default to generating only one data segment. The existence of multiple data segments for a program called SOMEPROG.EXE can be verified by the following command:

c:\> EXEHDR -v someprog.exe | more

Microsoft C version 6.0 does not have the /Gx option to stop the generation of FAR_BSS and to combine initialized and uninitialized data. While there are ways to stop the creation of FAR_BSS with C version 6.0, in most cases it is easier to use C/C++ version 7.0. To eliminate FAR_BSS with C version 6.0:

Initialize all uninitialized static variables, and mark all extern variables as NEAR.

Mark all variables as NEAR, forcing the variables into the DATA segment.

For large programs, these ways of eliminating FAR_BSS can be very time-consuming.

The big problem with all methods for gaining multiple instances is that the application still has only one read-write data segment. It does not have more data space than a medium- or small-model program. A large-model program can have either multiple instances or multiple read-write data segments, but not both.

Windows Version 3.0 Page-Locking Bug

Multiple data segments do not cause any problems in Windows version 3.1, except for requiring an application to run a single instance. In Windows version 3.0, however, there is a bug in the memory manager that page-locks fixed segments of an application. When a segment is page-locked, it becomes a dam in memory because it cannot be moved in physical memory nor paged to disk. This is of great concern for applications compiled with a large model, because large-model applications can have more than one data segment that is fixed. Under Windows version 3.1, fixed segments in a DLL are still page-locked to support interrupt service routines.

Page-Lock Fix

To get around the page-lock problem, follow these steps:

1.Compile your application normally, and generate a map file during linking. Examine the map file and find the names of the FAR_DATA and FAR_BSS segments.

2.Write one or more assembly language routines that will return handles to the FAR_DATA and FAR_BSS segments found in step 1. The following function will return a handle to the data segments named MYSEGMENT and FAR_BSS:

title simhan.asm

;****************************************************************

?WIN = 1

?PLM=1 ; PASCAL calling convention is DEFAULT

?WIN=1 ; Windows calling convention

?386=0 ; Use 386 code?

.MODEL LARGE

include cmacros.inc

sBegin DATA

sEnd DATA

MYSEGMENT SEGMENT MEMORY 'FAR_DATA'

MYSEGMENT ENDS

FAR_BSS SEGMENT MEMORY 'FAR_BSS'

FAR_BSS ENDS

sBegin CODE

assumes CS,CODE

assumes DS,DATA

;**************************************************************

cProc gethandle,<PUBLIC,FAR,PASCAL>

cBegin

mov ax,MYSEGMENT

cEnd gethandle

;**************************************************************

cProc gethandle2,<PUBLIC,FAR,PASCAL>

cBegin

mov ax,FAR_BSS

cEnd gethandle2

sEnd CODE

end

3.Add a call to the following function in your application's InitInstance function after testing the success of your CreateWindow call:

void unlockAll()

{

// This fix is only needed for Windows version 3.0 so check

// version.

if (LOWORD(GetVersion()) == 0x0003)

{

// Un-pagelock MYSEGMENT

unlockExtra(gethandle()) ;

// Un-pagelock FAR_BSS

unlockExtra(gethandle2()) ;

}

}

void unlockExtra(HGLOBAL hExtraSeg)

{

BOOL fRet ;

// Unfix segment in logical memory

GlobalReAlloc(hExtraSeg, 0, GMEM_MODIFY | GMEM_MOVEABLE);

// Only discardable memory can be GlobalPageUnlock'ed

GlobalReAlloc(hExtraSeg, 0, GMEM_MODIFY | GMEM_DISCARDABLE);

// Unfix in physical (protected mode) memory

GlobalPageUnlock(hExtraSeg);

// Reset the lock count to 0 because Windows happens to lock

// it multiple times.

do {

fRet = GlobalUnlock(hExtraSeg);

} while (fRet);

// Modify the flags to moveable

GlobalReAlloc(hExtraSeg, 0, GMEM_MODIFY | GMEM_MOVEABLE);

}

4.Modify your make file to assemble and link your procedures that return handles to your fixed data segments.

5.Recompile your program, and check results using the Microsoft Windows 80386 Debugger (WDEB386.EXE).

Testing the Page-Lock Fix

It is a good idea to test the fix under Windows version 3.0. A program that reports the page-lock status of segments is needed. Microsoft CodeViewÒ for Windows and the 3.0 version of the Windows Heap Walker utility do not report the page-lock status. Also, the 3.1 version of Heap Walker does not run reliably under Windows version 3.0. WDEB386, however, does report the page-lock status of segments.

Finally, you can use WDEB386 to get page-lock information, as follows:

1.Install the debugging version of WIN386.EXE and WIN386.SYM.

2.Run WDEB386.EXE.

3.Issue the DL selector command to dump the local descriptor table (LDT) entry for the selector in which you are interested.

4.Take the Base linear address from the DL command and issue the .ML linear address command.

5.Take the PFT address from the .ML command and issue the .MS PFT address command. This will list the lock count for that page.

For more information on WDEB386.EXE, refer to Chapter 5, "Advanced Debugging: 80386 Debugger," in the Microsoft Windows version 3.1 Software Development Kit (SDK) Programming Tools.

Words of Warning

It is important to keep the following points in mind when deciding to use the large model:

A bug in Microsoft C/C++ version 7.0 causes C++ objects to be placed outside the default data segment, ignoring the /Gx compiler option. To avoid this bug, specify the object as near. For example:

CTheApp NEAR theApp ;

To get multiple instance large-model Microsoft Foundation Class (MFC) applications, a special variant of the large-model libraries must be built. Use the following make line:

nmake MODEL=L TARGET=W DEBUG=1 OPT="/Gt65500 /Gx"

The above variant of the MFC library has not been extensively tested.

Large-model applications run more slowly than medium- and small-model applications.

Basically, a multiple-instance, large-model application differs from a medium-model application only in the size of its default data pointers.

Multiple-instance, large-model applications have only one read-write data segment.

Multiple-instance, large-model applications can have only 64K total of stack, local heap, and static data.

It is easier to build multiple-instance, large-model applications with Microsoft C/C++ version 7.0 and Borland C compilers than with Microsoft C version 6.0.

When porting from a flat-model 32-bit environment, it is probably best to use a compiler that supports development of 32-bit applications under Windows. These compilers, such as Watcom C 9.0, MetaWare 32-Bit Windows Application Development Kit, or MicroWay NPD C-386, use WINMEM32.DLL to get a full 32-bit flat-memory model. Another option is to wait for the release of Win32sÔ, a subset of the Win32Ô Application Programming Interface that lets you develop 32-bit applications for Windows version 3.1.

On a more positive note, large-model DLLs work very well because the equation SS != DS in the large model works exactly as it does in a DLL. Also, a DLL is always a single instance. The Microsoft Foundation Classes recommend using a large model for DLLs.