PART III
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Chapter 7  Memory Management
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Multitasking without memory management is like having a party in a closet:
You may be able to accommodate some of the earlier arrivals, but once
everybody starts mingling, some toes are going to get smashed.

Memory management has always been one of the most remarkable aspects of
Windows. Even Windows 1 included a sophisticated memory management scheme
that implemented in software some of the memory management features you
might expect to find in a protected-mode operating system. Here are some
examples of Windows 1 memory management:

  þ   When Windows runs multiple instances of the same program, it uses the
      same code segments and the same resources for each instance.
      (Resources include icons, cursors, menu templates, and dialog box
      templates, all of which are covered in the next three chapters.) In
      most cases, Windows requires only that data segments be unique for
      each instance of a program.

  þ   Much of the memory allocated within Windows is moveable, including (in
      most cases) the memory allocated for a program's code segments, data
      segments, and resources.

  þ   Code segments and resources are often "demand-loaded": Windows does
      not load them into memory until a program specifically needs them.

  þ   Code segments and resources are often discardable: When Windows needs
      to free some memory, it discards the segments from memory and later
      reloads them from the program's .EXE file as the program requires.

These memory management features allowed Windows 1 to run several large
programs in a memory space that might not be large enough for even one of
the programs under a less ambitious memory management scheme. The problem is
that this memory management scheme requires that Windows often reload code
segments and resources from the hard disk, hurting program performance. For
this reason, support of the Expanded Memory Specification (EMS) 4.0 was
added to Windows 2, and protected-mode support was added to Windows 3.

Windows 3 can run in three distinct modes:

  þ   On a machine based around the Intel 8086 processor (or an 80286 or
      80386 processor with less than 1 MB of memory), Windows 3 runs in
      "real mode." This is essentially compatible with the memory
      configuration of Windows 2.1. Windows and its applications occupy an
      upper area of the 640 KB of conventional memory above MS-DOS and any
      device drivers and RAM-resident programs that may be loaded.

      In this mode, Windows can take advantage of any expanded memory under
      the Lotus-Intel-Microsoft Expanded Memory Specification 4.0 (LIM EMS
      4.0). This configuration requires an EMS memory board and an EMS 4
      device driver.

  þ   On a machine based around the Intel 80286 processor with at least 1 MB
      of memory (or an 80386 processor with less than 2 MB of memory),
      Windows 3 runs in "standard mode." This is 286-compatible protected
      mode. Windows can use up to 16 MB of conventional memory and extended
      memory.

  þ   On a machine based around the Intel 80386 processor with at least 2 MB
      of memory, Windows 3 runs in "386 enhanced mode." This is essentially
      standard mode with two additional features: Windows uses the paging
      registers of the 386 processor to implement virtual memory. The 386
      pages are 4 KB in length. Windows can swap pages to disk and reload
      them when necessary. (This is the only form of virtual memory
      supported for Windows applications.) The page swapping is something
      you normally don't have to think about when coding for Windows. The
      second feature of 386 enhanced mode uses the Virtual-86 mode of the
      386 processor to support multiple virtual DOS machines. This does not
      impact Windows programming.

      You can override the default configuration by running Windows from the
      command line with a /R (real mode), /2 (standard mode), or /3 (386
      enhanced mode) parameter. A Windows program can obtain information
      about the mode in which it is running by calling GetWinFlags.

      Much of this chapter discusses the real mode memory configuration
      because this is the least common denominator of all the modes in which
      Windows can run. (Another reason for this is that the techniques
      Windows uses to manage memory in real mode are quite interesting!) If
      you initially feel a little queasy when thinking about Windows moving
      your program around in memory in real mode, that's good. It means that
      you're already aware that this is not an easy feat. You must keep this
      fact in mind in order to write programs that run without problems. You
      must cooperate with Windows' memory management. That's what we'll look
      at in this chapter.

SEGMENTED MEMORY, INTEL STYLE

Windows organizes memory in "segments"--so before we proceed, let's quickly
review the segmented memory architecture of the Intel 8086 family of
microprocessors. This family includes the 8088 found in the PC and PC/XT,
the 8086 and the 186 found in some compatibles, the 286 found in the PC/AT,
and the 386 and 486.

When these processors run in real mode, a memory address consists of two
parts, a 16-bit segment address and a 16-bit offset address. The 16-bit
segment address is shifted 4 bits to the left and added to the offset
address. The resultant 20-bit physical address can access 1 MB of data:

16-bit offset address        xxxxxxxxxxxxxxxx

16-bit segment address + xxxxxxxxxxxxxxxx0000

20-bit physical address  xxxxxxxxxxxxxxxxxxxx

Four internal registers of the microprocessor hold segment addresses. These
segment registers are called CS (code segment), DS (data segment), SS (stack
segment), and ES (extra segment). The 386 and 486 have two additional
segment registers: FS and GS.

Software for the 8086 family runs most efficiently when the segment
addresses are held constant and all addressing is done by varying the offset
addresses. The offset addresses generated by the microprocessor include the
instruction pointer (IP), which accesses code in combination with the CS
register; the stack pointer (SP) and base pointer (BP), which access the
stack in combination with the SS register; and the BX (base), SI (source
index), and DI (destination index) registers, which access data, most often
in combination with the DS or ES register. An address that uses only the
offset address with an implied segment address (the current segment address)
is called a "near pointer" or sometimes a "short pointer." An address that
uses both the segment and offset addresses is called a "far pointer" or a
"long pointer."

For any particular segment address, the offset address can vary within a
64-KB range, from 0000H through FFFFH. A block of memory that is based on a
particular segment address is called (appropriately enough) a "segment."
People used to think of segments as 64-KB blocks of memory, but this
definition is becoming less common. Now we say that segments can be any size
up to 64 KB. Sometimes the size of a segment (or the size of an area of
memory larger than 64 KB) is given in terms of "paragraphs." A paragraph is
16 bytes. Memory allocated for a segment is often a multiple of 16 bytes,
because a segment must begin on a 16-byte boundary of physical memory. (When
the segment register is shifted left 4 bits, the bottom 4 bits are 0.)

When the 286, 386, and 486 processors run in 286-compatible protected mode,
the segment does not refer to a physical memory address. Instead, the
segment is an offset into a "descriptor table" that provides a 24-bit base
address in physical memory. The offset address is then added to this base
address to generate a 24-bit physical address that can access up to 16
megabytes of memory.

The use of segments is central to Windows' memory organization. The entire
memory space controlled by Windows is divided into segments of various
lengths. Some of these segments contain code, and others contain data.


MEMORY ORGANIZATION IN WINDOWS

The entire memory area that Windows controls is called "global memory" or
the "global heap." This area begins at the location where MS-DOS first loads
Windows into memory and ends at the top of available memory, which most
often is the top of physical memory. (In C programming, the word global
usually refers to variables or functions in one source code file that can be
referenced from functions in another source code file of the same program.
That is not the meaning of global here. In this discussion of Windows'
memory organization, the word global instead means "everything.") Every
block of memory allocated from the global heap is a segment. Global memory
not currently allocated is called "free memory."

A Windows program can have one or more code segments and one or more data
segments. (The example programs shown in this book have only one of each.)
When Windows loads a program into memory, it allocates at least one segment
from the global heap for code and one segment for data. When the program
begins to execute, the microprocessor's CS register is set to the segment
address of the code segment that contains the entry point of the program.
The DS and SS registers are set to the segment address of the program's
automatic, or default, data segment, which is the data segment that contains
the stack. (The combination of the data and the stack into one segment
referenced by both DS and SS is normal for C compilers. DS is used to
reference data declared as static; SS is used to reference data on the
stack, which includes local nonstatic data and arguments passed to
functions. This approach allows near pointers to be used for function
parameters. The function doesn't have to know whether it's dealing with
static data or stack data. Problems related to unequal DS and SS segment
registers are discussed in Chapter 19, "Dynamic Link Libraries.")

When loading a program, Windows also allocates two other segments from the
global heap for program overhead. One of these segments contains the header
portion of the program's .EXE file. This segment is used for all instances
of a program, so it is allocated only for the first instance. The other
segment contains information unique to each instance, such as the program's
command-line string and the program's current subdirectory. When a program
loads resources (such as icons, cursors, or menu templates) into memory,
each resource gets its own segment in the global heap. A program may itself
also allocate some memory from the global heap.

If a program has only one code segment, then any calls it makes to functions
within the program are compiled as near calls. The CS code segment register
remains the same. However, when a program calls a Windows function, the
Windows function is in a different code segment. This situation requires
that the program generate a far call, which is the reason that all Windows
functions (and all functions within your program that are called by Windows)
must be declared as far.

A Windows program that has one data segment can use near pointers to access
memory within that data segment. However, when a Windows program passes a
pointer to a Windows function, the pointer must be a far (or long) pointer;
otherwise, the code that contains the Windows function will use its own data
segment. The far pointer is required for the Windows function to access the
data within your program's data segment.

Fixed and Moveable Segments

Every segment in Windows' total memory space is marked with certain
attributes that tell Windows how to manage the segment. First and foremost,
segments are marked as either "fixed" or "moveable." Windows can move
moveable segments in memory if necessary to make room for other memory
allocations. When Windows moves a segment in memory, all existing near
pointers to that segment continue to be valid, because near pointers
reference an offset from the beginning of a segment. However, far pointers
become invalid when the segment they reference is moved. A fixed segment
cannot be moved in memory. Segments must be marked as fixed if Windows is
incapable of modifying an existing far pointer to the segment.

In protected mode, all program segments are moveable because Windows can
move the segment without changing the segment address. Windows need only
change the physical base address in the descriptor table.

Most segments--including the segments allocated for your program's code and
data--are moveable, but some exceptions exist. Whenever Windows gives your
program a far pointer, the pointer references a fixed data segment. For
instance, when your Windows program begins executing, Windows passes a
parameter to WinMain that we call  lpszCmdLine. This is a far pointer to an
area of memory that contains a command-line argument for the program. I
mentioned above that this command-line string is stored in a program
overhead segment that Windows creates for each instance of a program. This
program overhead segment must be fixed. If Windows moves it, the
command-line pointer passed to your program becomes invalid.

