Dale Rogerson
Microsoft Developer Network Technology Group
Created: April 13, 1992
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.
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.
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.
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.
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:
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:
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.
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.
To get around the page-lock problem, follow these steps:
title simhan.asm
;****************************************************************
?WIN = 1
?PLM=1 ; PASCAL calling convention is DEFAULT
?WIN=1 ; Windows calling convention
; 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
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);
}
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:
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.
It is important to keep the following points in mind when deciding to use the large model:
CTheApp NEAR theApp ;
nmake MODEL=L TARGET=W DEBUG=1 OPT="/Gt65500 /Gx"
The above variant of the MFC library has not been extensively tested.
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.