Michael Spencer
Michael Spencer

Reputation: 1055

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;
    }
  }

}

Upvotes: 6

Views: 1961

Answers (3)

Carey Gregory
Carey Gregory

Reputation: 6846

The problem is your print logic is causing a buffer overrun because fni->FileNameLength is in bytes, not characters. Random memory corruption would explain why I got different results than you.

The fix is simply this:

std::wstring filename(fni->FileName, fni->FileName + fni->FileNameLength / sizoeof(fni->FileName[0]));

Upvotes: 0

Collin Dauphinee
Collin Dauphinee

Reputation: 13973

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.

Upvotes: 5

Carey Gregory
Carey Gregory

Reputation: 6846

How are you compiling this? Using Visual Studio it fails to compile because the third parameter to GetQueuedCompletionStatus is typed incorrectly. The parameter should be a pointer to a pointer to ULONG, not a pointer to ULONG. When I changed the declaration of the "key" variable to

ULONG_PTR key;

the programs works correctly.

Upvotes: 0

Related Questions