Here's one way Windows deals with moveable segments: You've seen how Windows
and your programs use numbers called "handles." In many cases, the handles
are really near pointers. Windows maintains a segment called BURGERMASTER
(named after a favorite restaurant of the early Windows developers) that
contains a master handle-to-memory table. The handle points to a small area
of memory within BURGERMASTER that contains the segment address of the item
that the handle references. When Windows moves the segment that contains the
item, it can adjust the address in BURGERMASTER without invalidating the
handle. BURGERMASTER is itself a moveable segment.

All non-Windows MS-DOS programs are assigned fixed segments when they run
under Windows. Windows cannot determine how these programs reference memory,
so Windows has no choice but to make them fixed. However, you should try
very hard to ensure that the code and data segments of your Windows programs
(as well as any additional segments your programs allocate) are moveable
segments. Fixed segments stand like brick walls in memory space and clog up
Windows' memory management. Users quickly learn which programs seem to use
little memory (because they use moveable segments) and which seem to use a
lot of memory (because they use fixed segments). Users have a name for
programs that use a lot of memory. They say, "This program is a real pig."
Your goal should be to write programs that are not pigs.


Discardable Memory

Moveable segments can also be marked as discardable. This means that when
Windows needs additional memory space, it can free up the area occupied by
the segment. Windows uses a "least recently used" (LRU) algorithm to
determine which segments to discard when attempting to free up memory.

