Windows Programming


Using Windows Memory Management Services

David Singleton


David Singleton is a Director and Principal Consultant with Singleton Systems Ltd, an independent computer systems consulting company. He has a BA in Physics and an MSc in Computer Science. He can be contacted at his home and business address — 30 The Albany, Sunset Avenue, Woodford Green, Essex, IG8 OTJ, England. His telephone number is +44-81-505-7996. He can also be contacted at CompuServe address 100265,3625.

Introduction

It is generally recommended that programs developed to run under MS-Windows use the medium memory model. However, use of the medium memory model normally confines the user to a single, 64 KB data segment. Windows provides services to allow programs to request additional space from its global heap. This article demonstrates a C++ class, MemoryObject, that hides the details of managing the Windows global heap from the applications programmer.

Windows recognizes that you might want to use one of the smaller memory models, but that you might need more than 64 KB of data space. For this reason, the Windows global memory services allow a user to request (and return) data segments from global memory. Programs must address these segments with a full segment:offset address, i.e., with a far pointer. I use the following main Windows global memory services:

Reference 1 gives full details of all the Windows global memory services. Reference 2 provides invaluable background material.

Design Approach

While it is not difficult to use the Windows global memory services, programmers must follow a few rules, as noted above. Further, there is a practical limit on the number of segments that can exist at any one time. (Reference 3 gives some indication of this number, but it largely depends on Windows internals with which I am unfamiliar.) So, it is not a good idea to request lots of small chunks of global memory from Windows. Instead, my approach is to request a few larger chunks and manage the internal allocation of these myself. I have therefore implemented a set of C++ classes to manage the allocation and deallocation of Windows global memory.

Managing the allocation/deallocation process proved to be the most difficult part of my class design. A program may allocate or deallocate individual blocks in any conceivable order. So my classes must allocate the correct amount of memory, without too much overhead, and return a block in such a way that it can be reallocated if needed. I've also designed the classes to provide some diagnostic capabilities that can be used to trace memory leakage within user programs.

Taking all of the above into account, I've come up with two main classes to manage the allocation/deallocation of Windows global memory: FarHeapBlock, and MemoryObject.

Class FarHeapBlock

This class manages the allocation/deallocation of large memory blocks from the Windows global memory area. For this application, I've arbitrarily defined a large block as 4,096 bytes. You can use a larger or smaller block size if you wish. Although the Windows global memory allocation functions allow you to request a block of up to about 16 MB, my implementation restricts the maximum block size to 64 KB, as it assumes that the block should fit within a single 16-bit segment.

I use doubly-linked lists to keep track of individual FarHeapBlocks. Thus, a FarHeapBlock contains pointers both to the previous FarHeapBlock and to the next FarHeapBlock. In the case of FarHeapBlocks, I also decided to store the HGLOBAL of the previous and next blocks, rather than a far pointer. With hindsight, I could have used far pointers instead. Indeed, this would probably make for slightly more efficient code.

Listing 1 and Listing 2 show the FarHeapBlock class. Most of the code is in the two operator functions new and delete.new requests a block of global memory from Windows and chains it into the list of existing FarHeapBlocks. new also checks that the request for memory has succeeded. If not, it raises an error. delete unlinks a FarHeapBlock from the list of FarHeapBlocks and returns the memory block to Windows. GetHbFromHandle converts a Windows HGLOBAL handle to a far memory pointer.

(Users of the Microsoft Foundation Class (MFC) library may see something familiar in the way that I move through a list of FarHeapBlocks. This is deliberate, after all, why change a good thing! I have defined a type FHB_POSITION, which gives the current position in the list of FarHeapBlocks. I use functions GetHeadPosition, GetAt, GetNext, and IsEmpty to iterate through the list of FarHeapBlocks. GetCount returns a count of the number of FarHeapBlocks that are currently in existence. These functions operate in the same manner as their synonyms in MFC.)

FarHeapBlock manages the allocation/deallocation of memory from the Windows global memory area; MemoryObject manages allocation/deallocation of memory from a FarHeapBlock. To FarHeapBlock, any block of memory, whether allocated or free, is just another MemoryObject. FarHeapBlock therefore requires the four MemoryObject pointers free_list, end_free_list, in_use_list, and end_in_use list. These pointers delineate the start and end of the free and allocated chains of MemoryObjects within a FarHeapBlock.

At this point, we must leave the realms of standard C++ and move into some Microsoft-specific features. All of the above pointers are declared as type MemoryObject BASED_VOID *. BASED_VOID is in turn defined as __based (void). To fully specify a pointer to an object, a program running on a 80x86 platform must specify both a segment and an offset — but the __based (void) type of pointer specifies only the offset value. To complete the pointer, Microsoft supplies a further extension, the :> operator. This operator enables a program to combine a segment with an offset to form a complete pointer. Using a __based (void) pointer reduces the overhead of each pointer to two bytes, while preserving the functionality of a far (four-byte) pointer.

