Memory-Mapped Files

In case you think you don't have enough memory management options already, I'll toss you another one. Suppose your program needs to read a DIB (device-independent bitmap) file. Your instinct would be to allocate a buffer of the correct size, open the file, and then call a read function to copy the whole disk file into the buffer. The Windows memory-mapped file is a more elegant tool for handling this problem, however. You simply map an address range directly to the file. When the process accesses a memory page, Windows allocates RAM and reads the data from disk. Here's what the code looks like:

HANDLE hFile = ::CreateFile(strPathname, GENERIC_READ, 
    FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ASSERT(hFile != NULL);
HANDLE hMap = ::CreateFileMapping(hFile, NULL, PAGE_READONLY,
    0, 0, NULL);
ASSERT(hMap != NULL);
LPVOID lpvFile = ::MapViewOfFile(hMap, FILE_MAP_READ,
    0, 0, 0); // Map whole file
DWORD dwFileSize = ::GetFileSize(hFile, NULL);  // useful info
// Use the file
::UnmapViewOfFile(lpvFile);
::CloseHandle(hMap);
::CloseHandle(hFile);

Here you're using virtual memory backed by the DIB file. Windows determines the file size, reserves a corresponding address range, and commits the file's storage as the physical storage for this range. In this case, lpvFile is the start address. The hMap variable contains the handle for the file mapping object, which can be shared among processes if desired.

The DIB in the example above is a small file that you could read entirely into a buffer. Imagine a larger file for which you would normally issue seek commands. A memory-mapped file works for such a file, too, because of the underlying virtual memory system. RAM is allocated and pages are read when you access them, and not before.

By default, the entire file is committed when you map it, although it's possible to map only part of a file.

If two processes share a file mapping object (such as hMap in the sample code above), the file itself is, in effect, shared memory, but the virtual addresses returned by MapViewOfFile might be different. Indeed, this is the preferred Win32 method of sharing memory. (Calling the GlobalAlloc function with the GMEM_SHARE flag doesn't create shared memory as it did in Win16.) If memory sharing is all you want to do and you don't need a permanent disk file, you can omit the call to CreateFile and pass 0xFFFFFFFF as the CreateFileMapping hFile parameter. Now the shared memory will be backed by pages in the swap file. Consult Richter for details on memory-mapped files. The EX35B and EX35C sample programs in Chapter 35 illustrate sharing of memory-mapped files.

If you intend to access only a few random pages of a file mapping object that is backed by the swap file, you can use a technique that Jeffrey Richter describes in Advanced Windows under the heading "Sparsely Committed Memory-Mapped Files." In this case, you call CreateFileMapping with a special flag and then you commit specific address ranges later with the VirtualAlloc function.

You might want to look carefully at the Windows message WM_COPYDATA. This message lets you transfer data between processes in shared memory without having to deal with the file mapping API. You must send this message rather than post it, which means the sending process has to wait while the receiving process copies and processes the data.

Unfortunately, there's no direct support for memory-mapped files or shared memory in MFC. The CSharedFile class supports only clipboard memory transfers using HGLOBAL handles, so the class isn't as useful as its name implies.