Platform SDK: Memory

Two Examples of File Mapping

If you want to view a portion of the file that does not start at the beginning of the file, you must create a file-mapping object. This object is the size of the portion of the file you want to view plus the offset into the file. For example, if you want to view the 1 kilobyte (1K) that begins 131,072 bytes (128K) into the file, you must create a file-mapping object of at least 132,096 bytes (129K) in size. The view would start 131,072 bytes (128K) into the file and extend for at least 1,024 bytes. This example assumes a file allocation granularity of 64K.

File allocation granularity affects where a map view can start. A map view must start at an offset into the file that is a multiple of the file allocation granularity. So the data you want to view may be the file offset modulo the allocation granularity into the view. The size of the view would be the offset of the data modulo the allocation granularity, plus the size of the data you want to examine.

If you wanted to examine 1K of data offset 138,240 bytes (135K) into the file, assuming that the GetSystemInfo function indicates an allocation granularity of 64K, you would:

  1. Create a file-mapping object of at least 139,264 bytes (136K) in size.
  2. Create a file view that starts at a file offset that is the largest multiple of the file allocation granularity less than the offset you require. In this case, the file view would start at offset 131,072 (128K) into the file. The view would be 139264 bytes (136K) minus 131,072 bytes (128K), or 8,192 bytes (8K), in size.
  3. Create a pointer offset 7K into the view to access the 1K in which you were interested.

If the data you want straddles a file allocation granularity boundary, you could make the view larger than the file allocation granularity. This would avoid breaking the data into pieces.

The following program illustrates the second example above.

/* filemap.c,

   This program demonstrates file mapping, especially how to align a view
   with the system file allocation granularity.

#include <windows.h>

#define BUFFSIZE 1024 // the size of the memory we examine at any one time

#define FILE_MAP_START 138240 // starting point within the file of the
 // data we're interested in (135K)


/* Our test file. We create and populate it in the code below, so there is
   no need to supply it in advance. */

char * lpcTheFile = "fmtest.txt"; // the file to be manipulated