The FarHeapBlock class supports the MemoryObject class. MemoryObject is the class through which you will typically access global memory; under normal circumstances you will not need to worry about FarHeapBlock.

Class MemoryObject

A MemoryObject is essentially a header for a block of memory; without the associated memory block, a MemoryObject is of little use! Listing 3 and Listing 4 show a significant portion of the MemoryObject class. (The full class source code is provided on this month's code disk.)

Like the FarHeapBlock, each MemoryObject contains a number of special data fields. A particular MemoryObject will always be referenced in some FarHeapBlock's in-use or free list. Therefore, each MemoryObject holds pointers to the next and previous MemoryObjects in whichever list it resides. mo_block_size indicates the size of each MemoryObject. (sizeof (MemoryObject) + the amount of data space requested by the user.) A MemoryObject must know how big it is so that it knows how much space to return to the free list. When deleting a MemoryObject, a program must know what FarHeapBlock it is stored in so that the MemoryObject can be transferred from the FarHeapBlock's in-use list to its free list. Therefore, each MemoryObject keeps a record of its associated FarHeapBlock in my_heap_block.

As in FarHeapBlock, most of the code for MemoryObject is found in the two operator functions new and delete, C++ gives the programmer the ability to override these operator functions to considerable benefit. MFC already redefines the global new and delete operator functions for memory tracking purposes. I use MFC whenever I am developing codes, so I decided not to redefine these operators at a global level. Instead, I've implemented class-specific new and delete operators for the MemoryObject class. This approach does impose one restriction on the use of classes derived from MemoryObject: users should not create arrays of such classes, as they would be created using the global new operator rather than the class-specific version.

I define three versions of MemoryObject::operator new:

I have also defined functions AllocateBlock and ReturnBlock so that users can request arbitrary blocks of global memory, managed as MemoryObjects, without always having to define derived versions of the MemoryObject class. These two functions work in a very similar way to the standard C++ functions malloc and free.

Debug Support

I have built debug support into the MemoryObject class. My code defines additional MemoryObject data items when I compile the code with debugging turned on. These objects are as follows:

I define a macro MO_DEBUG_NEW that can replace calls to MemoryObject's new operator. MO_DEBUG_NEW selects the correct version of new, depending on whether or not the code is being compiled for debugging. You can, if you wish, go one stage further. Include the following definition in your code, whenever you wish to invoke MO_DEBUG_NEW, but want your code to look like standard code:

#define new MO_DEBUG_NEW
You can then use new as usual throughout the rest of your code. You should use this technique with great care in whatever file also makes calls to non-MemoryObject versions of new, (e.g. the global operator new). I believe that it will work without problem, but I have not tested it exhaustively. (I issue this warning because MFC uses an almost identical technique and defines an equivalent macro DEBUG_NEW.)

At the end of a program I can call MemoryHeapIsEmpty to see whether I have had any memory leakage. If it returns FALSE, I can then call DumpAllObjects to get a list of the MemoryObjects that have not been correctly deleted together with the program details of where they were allocated.

The class's constructor and destructor both assist with validity checking during debugging, to trap errors before they get too far. The constructor sets an allocation flag and a validity tag. The destructor checks the validity tag and marks the block as unallocated.

I have also developed MemoryObject::PrintHeapReport to record the status of the current MemoryObject and FarHeapBlock internal structures to a file. This has proven invaluable during development. (Strictly speaking, I should have developed a separate FarHeapBlock::PrintHeapReports function to report on FarHeapBlock internals, but it was just as easy to develop the function as I have done.)

Final Remarks

In this article I've presented but one of possibly many ways to access Windows global memory. When I designed these classes I wanted not only to exploit the power of C++, but to tap the wealth of resources to be found in an application framework — in this case, MFC. Thus I hoped to avoid reinventing too many wheels. I believe that I have demonstrated what a little careful class design, combined with a little judicious borrowing, can do.

References

1. Windows SDK Programmer's Reference, Volume 2, Functions. Microsoft.

2. Programming Windows 3.1, Third Edition, Charles Petzold, Microsoft Press 1992.

3. i486 Microprocessor Programmer's Reference Manual, Intel 1990.

Additional Reading

1. Visual C++ Programming Techniques Manual, Microsoft.

2. "cout and cerr for Windows," David Singleton, The C Users Journal, Vol. 11 No. 9, September 1993.