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

3

6

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

}
Ducal answered 27/7, 2011 at 21:16 Comment(0)
P
5

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.

Purusha answered 27/7, 2011 at 22:34 Comment(0)
E
0

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.

Espy answered 27/7, 2011 at 21:47 Comment(1)
I'm using VS2010. That change did not fix the problem.Ducal
E
0

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]));

Espy answered 27/7, 2011 at 23:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.