Windows File Mappings: Section Objects, View Alignment, and Durable Writes
Use Windows file mappings safely: distinguish mapping objects from views, align offsets, synchronize shared memory, and separate page flushing from durable storage.
Windows file mapping associates file content with a region of a process’s virtual address space. A mapping object, also called a section object, describes the backing file or paging-file allocation and maximum extent. A view is the range actually mapped into one process. The distinction matters for both resource lifetime and correctness: creating a mapping does not map bytes into a process, two processes can map the same object at different addresses, and neither a shared pointer nor a flushed page supplies the synchronization or transaction guarantees an application may expect.
Build the mapping object from a compatible file handle
Open the backing file with access compatible with the mapping protection. A read-only view needs a readable file and compatible PAGE_READONLY-style protection; a writable view requires a writable file handle and PAGE_READWRITE protection. The CreateFileMappingW maximum size defines how much of the file the mapping object exposes. Passing zero for the high and low size fields maps the existing file size, which is convenient for readers. A zero-length file cannot produce a useful zero-sized mapping and should be handled explicitly. If a writable mapping is sized larger than the file, Windows can extend the file when creating the mapping, so size selection is a data-changing decision, not only a memory option.
#include <windows.h>
HANDLE CreateWritableFileMapping(HANDLE file, DWORD* errorOut)
{
if (errorOut == nullptr) {
return nullptr;
}
*errorOut = ERROR_SUCCESS;
if (file == nullptr || file == INVALID_HANDLE_VALUE) {
*errorOut = ERROR_INVALID_HANDLE;
return nullptr;
}
// The caller must open the file with write access for PAGE_READWRITE.
HANDLE mapping = CreateFileMappingW(
file,
nullptr, // add explicit security attributes for named sharing
PAGE_READWRITE,
0, // high 32 bits of maximum size
0, // zero means current file size
nullptr); // unnamed object
if (mapping == nullptr) {
*errorOut = GetLastError();
}
return mapping;
}
Creating the mapping handle and mapping a view are separate operations. MapViewOfFile selects the desired read/write access and the file offset and length that the process wants to expose. Check both calls independently. If an application opens an existing named mapping rather than creating a new one, GetLastError() can report ERROR_ALREADY_EXISTS after successful creation; a service that shares a named object should validate its expected size and protocol version rather than assuming the first creator’s configuration is correct.
View offsets use allocation granularity
The starting file offset passed to MapViewOfFile must be a multiple of the system’s allocation granularity, which can be larger than the page size. Query it with GetSystemInfo; do not round an arbitrary byte offset to a page boundary and assume the API will accept it. For a logical record at offset wanted, align the mapping offset downward to the allocation granularity, then retain a delta from the returned view base to the desired byte. Include that delta when checking the view length so the target range fits.
For large files or a process with constrained address space, map a window, process it, unmap it, and map the next window. The entire file does not need to fit in the process’s virtual address space at once. A 32-bit process is especially vulnerable to address-space fragmentation, while a 64-bit process still needs bounded view sizes and a clear ownership policy. The base address is chosen by the system in ordinary MapViewOfFile usage; avoid requiring the same base in different processes because each address space is independent.
The view’s memory protection must be compatible with the mapping object’s protection. FILE_MAP_READ, FILE_MAP_WRITE, and copy-on-write access are different contracts. Copy-on-write modifications are private to the process and do not update the original file as ordinary writable shared views do. Make the sharing intent explicit in the design and test that a second process sees the behavior you expect.
Shared views do not define a shared protocol
Two processes can access coherent data through views of the same local backing file or shared mapping object, but they still need an interprocess synchronization protocol. A pointer in one process is not meaningful in another: the same view can have different base addresses. Store offsets or indices within the mapped region, not absolute pointers. Use a named mutex, semaphore, event, or another documented synchronization primitive to coordinate initialization, record ownership, and shutdown. Define what happens if a process dies while holding a lock or midway through updating a structure.
Use fixed-width fields, an explicit version, and a generation or sequence number in shared data. Avoid native C++ containers, vtables, allocator pointers, and compiler-dependent structure layouts in a cross-process mapping. A robust producer can write a new record body, validate its checksum, then publish a completion marker or advance an atomic sequence under the shared lock. The consumer should reject incomplete or unknown versions rather than interpreting partially written bytes as valid state.
Named mappings are kernel objects with security descriptors and namespace scope. A guessed name is not authorization. Apply an explicit DACL for the intended users or service identities, and keep the object name out of untrusted input. Creating a Global\\ mapping from a non-session-zero session requires the documented privilege; use a local session namespace when cross-session access is not needed. A pagefile-backed mapping created with INVALID_HANDLE_VALUE is useful for shared memory that is not a disk file, but its size consumes system commit capacity and its contents disappear when the relevant object lifetime ends.
Dirty pages are not the same as durable data
Writes through a mapped view dirty memory-backed pages. The system writes those pages to the backing file as part of its cache-management policy, but a successful store instruction does not mean bytes have reached stable storage. FlushViewOfFile requests that dirty pages for a range be written to the file representation. It does not flush file metadata and does not wait for the underlying hardware cache to commit physically. When the application requires that stronger boundary, Microsoft documents calling FlushViewOfFile and then FlushFileBuffers on the file handle.
On a network mapping, flushing proves that the client wrote data from the local machine; it does not prove that the remote server has committed it to physical media. Server caching and storage-controller guarantees remain separate. If a distributed application needs durable transactions, use a storage protocol that explicitly defines commit, recovery, and server acknowledgement rather than treating a memory mapping as a database transaction mechanism.
Flush at the granularity the recovery protocol needs, and store a generation/checksum or journal so startup can distinguish a complete update from a torn one. Multiple processes writing overlapping bytes can still overwrite each other. Memory-mapped access provides a convenient view, not a lock, compare-and-swap protocol for arbitrary fields, or multi-record atomic commit.
Lifetime and cleanup
Keep the file handle alive according to the backing-file and flush requirements of the application. A mapping handle and its mapped views have different lifetimes: after a view is created, closing the original mapping handle does not serve as an explicit unmap of that view. Call UnmapViewOfFile when the process is done with the view, then close the mapping handle and file handle when their owners no longer need them. In a multi-process design, each process owns its own view lifetime and must not assume another process has unmapped first.
Do not truncate, replace, or shrink a mapped file behind active consumers without a protocol that coordinates those changes. A view can become invalid or fault if the backing file’s size no longer supports the range. Use a generation switch or open a new file identity for rotation, notify readers, and retire the old mapping only after users release it. A writer should not casually rename/delete a file that other components expect to keep mapped.
Validation and diagnostics
Test zero-length files, incompatible access/protection flags, offsets that are and are not allocation-granularity aligned, map windows near end-of-file, and large offsets on supported architectures. Run two processes with different base addresses and verify that offset-based references remain valid. Kill one process after writing data but before its publish marker, then confirm the reader rejects or repairs the incomplete generation. Exercise flush errors, remote storage behavior if supported, and file replacement while a view is active.
Track mapping object size, view address/length, aligned file offset, logical delta, access mode, backing file identity, owner, and flush status. Use VirtualQuery to inspect the mapped region when debugging state, but do not treat that snapshot as application ownership or persistence evidence. A production health signal should distinguish reservation failure, view mapping failure, access violation, flush failure, and protocol-level validation failure.
File mappings are an efficient way to work with large files and share bytes between processes, provided the application supplies the missing protocol. Separate object and view lifetimes, align offsets correctly, use offsets rather than pointers across processes, synchronize shared updates, and define the actual durability boundary.
Related:
- Windows VirtualAlloc: Reserve, Commit, Decommit, and Release Correctly
- Windows CreateFile Sharing Semantics: Access, Rename, Delete, and Byte-Range Locks
Sources:
- File Mapping - Microsoft Learn
- Creating a File Mapping Object - Microsoft Learn
- CreateFileMappingW function - Microsoft Learn
- File Mapping Security and Access Rights - Microsoft Learn
- MapViewOfFile function - Microsoft Learn
- Creating a File View - Microsoft Learn
- FlushViewOfFile function - Microsoft Learn
- FlushFileBuffers function - Microsoft Learn
- UnmapViewOfFile function - Microsoft Learn