6
votes

It seems that MapViewOfFile increases the reference count of the file mapping kernel object.

Quoted from the MSDN description of MapViewOfFile:

Mapped views of a file mapping object maintain internal references to the object, and a file mapping object does not close until all references to it are released. Therefore, to fully close a file mapping object, an application must unmap all mapped views of the file mapping object by calling UnmapViewOfFile and close the file mapping object handle by calling CloseHandle. These functions can be called in any order.

Also, from Windows via C/C++, 5th Edition:

The preceding code shows the "expected" method for manipulating memory-mapped files. However, what it does not show is that the system increments the usage counts of the file object and the file-mapping object when you call MapViewOfFile...

Despite these, my actual test suggests the opposite. I'm using Visual Studio 2015 on Windows 10 64-bit. The test program is as follows:

#include <windows.h>

int main() {
  HANDLE h = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 128, "test");
  void* p_memory = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 0);
  CloseHandle(h);
  h = OpenFileMappingA(FILE_MAP_WRITE, FALSE, "test");
  DWORD dw = GetLastError(); // ERROR_FILE_NOT_FOUND
}

The OpenFileMapping call failed with the last error ERROR_FILE_NOT_FOUND. When I remove the CloseHandle call, everything would be fine. This implies that the CloseHandle call eliminates the last reference count of the file mapping kernel object and destroys it. This in turn implies that MapViewOfFile does not actually increases the reference count of the object.

I want to make sure what is going on, and what is the exact semantics of MapViewOfFile with respect to reference counting of the file mapping kernel object.

1
Maybe it's an optimization to not increment the handle usage until the mapping is actually opened for the first time? - GSerg
I suspect that the implication in the documentation (that the file mapping object can still be used and/or reopened after the last direct handle to it is closed) was unintentional, and that while you do indeed have to unmap the views in order to release all the related kernel resources, that doesn't mean it is safe to close the handle before you're actually finished using the mapping. (At any rate, if you just keep the handle open, then the behaviour is unambiguous and you won't need to worry about it.) - Harry Johnston
@GSerg I have tried to actually write to the mapped view, and then close the handle. But the problem is still there. - Lingxi

1 Answers

5
votes

You can make it more convincing by using a file as the backing store instead of the paging file:

int main() {
    const char* path = "mmf.bin";
    DeleteFile(path);
    HANDLE hFile = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 
        FILE_FLAG_DELETE_ON_CLOSE,
        NULL, CREATE_NEW, 0, NULL);
    HANDLE h = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 128, "test");
    int* p_memory = (int*)MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 128);
    CloseHandle(h);
    DWORD attr = GetFileAttributes(path);
    if (attr != INVALID_FILE_ATTRIBUTES) puts("File still exists");
    else puts("File is gone");
}

Output: File is gone

So "the system increments the usage counts of the file object" is most definitely not correct. And I think you disproved that it increments the usage count on the file-mapping object. Not sure what to make of this, Richter doesn't get it wrong very often. Nothing in the errata for the book either. It might have worked this way in an earlier version of Windows, not sure since I never got this wrong on purpose. We'll have to stick with what the SDK documentation actually says:

A shared file mapping object will not be destroyed until all processes that use it close their handles to it by using the CloseHandle function.