int main(void)
{
  HANDLE hMapFile;              // handle for our test file's memory-mapped region
  HANDLE hFile;                 // the file handle
  BOOL bFlag;                   // a result holder
  DWORD dBytesWritten;          // number of bytes written
  DWORD dwFileSize;             // temporary storage for file sizes
  DWORD dwFileMapSize;          // size of the file mapping
  DWORD dwMapViewSize;          // the size of the view
  DWORD dwFileMapStart;         // where in the file we start the file map view
  DWORD dwSysGran;              // system allocation granularity
  SYSTEM_INFO SysInfo;          // system information; used to get the granularity
  LPVOID lpMapAddress;          // pointer to the base address of the memory-mapped region
  char * pData;                 // pointer to the data
  int i;                        // loop counter
  int iData;                    // the first int of the data, if everything goes right
  int iViewDelta;               // the offset into the view where our data shows up
  int iRet;                     // our return value

  // Create the test file. We open it "Create Always" to overwrite any
  // existing file. We re-create the data below.
  hFile = CreateFile(lpcTheFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, 
                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (hFile == INVALID_HANDLE_VALUE) {
    printf("hFile is NULL\n");
    printf("Target file is %s\n", lpcTheFile);
    return 4;
  }

  // Get the system allocation granularity. It is bad form to make
  // assumptions when we can ask the operating system.
  GetSystemInfo(&SysInfo);
  dwSysGran = SysInfo.dwAllocationGranularity;

  // Now we calculate a few variables. We will be working with a small
  // enough file that we can afford to ignore the fact that file offsets
  // are 64-bit values. We will work only with the low-order 32 bits of
  // each file offset. However, that could be dangerous. What you really
  // should do is calculate the file offsets as 64-bit values, and then
  // slice and dice them for the function calls. Ignoring the high-order
  // 32 bits is crude and would obscure what we are trying to show here,
  // two more reasons why we didn't do it.

  // Where do we start the file mapping? Calculate it by rounding down the
  // offset of the data into the file to the nearest multiple of the
  // system allocation granularity. Note: we could assume that the
  // granularity is always a power of two and use an AND mask. That would
  // save us a multiply and a divide. But that may be a dangerous
  // assumption, so we won't.
  dwFileMapStart = (FILE_MAP_START / dwSysGran) * dwSysGran;
  printf ("The file map view starts at %ld bytes into the file.\n",
          dwFileMapStart);

  // Calculate the size of the file mapping view.
  dwMapViewSize = (FILE_MAP_START % dwSysGran) + BUFFSIZE;
  printf ("The file map view is %ld bytes large.\n", dwMapViewSize);

  // How large will the file-mapping object be?
  dwFileMapSize = FILE_MAP_START + BUFFSIZE;
  printf ("The file-mapping object is %ld bytes large.\n", dwFileMapSize);

  // The data we're interested in won't show up at the beginning of the
  // view, so we need to determine how far into the view to set our
  // pointer.
  iViewDelta = FILE_MAP_START - dwFileMapStart;
  printf ("The data is %d bytes into the view.\n", iViewDelta);

  // Now we write a file with data suitable for our experiments. This
  // provides unique int (4-byte) locations in the file for easy visual
  // inspection. We'll be lazy and not check for storage medium overflow
  // or other errors, which you really should do. Since an int is 4 bytes,
  // the value at the pointer to the data should be one quarter of the
  // desired offset into the file.
  for (i=0;i<2*dwSysGran;i++) {
    WriteFile (hFile, &i, sizeof (i), &dBytesWritten, NULL);
  }

  // Let the user know that the resulting file is more than large enough
  // for the experiment.
  dwFileSize = GetFileSize(hFile,  NULL);
  printf("hFile size: %10d\n", dwFileSize);

  // Now create a file-mapping object for the file.
  hMapFile = CreateFileMapping( hFile, // current file handle
                                NULL, // default security
                                PAGE_READWRITE, // read/write permission
                                0,  // size of mapping object, high
                                dwFileMapSize, // size of mapping object, low
                                NULL); // name of mapping object

  if (hMapFile == NULL) {
    printf("hMapFile is NULL: last error: %d\n", GetLastError() );
    return (2);
  }

  // Map the view, and test our results.

  lpMapAddress = MapViewOfFile(hMapFile, // handle to mapping object
                               FILE_MAP_ALL_ACCESS, // read/write permission 
                               0, // high-order 32 bits of file offset
                               dwFileMapStart, // low-order 32 bits of file offset
                               dwMapViewSize); // number of bytes to map
  if (lpMapAddress == NULL) {
    printf("lpMapAddress is NULL: last error: %d\n", GetLastError());
    return 3;
  }

  // Calculate the pointer to the data.
  pData = (char *) lpMapAddress + iViewDelta;

//    dump((char *)pData, BUFFSIZE);

  // Extract the data, an int. Cast the pointer pData from a "pointer to
  // char" to a "pointer to int" so we get the whole thing.
  iData = *(int *)pData;

  printf ("The value at the pointer is %d,\nwhich %s one quarter of the desired file offset.\n",
          iData,
          iData*4 == FILE_MAP_START ? "is" : "is not");

  // Strictly speaking, we could return, and let the glue code close our
  // open file and file-mapping object. But it is bad practice to do that,
  // so we close them explicitly. Anyway, this is demonstration code, so
  // we might as well demonstrate closing these, too. :-)

  bFlag = CloseHandle(hMapFile); // close the file-mapping object

  if(!bFlag) {
    printf("\nOops! Error # %ld occurred closing the mapping object!",
           GetLastError());
  }

  bFlag = CloseHandle(hFile);   // close the file itself

  if(!bFlag) {
    printf("\nOops! Error # %ld occurred closing the file!",
           GetLastError());
  }

  return 0;

}