Search code examples
c++winapireaddirectorychangesw

When calling ReadDirectoryChangesW, only the first call returns any changes (both sync and async)


The following is a minimal program which uses ReadDirectoryChangesW. The problem I am having is that only the first call to GetQueuedCompletionStatus returns. The second time through the loop it blocks forever no matter how many changes are made to the directory.

I have also attempted using the synchronous version and have the exact same problem.

#include <array>
#include <cassert>
#include <iostream>
#include <Windows.h>

int main() {
  // Open the directory to monitor.
  HANDLE dir = ::CreateFileA(
      "G:\\Program Files (x86)\\Steam\\steamapps\\common\\eve online"
    , FILE_LIST_DIRECTORY
    , FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
    , NULL
    , OPEN_EXISTING
    , FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED
    , NULL
    );

  if (dir == INVALID_HANDLE_VALUE) {
    std::cout << "Failed to open directory for change notifications!\n";
    return 1;
  }

  // Setup IOCP.
  HANDLE iocp = ::CreateIoCompletionPort(
      dir
    , NULL
    , NULL
    , 1
    );

  // Monitor.
  while (true) {
    std::array<char, 1024 * 8> buf;
    DWORD bytes_read;
    OVERLAPPED overlapped;
    std::memset(&overlapped, 0, sizeof(overlapped));
    BOOL result = ::ReadDirectoryChangesW(
        dir
      , &buf.front()
      , buf.size()
      , false
      , FILE_NOTIFY_CHANGE_FILE_NAME // Includes file creation.
      , &bytes_read
      , &overlapped
      , NULL
      );

    if (result == FALSE) {
      DWORD error = ::GetLastError();
      std::cout << "Call to ReadDirectoryChangesW failed! " << error << "\n";
      return 1;
    }

    // Wait for completion.
    ULONG_PTR key;
    LPOVERLAPPED overlapped_result;

    result = ::GetQueuedCompletionStatus(
        iocp
      , &bytes_read
      , &key
      , &overlapped_result
      , INFINITE
      );

    if (result == FALSE) {
      std::cout << "Call to GetQueuedCompletionStatus failed!\n";
      return 1;
    }

    // Print results!
    for (FILE_NOTIFY_INFORMATION *fni =
           reinterpret_cast<FILE_NOTIFY_INFORMATION *>(&buf.front());
         ;
         fni = reinterpret_cast<FILE_NOTIFY_INFORMATION *>(
           reinterpret_cast<char *>(fni) + fni->NextEntryOffset)) {
      std::wstring filename(fni->FileName, fni->FileName + fni->FileNameLength);
      std::wcout << "Got change: " << filename.c_str() << "\n";

      if (fni->NextEntryOffset == 0) break;
    }
  }

}

Solution

  • A few problems.

    First, you're trying to output multi-byte string literals to wcout. You should turn them into wide strings by prepending L.

    Second, the FileNameLength variable represents the length of the name in bytes, not characters. You should divide it by 2 to get the number of characters.