You can use an OVERLAPPED structure with synchronous I/O, too

Raymond Chen

Raymond

Even if you didn’t open a file with FILE_FLAG_OVERLAPPED, you can still use the OVERLAPPED structure when you issue reads and writes. Mind you, the I/O will still complete synchronously, but you can take advantage of the other stuff that OVERLAPPED has to offer.

Specifically, you can take advantage of the Offset and OffsetHigh members to issue the I/O against a file location different from the current file pointer. (This is a file pointer in the sense of Set­File­Pointer and not in the sense of the C runtime FILE*.) If your program does a lot of reads and writes to random locations in a file, using the synchronous OVERLAPPED structure saves you a call to Set­File­Pointer at each I/O.

Let’s illustrate this by writing some code to walk through a file format that contains a lot of offsets to other parts of the file: The ICO file format. First, the old-fashioned way:

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <pshpack1.h>
struct ICONDIRHEADER {
    WORD idReserved;
    WORD idType;
    WORD idCount;
};
struct ICONDIRENTRY {
    BYTE bWidth;
    BYTE bHeight;
    BYTE bColorCount;
    BYTE  bReserved;
    WORD  wPlanes;
    WORD  wBitCount;
    DWORD dwBytesInRes;
    DWORD dwImageOffset;
};
#include <poppack.h>
BOOL ReadBufferAt(__in HANDLE hFile,
    __out_bcount(cbBuffer) void *pvBuffer,
    DWORD cbBuffer,
    DWORD64 offset)
{
 LARGE_INTEGER li;
 DWORD cbRead;
 li.QuadPart = offset;
 return SetFilePointerEx(hFile, li, nullptr, FILE_BEGIN) &&
        ReadFile(hFile, pvBuffer, cbBuffer, &cbRead, nullptr) &&
        cbBuffer == cbRead;
}
int __cdecl wmain(int argc, wchar_t **argv)
{
 HANDLE hFile = CreateFile(argv[1], GENERIC_READ,
  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  nullptr, OPEN_EXISTING, 0, nullptr);
 if (hFile != INVALID_HANDLE_VALUE) {
  ICONDIRHEADER hdr;
  if (ReadBufferAt(hFile, &hdr, sizeof(hdr), 0) &&
      hdr.idReserved == 0 && hdr.idType == 1) {
   for (UINT uiIcon = 0; uiIcon < hdr.idCount; uiIcon++) {
    ICONDIRENTRY entry;
    if (ReadBufferAt(hFile, &entry, sizeof(entry),
                     sizeof(hdr) + uiIcon * sizeof(entry))) {
     void *pvData = LocalAlloc(LMEM_FIXED, entry.dwBytesInRes);
     if (pvData) {
      if (ReadBufferAt(hFile, pvData,
                       entry.dwBytesInRes, entry.dwImageOffset)) {
       // process one image in the icon
      }
      LocalFree(pvData);
     }
    }
   }
  }
  CloseHandle(hFile);
 }
 return 0;
}

Run this program with the name of an icon file on the command line, and nothing interesting happens because the program doesn’t generate any output. But if you step through it, you can see that we start by reading the ICON­DIR­HEADER to verify that it’s an icon and determine the number of images. We then loop through the images: For each one, we read the ICON­DIR­ENTRY (specifying the explicit file offset), then read the image data (again, specifying the explicit file offset).

We use the Read­Buffer­At function to read data from the file. For each read, we first call Set­File­Pointer to position the file pointer at the byte we want to read, then call Read­File to read it.

Let’s change this program to take advantage of our newfound knowledge:

BOOL ReadBufferAt(__in HANDLE hFile,
    __out_bcount(cbBuffer) void *pvBuffer,
    DWORD cbBuffer,
    DWORD64 offset)
{
 OVERLAPPED o = { 0 };
 o.Offset = static_cast<DWORD>(offset);
 o.OffsetHigh = static_cast<DWORD>(offset >> 32);
 DWORD cbRead;
 return ReadFile(hFile, pvBuffer, cbBuffer, &cbRead, &o) &&
        cbBuffer == cbRead;
}

We merge the Set­File­Pointer call into the Read­File by specifying the desired byte offset in the optional OVERLAPPED structure. The I/O will still complete synchronously (since we opened the handle synchronously), but we saved ourselves the hassle of having to call two functions when it could be done with just one.

0 comments

Comments are closed.