Discardable segments are almost always those that do not change after they
are loaded. Code segments of Windows programs are discardable because (in
most cases) programs do not modify their code segments. Indeed, code
segments cannot be modified in protected mode. When Windows discards a code
segment, it can later reload the code segment by accessing the .EXE file.
Most of Windows' own code in the USER and GDI modules and various driver
libraries is also discardable. (The KERNEL module is fixed. This is the
module responsible for Windows' memory management.) Resources--such as
dialog box templates, cursors, and icons--also are often marked as
discardable. Again, Windows can simply reload the resource into memory by
accessing the .EXE file that contains the resource.

Sometimes you'll see that a disk is being accessed when you move the mouse
from the client area of one program to the client area of another. Why is
this? Windows has to send mouse movement messages to the second application.
If the program's code to process this message is not currently in memory,
Windows must reload it from the disk file. If you have several large Windows
programs loaded simultaneously, you may witness some "thrashing" (an
inordinate amount of disk activity) as you move from program to program.
Windows is reloading previously discarded segments.

Discardable segments must also be moveable segments, because discardable
segments can be reloaded in a different area of memory than the area they
occupied earlier. However, moveable segments are not always discardable
segments. This is usually the case with data segments. Windows cannot
discard a program's automatic data segment, because the segment always
contains read-write data and the stack.

Many people are under the impression that Windows also swaps memory--that
is, that when Windows needs additional memory, it saves some portion of
memory on the disk and reloads it at a later time. For Windows programs,
this is true only when Windows is running in 386 enhanced mode. Windows
swaps memory to disk only when running non-Windows programs (otherwise known
as "old applications"). When Windows loads these old applications from disk
back into memory, it must load them at the same memory address they occupied
previously.


The Global Memory Layout

As I noted before, global memory ranges from the spot where MS-DOS first
loads Windows to the top of available memory. At the bottom of global memory
(the area with the lowest memory address), Windows allocates fixed segments.
Fixed segments are allocated from the bottom up. At the top of global
memory, Windows allocates discardable code segments. (Remember that
discardable segments are also moveable segments.) Discardable segments are
allocated from the top down.

Between fixed segments and discardable segments, Windows allocates moveable
segments and nondiscardable data segments. The largest block of free memory
is usually located below the discardable segments. The memory layout looks
something like that shown in Figure 7-1 on the following page, with arrows
indicating the direction in which the areas expand. When Windows needs to
allocate a fixed segment, it starts searching from the bottom up for a
sufficiently large free block below the area of moveable segments. If it
can't find one, it starts moving moveable segments up in memory to make
room. If that doesn't work, Windows begins discarding discardable segments,
based on an LRU (least recently used) algorithm, again moving moveable
segments. To allocate moveable but nondiscardable segments, Windows searches
the free memory area below the discardable segments. If it doesn't find
enough room, Windows moves other moveable segments down in memory and
eventually starts discarding discardable segments.

FIGURE 7-1 ILLUSTRATION WITH LABELS AND ARTWORK

  (Figure 7-1. may be found in the printed book.)

Within the area of discardable memory, Windows maintains a space large
enough to accommodate the largest code segment of every currently running
program. Windows never runs out of memory space when reloading code
segments. However, Windows can run out of memory space when a program
attempts to allocate global memory or load a resource. Sometimes it can be a
little tricky to deal with this problem--you may need a text string or icon
not currently in memory to display an error message. If your program needs
to report that it is low on memory, you can use a message box. Windows keeps
in memory all the code necessary to create a message box. You'll want to use
the MB_SYSTEMMODAL flag to prevent the user from switching to another
application. MB_ICONHAND (which is supposed to accompany messages about
severe problems) is also always in memory. The text message in the message
box should either be in your default data segment or be a string resource
that has previously been copied into your data segment.


Local Memory

Every Windows program has at least one data segment called the default, or
automatic, data segment. A program's DS and SS segment registers both point
to this segment. In contrast to the "global memory" that Windows manages,
this automatic data segment is called your program's "local memory." Within
Windows' global memory organization, your program's automatic data segment
is most often a moveable but nondiscardable segment. The segment is called
DGROUP.

In both regular MS-DOS C programs and Windows programs, the memory within
DGROUP is organized into four areas, as shown in Figure 7-2. These four
areas are described below:

  þ   Initialized static data--This area contains initialized variables
      defined outside of functions, initialized static variables within
      functions, and explicit strings and floating-point numbers.

  þ   Uninitialized static data--This area has uninitialized variables that
      are defined outside of functions and uninitialized variables defined
      as static

      within functions. In accordance with C standards, all uninitialized
      static variables are initialized to 0 when the data segment is created
      in memory.

  þ   Stack--This area is used for "automatic" data items defined within
      functions (variables not defined as static), for data passed to
      functions, and for return addresses during function calls.

  þ   Local heap--This is free memory available for dynamic allocation by
      the program.

        (Figure 7-2. may be found in the printed book.)

      The module definition (.DEF) file specifies your program's stack and
      local heap size:

      HEAPSIZE  1024
      STACKSIZE 8192

      In a regular C program, you can allocate memory from the local heap
      using the malloc and calloc functions. In Windows programs, you can
      also allocate memory from the local heap, but you'll want to use the
      Windows memory allocation functions rather than the C functions. When
      you use Windows functions to allocate local memory, Windows organizes
      the local heap just like global memory, as shown in Figure 7-3.
      Although the stack is fixed in size, Windows can dynamically expand
      the local heap if you attempt to allocate more local memory than is
      specified in your module definition file. Windows can even move your
      data segment if that is necessary to expand the local heap.

        (Figure 7-3. may be found in the printed book.)



CODE AND DATA SEGMENTS

All the Windows programs shown so far have one code segment and one data
segment. Windows programs can also have multiple code and data segments. For
larger programs, using multiple code segments is highly recommended because
it helps relieve memory congestion in Windows. Using multiple data segments,
on the other hand, is a real problem. Let's take a look at this interesting
subject.

Memory Models: Small, Medium, Compact, Large, and Huge

When we speak about a program having one code or data segment or multiple
code or data segments, we're referring to "memory models." Microsoft C 6
supports five memory models that you can use for Windows programs:

Model    Code Segments  Data Segments
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Small    1              1
Medium   Multiple       1
Compact  1              Multiple
Large    Multiple       Multiple
Huge     Multiple       Multiple

Command-line switches of the compiler determine the memory model you use.
The small memory model is the default, but programs that have more than 64
KB of code must contain two or more code segments, and programs that have
more than 64 KB of data must contain two or more data segments. In
medium-model and large-model programs, all functions (unless explicitly
declared as near) are far, and the compiler generates far calls for them. In
compact-model and large-model programs, all data references use far
pointers.

The small, compact, medium, and large models all have their own C libraries.
The medium-model and large-model libraries contain functions that assume
they have been called from another segment. The functions in the
compact-model and large-model libraries always assume they have been passed
long pointers to data. The huge model is essentially the same as the large
model, except that individual data items may be greater than 64 KB. The huge
model has limited library support.

Most small Windows programs are compiled as small-model programs, with one
code segment and one data segment. Strictly speaking, however, Windows
programs are really "mixed-model" programs, because they extensively use the
near and far keywords. Windows programs make far calls to Windows functions,
and Windows makes far calls to functions within a program, such as window
procedures or call-back functions. All data pointers passed between a
program and Windows (with one oddball exception--the GetInstanceData
function) are far pointers.

Windows programs can also be compiled as medium-model programs. You can try
this out on any of the programs shown so far. You need to make two changes
to the make file:

  þ   Add the -AM switch to the compile step. This compiles for the medium
      model.

  þ   Change SLIBCEW to MLIBCEW in the link step. This is the library that
      contains the Windows-specific C run time library functions.

Delete the .OBJ file and run NMAKE. You'll find that the .EXE file is
somewhat larger than before because all the functions within your
program--not only the window procedure and call-back functions--now require
far calls.


Multiple Code Segments

The medium model doesn't make sense for a small program, and it comes into
play only when you have more than one source code module. But then it starts
making a lot of sense. In the medium model, each source code module becomes
a different code segment. Each of these code segments can be moveable and
discardable. The amount of space required to fit your code into memory is
the size of the largest code segment.

For instance, the approximately 160 KB of code in Windows WRITE is
distributed among 78 separate moveable and discardable code segments. The
largest code segment in WRITE is about 8 KB. Thus, when memory is limited,
WRITE can continue to run with only an 8-KB code space. As program logic
within WRITE moves from segment to segment, the code segment currently in
memory can be discarded and a new one can be loaded in.

If you like, you can think of the medium model as a simplified overlay
manager: You split your program into multiple source code modules and
compile for the medium model. Windows does the rest. In order to work
efficiently, the medium-model approach requires some planning. The functions
in each of your source modules should be organized in functional groups.
When your program is dealing with such routine matters as processing mouse
messages, for example, it should not have to load several code segments to
get from the top of the window procedure to the bottom.

While using the medium model is certainly the easiest approach to take with
a large program, it is not the most efficient. As you'll see shortly, when
you run the Microsoft C Compiler with the -Gw switch (the Windows switch),
the compiler adds some extra prolog code to all far functions. Only those
functions actually called by Windows (such as window procedures or call-back
functions) need this prolog code, however. When all the functions in your
program are far functions (as they are in the medium model), this extra code
can add up to a significant waste of space.

There are several solutions to this problem. The first is fairly simple.
When compiling a module that does not include any functions that are called
from Windows (such as window procedures, dialog procedures, or call-back
functions), compile with the -GW switch rather than the -Gw switch. This
reduces the prolog code on far functions.

Another approach to reduce wasted space in a medium-model program is to
define all functions used only within a module as near functions. Another
solution is to use a mixed model with a small model as a base. Let's assume
you have five source code modules:

  þ   Module 1 contains WinMain, the message loop, your window procedure,
      and most message processing.

  þ   Module 2 contains one function that has all initialization code. This
      function is called by WinMain from Module 1 before entering the
      message loop.

  þ   Module 3 contains one function called from your window procedure and
      several other functions called only within this module.

  þ   Module 4 also contains a function called from your window procedure
      and several other functions called only within this module. This
      module also calls a function in Module 5.

  þ   Module 5 contains a function called from Module 4 and several other
      functions called only within this module.

You can organize this program into four segments, with Modules 4 and 5 in a
single segment. Within each module, you explicitly define as far any
function called from outside the segment. This involves one function each in
Modules 2, 3, and 4. In the modules that call these functions, the functions
must be declared as far using the function name prefaced by FAR near the top
of the program.

You compile each module for the small model, except that you assign one code
segment name to Module 2, another to Module 3, and yet another to Modules 4
and 5. These names are assigned by including the -NT ("name the text
segment") switch when compiling. Each module with the same code segment name
is in the same segment. Now you have far functions only where you need
them--for functions that are called from another segment.

As you can see, this mixed-model approach is more of a headache than the
medium-model approach. It requires that you figure out which functions must
be declared far and which can be near. It also has an additional problem:
You can call normal C library routines from only one segment--the segment
that gets the default segment name _TEXT when you compile without the -NT
switch.


What About the Compact and Large Models?

Windows programmers who require more than 64 KB of data in their programs
might be feeling a little nervous at this point. They have a right to be,
because the compact and large models are not recommended for Windows
programs. This doesn't mean they can't be used, however. The Windows
Software Development Kit allows you to install compact-model and large-model
Windows libraries, so obviously these models are legal. However,
compact-model and large-model programs are subject to a very strict penalty:
The data segments must be fixed in memory. They cannot be flagged as
moveable.

Why this restriction? There are various reasons; here's an easy example that
illustrates one of them. Suppose that somewhere within your program you
define a static pointer that looks like this:

char *pointer ;

Because you're compiling for a compact model or large model, this is a far
pointer. During your program's execution, you assign a value to that
pointer. If the pointer references a moveable data segment, then the value
of the pointer must be adjusted when Windows moves the data segment it
references. But the compiler and linker will not even generate a relocation
address for that pointer because it's not initialized.

Program developers who cry "but I need the large model" should consider the
alternatives made evident by existing Windows applications. Take a look at
some large Windows programs such as Microsoft Excel and Aldus PageMaker.
These programs use many code segments but only one data segment. And if
these programs don't need the large model, then you probably don't either.
If your program needs more than 64 KB of data, you have alternatives to
using the compact model or large model. Here they are:

  þ   If your program uses large blocks of uninitialized data, do not define
      these data as variables within the program. Instead, use the Windows
      GlobalAlloc function (discussed later in this chapter) to allocate a
      block of moveable memory outside your program.

  þ   If your program uses large blocks of initialized read-only data (the
      most obvious example is "help" text), make the data a discardable
      "user-defined resource" (discussed in Chapter 8) to be loaded from the
      disk only when necessary. This keeps the data out of memory when not
      needed.

  þ   If your program uses large blocks of initialized read-write data, put
      the initialized data in a discardable "user-defined resource" and
      transfer the data to a global memory block allocated with GlobalAlloc.


Avoiding Movement Problems

We have been creating small Windows programs for several chapters now and
have not run into problems when Windows has moved the code and data segments
in memory. Here are some general rules for continuing to avoid problems:

  þ   Use the small model or medium model.

  þ   Don't declare any variables as far. For instance, don't do something
      like this:

      int far array [10][1000] ;    // Bad!!!!!

      This code creates a second data segment in your program. Unless you
      mark this segment as fixed, Windows does not properly handle
      references to this array.

  þ   Don't store any far pointers to data except those that Windows gives
      you. The lpszCmdLine parameter passed to WinMain is OK. The far
      addresses returned from GlobalLock (discussed later in this chapter)
      and LockResource (discussed in Chapter 8) point to fixed data until
      you specifically unlock the data, so these are legitimate also. Some
      window messages (WM_CREATE, for instance) have lParam values that are
      far pointers. Use these pointers only for the duration of the message.

  þ   When you call Windows functions, you must often pass far pointers to
      data that are within your data segment. But don't declare far pointers
      and assign them far addresses to local data items and then later use
      the far pointers when calling Windows functions. Instead, cast the
      near pointers into far pointers when you call the functions, or let
      the compiler do this casting for you.

  þ   Don't store or use any far pointers to code, except for pointers to
      functions that are specifically declared as far or far pointers
      returned from MakeProcInstance. (Pointers to functions in medium-model
      programs are also OK.)

  þ   When you need to give Windows a far pointer to a function for a
      call-back function (as discussed in Chapter 5), a window subclassing
      function (as discussed in Chapter 6), or a dialog box function (as
      discussed in Chapter 10), obtain the pointer from MakeProcInstance.
      The function must be declared as FAR PASCAL and included in the
      EXPORTS section of your module definition file.

Although these rules are numerous and important, don't let them drive you to
paranoia. Keep in mind that Windows is nonpreemptive. Windows will not pull
the rug out from under you by interrupting your code. Windows will move or
discard your code segments only when you make a Windows call. Windows will
not move your data segment except when you make a few select Windows calls.
If you have a long stretch of code between two Windows calls, you can be as
carefree as you like in that code without worrying about sudden movements.


Program Segment Attributes

I have been talking about program segments that are fixed, moveable, and
discardable. These characteristics are called "segment attributes." To tell
Windows the attributes you want in your program segments, you use the CODE,
DATA, and SEGMENTS statements in the module definition file. During linking,
LINK encodes the attributes for each program segment into a 16-bit flag and
stores this flag in the segment table of the .EXE file. Windows has access
to these segment attributes when loading your code and data segments. The
sample programs presented so far contain these two lines in their module
definition files:

CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE

We have not yet encountered the SEGMENTS statement.

The CODE statement applies to all code segments within your program that are
not explicitly listed in the SEGMENTS statement. Similarly, the DATA
statement applies to all data segments except those listed in the SEGMENTS
statement.

There are four standard attributes: "load," "memory," "discardable," and
"instance."

The "load" attribute can be either PRELOAD or LOADONCALL. This attribute
tells Windows when to load the segment. The default is PRELOAD, which means
that the code segment (or segments) should be loaded at the time the program
begins execution. A LOADONCALL segment will be loaded into memory the first
time it is needed. For a program with a single code module, it really
doesn't matter which attribute you use. For programs with multiple code
modules, you should use PRELOAD for segments that are required when the
program first starts up (for instance, those that do initialization from
WinMain) and LOADONCALL for the other code segments. To specify different
attributes for multiple code segments, you must use the SEGMENTS statement,
discussed later.

The "memory" attribute can be either FIXED or MOVEABLE. The default is
FIXED. If you're writing normal Windows programs, you should have no problem
specifying MOVEABLE. (Device drivers that must process hardware interrupts
are another matter. These usually require one fixed code segment.)

The "discardable" attribute is indicated by the DISCARDABLE keyword. This
indicates that Windows can discard the segment from memory and reload it
from the .EXE file when necessary. Code segments in normal Windows programs
should be flagged as DISCARDABLE. The default data segment cannot be flagged
as DISCARDABLE.

The "instance" attribute is relevant only for data segments. It should be
set to MULTIPLE for Windows programs, indicating that each instance gets its
own data segments. The NONE and INSTANCE options are for Windows dynamic
link libraries (discussed in Chapter 19), because Windows libraries can have
only one instance. If the Windows library has a data segment, INSTANCE (or
SINGLE) is used. If not, NONE is used.

The SEGMENTS statement lets you assign different segment attributes to other
code and data segments within your program. The general form of the SEGMENTS
statement is:

SEGMENTS segment-name [CLASS 'class-name'] [allocate] [attributes]

You'll probably use this statement most often if you create a medium-model
program or a small-model program with multiple code segments. For a
medium-model program, the C library functions and start-up code are in a
code segment named _TEXT. Each source code module is assigned a different
code segment name that by default is the filename of the source code file
followed by _TEXT. For instance, if you have three source code modules
called PROGRAM.C, MODULE1.C, and MODULE2.C, the PROGRAM.EXE file has four
code segments: _TEXT, PROGRAM_TEXT, MODULE1_TEXT, and MODULE2_TEXT.

If you do not include a SEGMENTS statement, all four code modules take on
the attributes from the CODE statements. If you do include a SEGMENTS
statement, however, your results might look something like this:

CODE      PRELOAD MOVEABLE DISCARDABLE
DATA      PRELOAD MOVEABLE MULTIPLE
SEGMENTS  MODULE1_TEXT LOADONCALL MOVEABLE DISCARDABLE
          MODULE2_TEXT LOADONCALL MOVEABLE DISCARDABLE

Now the _TEXT and PROGRAM_TEXT segments are PRELOAD and are loaded when the
program first begins execution. MODULE1_TEXT and MODULE2_TEXT are
LOADONCALL. Windows loads them only when they are needed.

If you use the SEGMENTS statement for data segments, you must include:

CLASS 'DATA'

By default, the class is CODE. You can also add a minimum allocation size to
increase the size of the data segment when it's loaded into memory.



HOW WINDOWS MOVES AND

RELOADS PROGRAM SEGMENTS

In this section, I want to give you some insights into how Windows is able
to move your program's code and data segments and (in the case of code
segments) discard and later reload the segments in real mode. If you'd
rather not know (or if the assembly language in the pages ahead looks like
Greek to you), that's fine. But you're likely to see some of this code when
debugging Windows programs, so it's better that you see it here first and
understand what's going on.

I'll be discussing the most general recommended case--a program that
contains multiple moveable and discardable code segments and a single
moveable data segment. Everything works a little differently (and more
simply) when the segments are fixed and nondiscardable.

When a program has a single data segment, neither the code nor the data have
to contain any explicit references to the segment address of the data. When
a Windows program begins running, Windows has already set the DS and SS
registers to the data segment. All pointers are near pointers. When the
program needs the data segment address (for instance, for casting near
pointers into far pointers when calling Windows functions), it simply uses
the value of DS. (In contrast to this, when you run a regular .EXE program
outside Windows, MS-DOS does not set DS to the program's data segment on
entry to the program. The program must begin by executing code like this:

MOV AX, DGROUP
MOV DS, AX

You'll never see code like this in a compiled Windows program.) If you cast
a near data pointer into a far pointer and store the far pointer in a
variable, you're storing the current value of the data segment address. If
Windows moves the data segment, that far pointer will no longer be valid.
That's why it is recommended that you not do this.

Windows moves a program's data segment only during certain Windows calls.
Most often, movement of a data segment occurs during a GetMessage or
PeekMessage call. Windows always returns the new values of DS and SS to the
program when it returns from one of these calls.

Windows programs use the same segment for data and the stack. However,
Windows libraries (including the USER, KERNEL, and GDI modules, as well as
the drivers) have their own data segments but not their own stacks. They use
the stack of the program calling the Windows function. Thus, the stack
segment address is always that of the currently running program, even if the
program is calling a Windows function. When switching from one program to
another, Windows must also switch the stack.

When Windows calls a function in your program (such as a window procedure, a
window subclassing function, a call-back function, or a dialog box
function), the stack segment address is set to your program's stack segment,
but the data segment address is still the one used by the Windows library
calling your function. For this reason, Windows must also include a facility
so that programs retrieve their own data segments during one of these calls.

All these aspects of Windows' memory management--supporting multiple
instances, moving code and data segments, and discarding and reloading code
segments--are tied together.

Special Treatment of Far Functions

When you compile a C program, the compiler inserts prolog and epilog code in
each function. This code sets up a "stack frame" for the function. The
function uses the stack frame to retrieve parameters passed to the function
on the stack and to temporarily store the function's own local variables.
For a normal MS-DOS program, the prolog and epilog can be as simple as this:

PUSH BP
MOV  BP, SP
SUB  SP, x
[other program lines]
MOV  SP, BP
POP  BP
RET  y

I'm assuming here that stack overflow checking has been disabled. The value
of x that the prolog subtracts from SP is the total size of the function's
local nonstatic data (increased to the next even number).

After the three prolog instructions have been executed, the stack is
organized as shown in Figure 7-4 (from higher addresses to lower). The
function uses the BP register to reference both data passed to the function
on the stack (which have positive offsets to BP) and local data declared
within the function (which have negative offsets to BP). If the function
uses the SI and DI registers, these registers will also be saved on the
stack because they can be used by the caller for register variables. The DS
register must also be preserved during a function call.

  (Figure 7-4. may be found in the printed book.)

For a function declared as near, the return address is only one word--the
offset address. For a far function, the return address is two words. A
function declared as near or far normally has the same prolog and epilog,
but the compiled function must take into account the size of the return
address--one word or two--when accessing the parameters passed to the
function.

At the end of the function, the value of SP is set equal to BP, and BP is
popped off the stack to restore it to its original value. The function is
now ready to return to the caller. This involves a near RET or a far RET,
depending on whether the function is near or far. If the function is also
declared as pascal, the y value in the code above is the total size of
parameters passed to the function. Otherwise, y is not used, and the caller
adjusts the stack pointer when the function returns.

That's a normal compilation. When you compile a Windows program with the -Gw
switch (the Windows switch), every far function gets special prolog and
epilog code that looks like this:

PUSH DS
POP  AX        ; set AX to DS
NOP
INC  BP
PUSH BP        ; save incremented BP on stack
MOV  BP, SP
PUSH DS        ; save DS on stack
MOV  DS, AX    ; set DS to AX
SUB  SP, x
[other program lines]
DEC  BP
DEC  BP
MOV  SP, BP    ; reset SP to point to DS on stack
POP  DS        ; get back DS
POP  BP        ; get back incremented BP
DEC  BP        ; restore BP to normal
RET  y

Functions that are declared as near get the normal prolog and epilog even
with the -Gw switch. Notice two points here: First, the prolog sets the
value of AX equal to DS, saves the DS register on the stack, and then sets
DS from AX. On exiting, DS is retrieved from the stack. That code is not
doing anything (or anything harmful) except taking up unnecessary space.
Second, the previous value of the BP register is incremented before being
saved on the stack and decremented after it is retrieved from the stack.
This certainly looks mystifying right now. Figure 7-5 shows what the stack
frame looks like for far functions compiled with the -Gw compiler switch
after the prolog code is executed.

  (Figure 7-5. may be found in the printed book.)

When LINK creates the program's .EXE file, it treats every far function in a
moveable code segment as a "moveable entry point." In the .EXE header, LINK
builds an entry table that contains 6 bytes of information for every
moveable entry point in the program. This information includes the segment
ordinal number of the function--simply a sequential numbering of the
program's segments--and the offset of the function within that segment. The
entry table also includes the 2 bytes CDH and 3FH for every moveable entry
point. These 2 bytes are actually the instruction INT 3FH. This same
software interrupt shows up in non-Windows programs, where it is used for
overlay management. In Windows, the interrupt performs a similar function in
that it loads a program's code segment from disk into memory.

A flag in the entry table indicates whether the far function was listed in
the EXPORTS section of the module definition (.DEF) file. As I've discussed,
the EXPORTS section of the .DEF file must list all far functions in your
program that are called by Windows. These include window procedures (such as
the function I've been calling WndProc), call-back functions, window
subclassing functions, and dialog box functions.

When your program's code contains references to the addresses of far
functions, LINK has to leave the instruction incomplete. For example, if you
call a far function, the compiler generates a far CALL instruction, but the
address is not yet known because LINK doesn't know where the segment
containing the function will be loaded in memory. LINK builds a relocation
table in the .EXE file at the end of each code segment. This table lists all
the references to far functions within your program as well as all
references to Windows functions.

Now that the compiler and LINK have done all these strange things to your
program, it's time for Windows to run the program and do its magic.


When Windows Runs the Program

When Windows runs the first instance of a program, it loads the data segment
and also loads one or more code segments. Windows also builds two fixed
segments for program overhead. One segment contains information unique to
each instance of the program, such as the command-line argument passed to
the program and the program's current subdirectory. The other overhead
segment, which is used for all instances of the program, contains a large
chunk of the program's .EXE file header, including the entry table. The
entry table already has 6 bytes of data for every far function in your
program. Windows expands each entry into 8 bytes. These entries, called
"reload thunks," are small pieces of code that handle segment loading.

If the segment containing the far function has not yet been loaded into
memory, the reload thunk for that far function looks like this:

SAR BY CS:[xxxx], 1
INT 3F 01 yyyy

The first statement is part of Windows' mechanism to determine whether a
segment is a candidate for discarding. The second statement calls Interrupt
3FH, the software interrupt that loads into memory the appropriate segment
containing the function.

If the segment containing the far function is present in memory, the reload
thunk looks like this:

SAR BY CS:[xxxx], 1
JMP ssss:oooo

The second instruction is a far jump instruction. The ssss:oooo segment and
offset address points to the beginning of the far function in the loaded
code segment.

The references in your program's code to far functions are listed in the
segment's relocation table. Windows must insert the addresses of the far
functions into your code. The addresses that Windows uses for this are not
the actual addresses of the far functions but rather the addresses of the
reload thunks for the far functions. Because the reload thunk is in fixed
memory, Windows doesn't need to make other changes to the code when moving
the code segment that contains the far call or moving the code segment that
the far call references. Windows needs to change only the ssss:oooo address
in the reload thunk.

When the program calls a far function, it's actually calling the reload
thunk. If the segment containing the far function is not currently in
memory, the reload thunk executes the Interrupt 3FH loader that loads the
segment into memory. Windows then alters the reload thunk to contain a JMP
instruction to the function. Windows jumps back to the reload thunk, which
then jumps to the actual function. When Windows discards a segment from
memory, it changes the reload thunk from the JMP instruction back to the INT
3FH instruction. The next time a function in that segment is needed, the
segment is reloaded.

The calls in your program to Windows functions are translated into far CALL
instructions and are also listed in the segment's relocation tables. When
Windows loads your code segment into memory, it also resolves the calls to
Windows functions. For Windows functions in fixed code segments, Windows
simply inserts the address of the Windows function into your code. Windows
functions in moveable segments have their own reload thunks, and Windows
inserts the addresses of these thunks into your code.

Windows also does something special with functions that have been listed in
the EXPORTS section of the program's module definition file. Windows
modifies the function prolog of these functions when loading the segment
into memory. It replaces the first 2 bytes of every exported far function
with NOP (no operation) instructions. The prolog now looks like this in
memory:

NOP
NOP
NOP
INC  BP
PUSH BP        ; save incremented BP on stack
MOV  BP, SP
PUSH DS        ; save DS on stack
MOV  DS, AX    ; set DS to AX
SUB  SP, x

Those two NOPs make a big difference. Now the prolog saves the original
value of DS and sets DS to AX. When this function is called, AX must already
be set to the data segment that the function must use. This change makes the
exported function unusable for normal calls to the function. This is why you
cannot call your window procedure (or any other exported function) directly
from within your program.


What MakeProcInstance Does

You've just seen that far functions listed in the EXPORTS section of your
.DEF file become unsuitable for normal use when Windows loads them into
memory. These functions require that the value of AX on entry be the
program's data segment. This data segment is different for every instance of
your program. You can do only one of two things with an exported far
function:

  þ   If the function is a window procedure, you can give Windows the
      address of the function in the window class structure when registering
      the window class with RegisterClass:

      wndclass.lpfnWndProc = WndProc ;

  þ   If the function is a call-back function, a window subclassing
      function, or a dialog box function, you must give Windows an address
      returned from MakeProcInstance. For instance, if CallBackProc is the
      name of a call-back function, you must first make a call like this:

      lpfnCallBack = MakeProcInstance (CallBackProc, hInstance)

      Because CallBackProc is a far function, the address you pass to
      MakeProcInstance is actually the address of the reload thunk. The
      lpfnCallBack address you get back from MakeProcInstance can now be
      used as a parameter to a Windows function such as SetTimer.

      MakeProcInstance and RegisterClass both deal with the exported far
      function in the same way. They create an "instance thunk" for the
      function. The address that MakeProcInstance returns is the address of
      the instance thunk. Instance thunks are in a fixed area of memory, and
      they look like this:

      MOV AX, xxxx
      JMP ssss:oooo

      The xxxx value is the data segment address for this instance of your
      program. The instance thunks are different for each instance because
      each instance uses a different data segment. The ssss:oooo address in
      the instance thunk is the segment and offset address of the reload
      thunk that reloads (or jumps to) the function. The same reload thunks
      are used for all instances of a program because they jump to the same
      shareable code.

      When Windows needs to call a function in your program (such as a
      window procedure), it actually calls the instance thunk. The instance
      thunk sets AX equal to the data segment address for that instance and
      jumps to the reload thunk. The reload thunk loads the segment if it is
      not currently present in memory and branches to the function. The
      function then saves the previous value of DS and sets DS from the
      value of AX--the data segment address for the instance. When the
      function ends, it retrieves the original value of DS from the stack
      and returns control to Windows. When Windows moves the data segment
      for a particular instance, Windows must also change the xxxx values in
      all the instance thunks for that instance.


The Difference for Dynamic Libraries

You've seen that the far function prolog inserted by the compiler is
modified by Windows if the function is exported. When far functions are in
memory, they can have one of three possible prologs. If the prolog starts
off like this:

MOV AX, DS
NOP

then the far function is called only from within the same program and is not
called by Windows. If the prolog starts off like this:

NOP
NOP
NOP

then the far function has been exported. It is not called from within the
program but instead is called only by Windows. A program's window procedure
starts off in this way.

What's the purpose of the extra NOP that shows up in both prologs? The extra
NOP disappears in the third form of the prolog:

MOV AX, xxxx

You'll find this form at the beginning of many Windows functions in the
Windows library modules (USER, KERNEL, and GDI) and drivers (MOUSE,
KEYBOARD, SYSTEM, and so forth). Unlike Windows programs, Windows libraries
cannot have multiple instances, so they do not need instance thunks. The far
function itself can contain code that sets AX equal to the segment address
of the library's data segment (the value xxxx). The rest of the prolog then
saves DS and sets DS equal to AX.

When Windows moves a library's data segment in memory, it must change the
prologs of the library functions that use that data segment. And when your
program calls a Windows function, the address it calls is either the address
of the function's reload thunk (if the function is in a moveable segment) or
the function itself (if the function is in a fixed segment).

Note that Windows library functions use their own data segments but continue
to use the stack of the caller. If the library function were to use its own
stack, it would need to copy function parameters from the caller's stack.
Windows switches the stack only when switching between tasks; calling a
library function does not constitute a task switch.


Walking the Stack

One little mystery remains. You'll recall that the far function prolog
includes the statements:

INC  BP
PUSH BP

The epilog then returns BP to normal:

POP  BP
DEC  BP

What is this for?

Think about this problem: Free memory is very low. A function in one of your
program's code segments calls a far function in another of your program's
code segments. This function then calls a Windows function. The code segment
containing this Windows function is not currently present in memory. To load
it into memory, Windows has to move your data segment and discard both your
code segments. This sounds like a serious problem, because when the Windows
function returns to your program, your program will be gone.

When Windows must discard code segments, it first goes through a little
exercise called "walking the stack." Within any function, the value of
SS:[BP] is the value of BP from the previous function. If this previous
value of BP is even, the previous function is a near function; if it's odd,
the previous function is a far function. By using successive values of BP
stored on the stack, Windows can trace through the stack until it reaches
the top, which is the stack pointer address originally given to your program
when the program began executing. Windows can determine the segment
addresses and the saved DS register values of all the functions involved in
making the current Windows function call.

If Windows has to move your program's data segment (which also requires
moving the stack), it can adjust the DS register on the stack to the new
segment address. If Windows has to move a code segment containing functions
that have been involved in the current Windows function call, it changes the
return address on the stack. If Windows has to discard a code segment, it
replaces the return address with an address that points to code, which
reloads the segment and which then branches to the appropriate return
address.

As I mentioned at the onset of this discussion, you may prefer not to think
about all this activity going on in the innards of Windows.


Expanded Memory

So far, I've been discussing how Windows manages memory in simple 640-KB
systems. Windows also supports bank-switched memory that follows the
Lotus-Intel-Microsoft Expanded Memory Specification 4.0 (LIM EMS 4.0).

In a bank-switched memory scheme such as EMS, special memory boards provide
multiple banks of memory that can occupy a single same address space. Only
one of these multiple banks occupies the address space at any time. By
manipulating registers on the memory board, the EMS device driver can switch
out one bank of memory and switch in another bank.

Expanded memory under EMS 4.0 is supported in Windows 3 in two
configurations:

In the first configuration (called "small frame"), the bank-switched memory
resides in an unused area above the 640-KB level of conventional memory and
below the 1-MB level addressable by the 8086 family running in real mode.
This 384-KB area contains the ROM BIOS, some BIOS device drivers, and video
adapter buffers, but at least 64 KB are usually available for expanded
memory.

In the second configuration (called "large frame"), the bank-switched memory
also occupies an area of the 640-KB address space containing conventional
memory. The bank-switched memory can usually occupy as much as 384 KB of
conventional memory, from the 256-KB level to the 640-KB level. The level
above which memory is bank-switched is called the "bankline."

The GetWinFlags function includes the flags WF_SMALLFRAME and WF_LARGEFRAME
if you need to determine the configuration under which your program is
running.

Windows 3 uses bank-switched memory on a per process basis. When switching
between Windows programs (which normally occurs during calls to the
GetMessage, PeekMessage, and WaitMessage functions), Windows 3 will switch
out the banks of memory associated with the first process and switch in the
banks of memory associated with the second process.

The chapter entitled "More Memory Management" in the Windows Guide to
Programming discusses which memory objects are stored below the bankline and
above the bankline in the small-frame and large-frame configurations.
Generally, this is transparent to the Windows application. The only problem
arises when two Windows programs share memory using a technique other than
the clipboard, dynamic data exchange, or dynamic link libraries. Such
sharing is not recommended because it may not work in future versions of
Windows.


Protected Mode

When running on a 286 or 386 processor with at least 1 MB of memory, Windows
runs in 286-compatible protected mode. In this mode, Windows can use the 640
KB of conventional memory and memory allocated from extended memory using
the XMS (Extended Memory) driver included in the retail release of Windows
3.

In this mode, segment addresses do not correspond to physical memory.
Instead, the segment addresses are called "selectors," which reference
physical memory through a descriptor table. Protected mode is called
"protected" because the hardware of the 286 and 386 microprocessors ensures
that programs do not load invalid segment addresses or attempt to access a
segment beyond the segment's size. The processor generates a protection
exception that the operating system (or in this case, Windows) traps.
Windows responds by terminating the offending application.

For this reason, several rules are associated with protected mode:

  þ   Do not perform segment arithmetic.

  þ   Do not load far pointers with invalid addresses.

  þ   Do not attempt to address a segment beyond its allocated length.

  þ   Do not store data in code segments.

If you follow the memory allocation guidelines I discuss below, you should
have no problem running your program in protected mode. You should do your
development work in protected mode to more easily catch bugs; if you can't,
you should definitely test your code in protected mode, because it can
reveal bugs in your code that are not so evident when running in real mode.



ALLOCATING MEMORY WITHIN A PROGRAM

Programs often need to dynamically allocate blocks of memory for internal
use. Windows programs can allocate memory either from the program's private
local heap or from Windows' global heap. Windows includes two sets of memory
allocation functions, one set for using the local heap and one for the
global heap.

There are certain trade-offs between local and global memory allocations.
Local memory allocations are generally faster and require less overhead, but
the memory in the local heap is limited to 64 KB less the combined size of
the program's initialized static variables, the program's uninitialized
static variables, and the stack. Global memory allocations are not limited
to 64 KB, either in the size of single blocks or in the total memory you can
allocate. However, pointers to global memory blocks are far pointers, which
can be awkward to work with in your program. (You cannot pass a far pointer
to small-model and medium-model C library functions, for instance.)

This chart summarizes the differences between memory allocations from the
local heap and the global heap:

                Local Heap       Global Heap
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Size of block:  Less than 64 KB  Any size
Total memory:   Less than 64 KB  Free global memory
Pointer:        Near             Far

You'll probably find local heap allocations convenient for small,
short-lived blocks of memory and global heap allocations for large,
long-lived blocks.

The memory blocks you allocate can be fixed, moveable, or discardable. You
specify the attributes you want when you allocate the memory block. When you
write polite, well-mannered Windows programs, you'll probably want to use
moveable blocks when allocating global memory, but you can use either fixed
or moveable blocks for local memory. How do you deal with a moveable memory
block in your program? Very carefully.

Lock Your Blocks

At first, it seems impossible for Windows to support moveable memory blocks
in real mode. When a program allocates some local or global memory, Windows
has to give the program a pointer so that the program can access the memory.
If Windows later moves the memory block, then that pointer will be invalid.
It will no longer point to the right place.

How does Windows do it? The catch here is that the memory allocation
functions do not directly return pointers that the program may use. Instead,
these functions return--as you can probably guess by now--handles. WINDOWS.H
defines two data types called LOCALHANDLE and GLOBALHANDLE. These are
defined as type HANDLE (which is a WORD, or unsigned 16-bit short integer).
Before your program can use the allocated memory block, it must pass that
handle back to Windows in another function that locks the memory block and
returns a pointer. When a memory block is locked, Windows will not move it.
The pointer will continue to be valid until you call another function to
unlock the block. After that, Windows is free to move the memory again.

More precisely, the functions that lock a block of memory increment a "lock
count" for the memory block. The unlocking functions decrement the lock
count. When you first allocate moveable memory, the lock count is 0, meaning
that Windows can move it if necessary. When the lock count is positive,
Windows cannot move the block. (A lock count is preferable to a simple flag
that denotes whether a block is locked, because different parts of your
program can independently lock a block, use it, and unlock it. If Windows
used a flag instead of a lock count, the memory block could become unlocked
when another section of the program still needed it.)

When you use moveable memory in your Windows program, you should keep it
locked only when your program has control. You should unlock it before you
leave your window procedure. When you are entirely done with the memory
block, you can then free the block.


A Quick Example

Before going into details, let's look at a quick example to get the feel of
this process. Let's say that your program needs a 48-KB chunk of global
memory during the entire time the program is running. During initialization
(for instance, when processing the WM_CREATE message), the program uses
GlobalAlloc to allocate a 48-KB segment of moveable memory:

hGlobalMemory = GlobalAlloc (GMEM_MOVEABLE, 0xC000L) ;

The value returned from GlobalAlloc is a handle to a 48-KB global memory
segment. Store the handle in a static variable (here called hGlobalMemory).

While processing other messages, you might need to read from or write to
this memory segment. At that time you lock the block of memory using
GlobalLock and save the far pointer returned from that function:

lpGlobalMemory = GlobalLock (hGlobalMemory) ;

You can now use the lpGlobalMemory pointer as a normal far pointer. When you
are finished using the memory or processing the message, unlock the segment
so that Windows can move it around in memory again:

GlobalUnlock (hGlobalMemory) ;

When your program cleans up in preparing to terminate (probably when
processing the WM_DESTROY message), it can free up the memory segment by
using:

GlobalFree (hGlobalMemory) ;

This procedure shows how a polite, well-behaved Windows program uses global
memory. (I've ignored some details for now but will cover them shortly.)
Good Windows programmers keep in mind that their programs share resources
with other Windows programs. Good Windows programmers structure their
programs to allow Windows to move the global segments around in memory if
necessary. Good Windows programmers lock their segments only when they need
to use the memory and unlock the segments when they are done.

An impolite, bad Windows program uses initialization code like this:

lpGlobalMemory = GlobalLock (GlobalAlloc (GMEM_FIXED, 0xC000L)) ;

The GMEM_FIXED flag in GlobalAlloc specifies that the segment is fixed in
memory. Windows can't move it; therefore, the lpGlobalMemory value returned
from GlobalLock will be valid until the segment is freed up. More
convenient, yes. But don't do it.


Global Memory Functions

Now for the details. This is the general syntax of the GlobalAlloc call:

hGlobalMemory = GlobalAlloc (wFlags, dwBytes) ;

The dwBytes parameter is a double word (unsigned long). This value can be
greater than 65,536, but there are special considerations in using global
memory blocks larger than 64 KB. (These will be discussed later.)

The wFlags parameter can be a combination of several identifiers that are
combined with the C bitwise OR operator. You first have a choice of three
identifiers to define the attribute of the allocated memory block:

  þ   GMEM_FIXED--Memory is fixed. (This is the default if wFlags is 0.)

  þ   GMEM_MOVEABLE--Memory is moveable.

  þ   GMEM_DISCARDABLE--Memory is discardable. This option should be used
      only with GMEM_MOVEABLE. I'll discuss later how you can manage
      discardable global memory in your programs.

With any of the above three flags, you can use the GMEM_ZEROINIT flag for
convenience; this flag tells Windows to initialize memory contents to 0.

You can use two more flags to tell Windows what to do if not enough free
memory exists in the global heap. When Windows attempts to allocate the
block requested by GlobalAlloc, it first searches to see if a large enough
free block exists already. If not, Windows begins moving blocks of memory
that are moveable and not currently locked. If that still doesn't generate
enough space, Windows begins discarding blocks that are marked as
discardable and not currently locked, again moving moveable unlocked
segments. You can inhibit this action by using one of two flags:

  þ   GMEM_NOCOMPACT--Windows will neither compact memory nor discard memory
      when attempting to allocate the block.

  þ   GMEM_NODISCARD--Windows will not discard discardable global memory
      when attempting to allocate the block. Windows may still compact
      memory by moving moveable blocks.

If your program implements Dynamic Data Exchange (DDE), you'll need to use
the GMEM_DDESHARE flag to allocate blocks of memory that are shareable among
multiple programs. I'll discuss this in Chapter 17.

WINDOWS.H includes two shorthand flags for the most common global memory
allocations. The flag GHND (which stands for "global handle") is defined as:

GMEM_MOVEABLE | GMEM_ZEROINIT

The flag GPTR ("global pointer") is defined as:

GMEM_FIXED | GMEM_ZEROINIT

The name of this flag seems odd. Why is a fixed global block referred to as
a "global pointer"? The answer is given later in this chapter, in the
section entitled "Memory Allocation Shortcuts."

The hGlobalMemory value returned from GlobalAlloc is a handle to the global
memory block. It is NULL if GlobalAlloc could not allocate the requested
memory. You should definitely check the return value from GlobalAlloc when
allocating global memory.

The function GlobalLock locks the segment in memory by incrementing the lock
count and returns a far pointer to type char. You should have a variable
declared for this pointer:

LPSTR lpGlobalMemory ;
[other program lines]
lpGlobalMemory = GlobalLock (hGlobalMemory) ;

If hGlobalMemory is valid, GlobalLock can return NULL only if you flagged
the memory block with GMEM_DISCARDABLE. The NULL return value indicates that
the block has been discarded.

Because GlobalLock is declared as returning a far pointer to type char, you
should use casting if you need something different:

DWORD FAR *lpdwGlobalMemory ;
[other program lines]
lpdwGlobalMemory = (DWORD FAR *) GlobalLock (hGlobalMemory) ;

The far pointer returned from GlobalLock points to the beginning of a
segment. The offset address is 0. If you need to save pointers to areas
within a moveable block, do not save them as far pointers. These far
pointers may be invalid the next time you lock the segment. Instead, store
an offset from the beginning of the block. For a global block less than 64
KB, for instance, you need save only the offset address (the lower 16 bits)
of the pointer.

The GlobalUnlock function decrements the lock count for the hGlobalMemory
handle:

GlobalUnlock (hGlobalMemory) ;

Calling GlobalUnlock invalidates the lpGlobalMemory pointer returned from
GlobalLock. When the lock count is 0, Windows can move the block in memory.

GlobalLock and GlobalUnlock are fairly fast, so you don't suffer a real
performance penalty if you use the two functions liberally. You should
definitely not keep a block locked from one message to another. Remember
that Windows performs best when memory is moveable. When you make a call to
a Windows function, Windows may need to load code into memory. If you have a
locked memory block sitting around, Windows may have to discard other
segments to make room.

When you are entirely finished with the memory block, you can call:

GlobalFree (hGlobalMemory) ;

Following this call, the hGlobalMemory handle is no longer valid, and the
block is freed.


More Global Memory Functions

Although the four global memory functions shown above are the ones you'll
use most often, Windows also provides several others. Before using
GlobalAlloc, you may want to determine the amount of global memory currently
available:

dwAvailable = GlobalCompact (dwRequested) ;

GlobalCompact causes Windows to move moveable blocks and to calculate the
area of free memory that could be obtained by also discarding discardable
blocks. If the function cannot generate dwRequested bytes, it returns the
largest block of free memory available. Discarding doesn't take place until
you call GlobalAlloc using the size returned from GlobalCompact.

After you allocate a memory block, you can determine its size using:

dwBytes = GlobalSize (hGlobalMemory) ;

You can also change the size of the memory block or change its attributes
using GlobalReAlloc. This function is a little tricky, because it can be
used in one of three ways. Here's the general syntax:

hGlobalMemory = GlobalReAlloc (hGlobalMemory, dwBytes, wFlags) ;

First, you can change the size of a global memory block (either increasing
or decreasing it) using:

GlobalReAlloc (hGlobalMemory, dwNewBytes, wFlags) ;

The data already stored in the block are preserved. The function returns
NULL if it cannot increase the block to the requested size.

The wFlags parameter is used in the same way as the wFlags parameter for
GlobalAlloc: GMEM_NODISCARD and GMEM_NOCOMPACT place restrictions on what
Windows will do to satisfy the allocation request. GMEM_ZEROINIT zeroes out
additional bytes if you are expanding the block. When calling GlobalReAlloc,
you don't have to include the GMEM_FIXED, GMEM_MOVEABLE, or GMEM_DISCARDABLE
flags. Windows preserves the attribute specified when the block was
allocated. However, you may want to use the GMEM_MOVEABLE flag for
reallocating a fixed block. Doing so gives Windows permission to move the
block in memory to satisfy the allocation request. In this case,
GlobalReAlloc returns a new global memory handle to the fixed block:

hGlobalMemoryNew = GlobalReAlloc (hGlobalMemoryOld, dwNewBytes,
                                   GMEM_MOVEABLE) ;

If GlobalReAlloc returns NULL, the request for memory was refused, and the
original hGlobalMemoryOld value passed to GlobalReAlloc is still valid for
the fixed block.

The second way to use GlobalReAlloc is to change the discardable attribute
of moveable blocks. The dwNewBytes value is ignored. You can change a
moveable block to a discardable one:

GlobalReAlloc (hGlobalMemory, 0L, GMEM_MODIFY | GMEM_DISCARDABLE) ;

or change a discardable block to a moveable (but nondiscardable) one:

GlobalReAlloc (hGlobalMemory, 0L, GMEM_MODIFY | GMEM_MOVEABLE) ;

The third use of GlobalReAlloc is to discard a discardable memory block.
This requires dwNewBytes to be 0 and the wFlags parameter to be
GMEM_MOVEABLE:

GlobalReAlloc (hGlobalMemory, 0L, GMEM_MOVEABLE) ;

You can do the same thing using:

GlobalDiscard (hGlobalMemory) ;

In fact, GlobalDiscard is a macro defined in terms of GlobalReAlloc.


Using Discardable Global Memory

Discardable memory segments are generally used for data that can be easily
regenerated. For example, suppose you decide to use a separate file for
"help" text that your program displays when the user requests it. You could
allocate a moveable block, lock it, read some data from the file, display it
on the screen, unlock the block, and free it. However, this approach
requires that your program allocate a new block of memory and access this
file every time help information is requested.

Alternatively, you could keep a moveable block for this file buffer in
memory all the time. When the user requests some help information, you check
to see that the information is already in the buffer, and then you use that
information rather than access the disk again. The performance of your
program improves, but it does so at the cost of having a block of global
memory allocated for the duration of your program.

How about using a discardable block instead? This keeps the buffer in memory
but also gives Windows permission to discard it if necessary. When you lock
the block:

lpGlobalMemory = GlobalLock (hGlobalMemory) ;

the lpGlobalMemory return value will be NULL if the block has been
discarded. In that case, you use GlobalReAlloc to reallocate the segment.
Windows never discards a discardable block when the lock count is nonzero.

If you have obtained a valid far pointer from GlobalLock, that pointer is
valid until you call GlobalUnlock. Even after the block is discarded, the
handle is still valid. (This avoids problems when you pass the handle to
GlobalLock.)

You can also determine that a block is discardable or has been discarded by
using the GlobalFlags function:

wFlags = GlobalFlags (hGlobalMemory) ;

WINDOWS.H has identifiers you can use in combination with wFlags. This value
is nonzero if the block is discardable:

(GMEM_DISCARDABLE & wFlags)

This value is nonzero if the block has been discarded:

(GMEM_DISCARDED & wFlags)

Another approach is to include the GLOBAL_NOTIFY flag when allocating a
discardable segment. In this case, Windows will call a call-back function in
your program that has been registered with the GlobalNotify function when it
is about to discard a discardable segment.


Huge Global Memory Blocks

The dwSize parameter in GlobalAlloc is a 32-bit DWORD (double word), large
enough in theory to allocate a 4-gigabyte block of memory. Although you
obviously won't be able to get a block quite that large, it appears that you
can still allocate a block of memory larger than 64 KB. Yes, you can, but
you have to be careful. Beginning with version 4 of the Microsoft C
Compiler, the huge keyword was implemented for defining variables that are
larger than 64 KB. A huge pointer is 32 bits, just like a far pointer.
However, the Microsoft C Compiler assumes that a far pointer addresses only
a 64-KB range and will never run past the end of the segment. With a huge
pointer, the compiler generates code that checks for segment overrun and
does appropriate segment arithmetic on the pointer.

The phrase "segment arithmetic" should have triggered a bell in your head! I
mentioned earlier that you should not perform segment arithmetic in your
Windows programs because it violates rules of protected mode. Fortunately,
the Microsoft C 6 compiler and Windows work together to perform different
segment arithmetic depending on whether the program is running in real mode
or protected mode. In real mode, jumping from the end of one 64-KB segment
to the beginning of another segment requires adding 0x1000. In protected
mode, the selectors are allocated so that 8 is added for the segment jump.
(Note: Don't rely on this number; it may change under future versions.)

When you use GlobalAlloc to allocate memory greater than 64 KB, you must
cast the pointer returned from GlobalLock into a huge pointer and save it as
a huge pointer. For instance, this code allocates a 128-KB memory block and
locks it:

GLOBALHANDLE hGlobalMemory ;
char huge    *lpGlobalMemory ;
[other program lines]
hGlobalMemory = GlobalAlloc (GMEM_MOVEABLE, 0x20000L) ;
[other program lines]
lpGlobalMemory = (char huge *) GlobalLock (hGlobalMemory) ;

Every function that manipulates this huge pointer must be aware that the
pointer is huge. If a function that is passed a huge pointer believes that
the pointer is a simple far pointer, the Microsoft C Compiler will not
generate any segment arithmetic when you manipulate the pointer. For this
reason, you should not pass a huge pointer to most of the standard C library
functions (the C 6 manuals list functions that support huge arrays) or to
any of the Windows functions unless you know that the function will not be
referencing the pointer past the end of a segment.

That's one problem with huge pointers. Another problem is the possibility
that a single data item referenced by the pointer may straddle two segments.
With a huge pointer to character data, this is never a problem, because each
character is a byte. The offset address that GlobalLock returns is always 0,
so the huge pointer can also safely reference arrays of all the standard
data types (char, int, short, long, float, and double).

If you use a huge pointer to an array of structures, you will have no
problems if the size of the structure is a power of 2 (such as 2, 4, 8, 16,
and so forth). That guarantees that no single structure will straddle two
segments. If the size of the structure is not a power of 2, then you are
bound by two restrictions:

  þ   The data block allocated with GlobalAlloc cannot be larger than 128
      KB.

  þ   The offset address returned from GlobalLock must be adjusted so that a
      structure does not straddle two segments.

The first rule is actually implied by the second rule. If the initial offset
address is adjusted so that an element of the structure does not straddle
the first and second segments, it will straddle the second and third
segments.

This explanation requires an example. Let's say you want a huge memory block
to hold an array of 15,000 structures, where each structure requires 6
bytes. You can use typedef statements for this structure and a far pointer
to the structure:

#typedef struct
          {
          int  element1 ;
          long element2 ;
          }
          MYSTRUCT ;

#typedef MYSTRUCT huge *LPMYSTRUCT ;

In your program you can define a variable for the far pointer to the
structure:

GLOBALHANDLE   hGlobalMemory ;
LPMYSTRUCT     lpMyStruct ;
[other program lines]
hGlobalMemory = GlobalAlloc (GHND, 15001 * sizeof (MYSTRUCT)) ;

lpMyStruct = (LPMYSTRUCT) ((65536L % sizeof (MYSTRUCT)) +
                       GlobalLock (hMem)) ;

The pointer returned from GlobalLock will have an offset address of 0. You
must increase that so that a single structure does not straddle the two
segments. The adjustment value is the remainder of 65,536 divided by the
size of the structure. (In this case, the adjustment value is 4.) Because
you have a little waste here, GlobalAlloc allocates one more structure than
is really needed.


Allocating Local Memory

I've been stressing the importance of using moveable (and, if possible,
discardable) global memory blocks. With local memory you have the same
options, but the guidelines are more relaxed. Whether you use fixed or
moveable memory blocks within your local heap is up to you. Because your
entire data segment is moveable (as it will be if you use the small or
medium model), what you do inside your data segment doesn't affect other
applications.

In fact, Windows makes it easier to use local memory if the blocks are
fixed. The question to ask is: Can my local heap be smaller if I use
moveable blocks instead of fixed blocks? If you use local memory a lot, and
the life spans of the memory blocks overlap each other, then the answer to
that question may be yes. If you use local memory allocations solely for
short-lived memory, there's no reason to make the blocks moveable.

The local memory functions are similar to the global memory functions.
Instead of GlobalAlloc, GlobalLock, GlobalUnlock, and GlobalFree, you use
LocalAlloc, LocalLock, LocalUnlock, and LocalFree. Instead of identifiers
that begin with GMEM, you use identifiers that begin with LMEM. The only
real differences are these: The memory size passed to LocalAlloc is a WORD
(unsigned integer) rather than a DWORD, and the pointer returned from
LocalLock is a near pointer rather than a far pointer.

This is the syntax of LocalAlloc:

hLocalMemory = LocalAlloc (wFlags, wSize) ;

The wSize parameter is large enough to accommodate a requested size of
65,536 bytes, but you won't get a local block that large, because the data
segment also includes your program's stack and static variables.

The wFlags parameter can first specify the attributes of the block:

  þ   LMEM_FIXED--Memory is fixed. (This is the default if wFlags is 0.)

  þ   LMEM_MOVEABLE--Memory is moveable.

  þ   LMEM_DISCARDABLE--Memory is discardable. This option should be used
      only with LMEM_MOVEABLE.

The LMEM_ZEROINIT flag zeroes out the memory block.

These two flags are equivalent to the similar flags for GlobalAlloc:

  þ   LMEM_NOCOMPACT--Windows will neither compact nor discard memory in the
      local heap when attempting to allocate the block.

  þ   LMEM_NODISCARD--Windows will not discard discardable memory in the
      local heap when attempting to allocate the block. Windows may still
      compact memory by moving moveable blocks.

WINDOWS.H also includes two shorthand flags for local memory allocations.
The flag LHND (which stands for "local handle") is defined as:

LMEM_MOVEABLE | LMEM_ZEROINIT

The flag LPTR ("local pointer") is defined as:

LMEM_FIXED | LMEM_ZEROINIT

If Windows cannot find enough memory in the local heap to allocate the
block, it will attempt to expand the local heap by enlarging the size of the
entire data segment. (Remember that the local heap is always at the top of
the automatic data segment.) Windows may even move the data segment to
another location in memory if that will provide the space it needs to expand
the local heap. When LocalAlloc returns, your data segment may have been
moved. (If this makes you nervous, check the section below entitled "Locking
Your Own Data Segment.") The HEAPSIZE specification in the module definition
(.DEF) file is really a minimum value for the heap.

If, after all this, Windows still cannot find enough memory in the local
heap to allocate the memory block, the handle returned from LocalAlloc will
be NULL. If you use local memory allocation only for small, short-lived
memory blocks, you probably don't need to check the handle for a NULL value.
(Alternatively, you might want to check the value during program development
but not in the final version of the program.) If you do a lot of random
local memory allocation with blocks of various sizes and different life
spans, then you'll have to implement some kind of error processing.

LocalLock turns the local memory handle into a near pointer and then locks
the block. LocalUnlock unlocks the block and invalidates the pointer.
LocalFree frees the memory block and invalidates the handle.

Here's an example of using local memory to define the window class structure
during program initialization:

LOCALHANDLE    hLocalMemory ;
NPWNDCLASS     npwndclass ;
[other program lines]
if (!hPrevInstance)
     {
     hLocalMemory = LocalAlloc (LHND, sizeof (WNDCLASS)) ;
     npwndclass = (NPWNDCLASS) LocalLock (hLocalMemory) ;

     npwndclass->style         = CS_HREDRAW | CS_VREDRAW ;
     npwndclass->lpfnWndProc   = WndProc ;
     npwndclass->cbClsExtra    = 0 ;
     npwndclass->cbWndExtra    = 0 ;
     npwndclass->hInstance     = hInstance ;
     npwndclass->hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     npwndclass->hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     npwndclass->hbrBackground = GetStockObject (WHITE_BRUSH) ;
     npwndclass->lpszMenuName  = NULL ;
     npwndclass->lpszClassName = szAppName ;

     RegisterClass (npwndclass) ;

     LocalUnlock (hLocalMemory) ;
     LocalFree (hLocalMemory) ;
     }

The size of the memory block passed to LocalAlloc is the size of the
WNDCLASS structure. LocalLock always returns a near pointer regardless of
the memory model, because it allocates memory from the local heap in the
program's automatic data segment. In this example, the pointer to type char
that LocalLock returns is cast into a pointer to a WNDCLASS structure. The
-> notation is used to reference the elements of a structure based on a
pointer to the structure. In the RegisterClass call, we don't use the
address (&) operator because npwndclass is already a pointer. Note also that
the use of LHND initializes the block of memory to 0. All variables in the
structure that take a 0 or NULL value need not be explicitly assigned.


Other Local Memory Functions

Some other local memory functions parallel those for global memory
allocations, except that sizes are in terms of wBytes rather than dwBytes.
You can get the current size of a local memory block by calling:

wBytes = LocalSize (hLocalMemory) ;

The function LocalReAlloc can change the size of an allocated memory block,
change a moveable block to discardable, change a discardable block to
nondiscardable, and discard a discardable block, just like GlobalReAlloc.
During a LocalReAlloc call, Windows may expand the size of the local heap by
expanding the size of the entire data segment, possibly moving it to another
location in memory. LocalCompact can determine the amount of free local
memory available in the heap, LocalDiscard discards a discardable memory
block, and LocalFlags provides the current status of discardable blocks.

Two other local memory functions do not have counterparts in the global
memory functions. You can prevent your local heap from being compacted by
calling:

LocalFreeze (0) ;

When you later want to allow compacting, you can call:

LocalMelt (0) ;


Locking Your Own Data Segment

Now that you are thoroughly paranoid about locking and unlocking memory
blocks, you may start to wonder about the automatic data segment of the
program itself. When the program begins executing, your automatic data
segment has a lock count of 1, and the data segment cannot be moved in
memory. Windows decrements that lock count when the program makes one of the
following calls: GetMessage, PeekMessage, WaitMessage, LocalAlloc, or
LocalReAlloc.

The GetMessage, PeekMessage, and WaitMessage calls can cause a switch from
your program to another program. When your program gets control again, your
data segment may have been moved. A LocalAlloc or LocalReAlloc call can
cause Windows to expand the size of your local heap, in the process moving
the data segment to another location in memory. Windows increments the lock
count when it returns from these calls to your program. So in most cases,
your program's data segment is locked when your program has control. This
means that you can construct (through casting) far pointers to data in your
data segment, and they will remain valid until you make one of these five
calls or exit the window procedure.

If you want to prevent the movement of your data segment during a LocalAlloc
or LocalReAlloc call, you can increase the lock count by 1 before calling
the function:

LockData (0) ;

Following the LockData call, the lock count of your data segment will be 2.
When Windows decrements the count during a LocalAlloc or LocalReAlloc call,
it will still be positive and your data segment will still be locked. You
can decrement the lock count by calling:

UnlockData (0) ;

If you're brave, you might also want to take the opposite approach. You
might be willing to keep your data segment unlocked while making Windows
calls. You would do this by calling UnlockData to decrement the lock count
to 0 and then LockData to increment it to 1 again before exiting the window
procedure. When your data segment is unlocked, Windows has more flexibility
in moving segments in memory because Windows can move your data segment as
well. However, when your data segment is unlocked, you should not make any
Windows calls that require a far pointer to your data segment. Depending on
what happens during that call, the pointer may become invalid by the time
Windows needs to use it.


Memory Allocation Shortcuts

We have been treating handles as abstract numbers. But sometimes handles are
actually pointers. If you use LMEM_FIXED in LocalAlloc, the handle returned
from the call is a near pointer that you can use directly. You do not need
to call LocalLock. (If you do, it simply returns the same value you pass as
a parameter--the handle that is actually a valid near pointer.) In fact,
WINDOWS.H defines a special flag for calling LocalAlloc with an LMEM_FIXED
parameter. The flag is LPTR ("local pointer") and is defined as:

LMEM_FIXED | LMEM_ZEROINIT

When you use this flag, you need only cast the return value from LocalAlloc
into a near pointer of the type you want:

npString = (char NEAR *) LocalAlloc (LPTR, wSize) ;

You free it up by casting the near pointer back to a handle and calling
LocalFree:

LocalFree ((LOCALHANDLE) npString) ;

This technique is handy for allocating small chunks of local memory. For
instance, here's the example shown above for allocating memory for the
window class structure. It now uses a fixed block of local memory:

NPWNDCLASS     npwndclass ;
[other program lines]
if (!hPrevInstance)
     {
     npwndclass = (NPWNDCLASS) LocalAlloc (LPTR, sizeof (WNDCLASS)) ;

     npwndclass->style         = CS_HREDRAW | CS_VREDRAW ;
     npwndclass->lpfnWndProc   = WndProc ;
     npwndclass->cbClsExtra    = 0 ;

     npwndclass->cbWndExtra    = 0 ;
     npwndclass->hInstance     = hInstance ;
     npwndclass->hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     npwndclass->hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     npwndclass->hbrBackground = GetStockObject (WHITE_BRUSH) ;
     npwndclass->lpszMenuName  = NULL ;
     npwndclass->lpszClassName = szAppName ;

     RegisterClass (npwndclass) ;

     LocalFree ((LOCALHANDLE) npwndclass) ;
     }

We've eliminated two lines of code (LocalLock and LocalUnlock) and one
variable (the local memory handle). Note the casting in the LocalAlloc and
LocalFree calls.

The same technique is even applicable for GlobalAlloc when the GMEM_FIXED
(or GPTR) flag is used. The "handle" returned from GlobalAlloc is the
segment address of the segment. It's a little more clumsy to convert that
into a far pointer, but here's how to do it:

lpString = (LPSTR) MAKELONG (0, GlobalAlloc (GPTR, dwSize)) ;

The MAKELONG macro combines the segment address returned from GlobalAlloc
and an offset address of 0 to make a long integer, which is then cast into a
long pointer to type char. To free this block, you have to extract the
segment address from the pointer and pass it to GlobalFree:

GlobalFree ((GLOBALHANDLE) LOWORD ((LONG) lpString))) ;

I don't like the memory allocation shortcuts, and I don't think they should
be used. I've included them only because many sample Windows programs use
them, particularly when allocating local memory.


Using C Memory Allocation Functions

The start-up code that LINK attaches to C programs running under Windows
contains functions for many of the memory allocation functions in Microsoft
C 6, such as calloc, malloc, realloc, and free. The routines in the start-up
code convert the normal C memory allocation functions into equivalent
Windows memory allocation functions. For instance, the function:

malloc (wSize) ;

is translated into:

LocalAlloc (LMEM_FIXED | LMEM_NOCOMPACT, min (1, wSize)) ;

These functions are included in the Windows start-up code not for your
benefit, but because several other C functions from the standard library
make calls to these C memory allocation functions. These other C functions
cannot work properly without using the  Windows memory allocation calls.
Although it's not intended that you use these functions, you can use them.
Be aware, however, that in compact-memory and large-memory models (which you
shouldn't be using for Windows programs anyway), the Windows malloc returns
a far pointer to your program's automatic data segment--as opposed to the
non-Windows malloc, which returns a far pointer to a block of memory outside
the automatic data segment. Also be aware that _fmalloc and halloc are
translated into GlobalAlloc calls with a flag that is equal to (GMEM_FIXED |
GMEM_NODISCARD), and as you know, you should not use fixed global memory
blocks. Moreover, the pointer returned from the Windows halloc is not
properly aligned for an array of elements that are not multiples of 2, and
the memory is not initialized to 0. The point is clear: Unless you feel
deprived doing C programming without malloc, use the Windows functions for
all memory allocations.


If You Know You're Running in Protected Mode

In this chapter, I have tried to present guidelines for memory management
that are valid in all modes in which Windows 3 can run. However, if your
program is so large that it can (realistically) run only in protected mode,
then you can simplify your memory management. Such a program should use
GetWinFlags and terminate if it's running in real mode.

When allocating moveable global memory, you can lock the memory block
immediately to obtain the pointer. Even though the block is locked, Windows
can still move it in memory--and the pointer will remain valid. You don't
even have to save the global memory handle. To free the block, you can use
GlobalHandle to obtain the handle from the pointer. Then unlock the block
and free it as normal.

You can compile with the -G2 flag to generate 286 code.