Why is GetOverlappedResult giving 0 bytes result for ReadDirectoryChangesW?

荒凉一梦 提交于 2020-03-05 05:08:06

问题


I have written a file system watcher for our project. Suddenly, it stopped getting events properly. I found out, that after GetOverlappedResult returns true, the result data are empty and so is bytes returned.

This is how I create file handle for watching a directory:

_directoryHandle = ::CreateFileA("some path", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

This is how I start watching:

BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
static constexpr DWORD ResultDataSize = 20;
FILE_NOTIFY_INFORMATION _resultData[ResultDataSize] = { 0 };

_watchRequestResult = ::ReadDirectoryChangesW(
  _directoryHandle,
  (LPVOID)_resultData,
  ResultDataSize,
  TRUE,
  FILE_NOTIFY_CHANGE_FILE_NAME,
  NULL,
  &_ovl,
  NULL
);

After I use WaitForMultipleObjects to wait for the event (there's more than one), this is how I try to fetch the results:

DWORD _ovlBytesReturned;
if (::GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE))
{
  // Read results
}

But suddenly when I copy file to watched directory the event fires - but I can see in the debugger that _ovlBytesReturned is 0 and _resultData also is just zeroes.

Is there any flag I could try changing to fix this? I'm quite sure it used to work, I have no idea what could have changed.

I already tried to change false to true in GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE), in case there was additional need for waiting. It did not have any effect.


回答1:


FILE_NOTIFY_INFORMATION is at least 16 bytes (for 0 wchar_ts long filenames) and you tell ReadDirectoryChangesW that you only have 20 bytes in the buffer (nBufferLength) - so overlapped results will have problems to fit. Use sizeof(_resultData) instead of ResultDataSize for the nBufferLength - but I think you should increase the size of the buffer a lot. 16*20 bytes isn't much when stuff starts happening.

Also note that you can't use _resultData[ index+1 ] to get to the next result. FILE_NOTIFY_INFORMATION is variable length, the next FILE_NOTIFY_INFORMATION is NextEntryOffset bytes ahead (with 0 meaning that you're at the last overlapped result).

You also need to create and assign an event handle (hEvent) in your OVERLAPPED structure in order for GetOverlappedResult() to work unless you use a completion routine instead - and the directory handle must be open all the time or you'll miss events.

Pseudo code:

handle = CreateFileW(...FILE_FLAG_OVERLAPPED...);
while(read_directory_changes) {
  ReadDirectoryChangesW();
  WaitForSingleObject() / WaitForMultipleObjects();
  GetOverlappedResult();
}
CloseHandle(handle);

Here's an example with those things in place.

#include <Windows.h>

#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>

// A base class for handles with different invalid values.
template<std::uintptr_t hInvalid>
class Handle {
public:
    Handle(const Handle&) = delete;
    Handle(Handle&& rhs) :
        hHandle(std::exchange(rhs.hHandle, hInvalid))
    {}
    Handle& operator=(const Handle&) = delete;
    Handle& operator=(Handle&& rhs) {
        std::swap(hHandle, rhs.hHandle);
        return *this;
    }

    // converting to a normal HANDLE
    operator HANDLE () { return hHandle; }

protected:
    Handle(HANDLE v) : hHandle(v) {
        // throw if we got an invalid handle
        if (hHandle == reinterpret_cast<HANDLE>(hInvalid))
            throw std::runtime_error("invalid handle");
    }
    ~Handle() {
        if (hHandle != reinterpret_cast<HANDLE>(hInvalid)) CloseHandle(hHandle);
    }
private:
    HANDLE hHandle;
};

using InvalidNullptrHandle = Handle<reinterpret_cast<std::uintptr_t>(nullptr)>;
using InvalidHandleValueHandle =
                Handle<reinterpret_cast<std::uintptr_t>(INVALID_HANDLE_VALUE)>;

// A class for directory handles
class DirectoryHandleW : public InvalidHandleValueHandle {
public:
    DirectoryHandleW(const std::wstring& dir) :
        Handle(
            ::CreateFileW(
                dir.c_str(), FILE_LIST_DIRECTORY,
                FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
                NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
                FILE_FLAG_OVERLAPPED, NULL)
        )
    {}
};

// A class for event handles
class EventHandle : public InvalidNullptrHandle {
public:
    EventHandle() :
        Handle(::CreateEvent(nullptr, true, false, nullptr))
    {}
};

// FILE_NOTIFY_INFORMATION action names
wchar_t const* get_action(DWORD a) {
    static wchar_t const* const Actions[FILE_ACTION_RENAMED_NEW_NAME + 1] = {
        L"Unknown action",
        L"ADDED",
        L"REMOVED",
        L"MODIFIED",
        L"RENAMED_OLD_NAME",
        L"RENAMED_NEW_NAME"
    };

    if (a > FILE_ACTION_RENAMED_NEW_NAME) a = 0;
    return Actions[a];
}

// A stepping function for FILE_NOTIFY_INFORMATION*
bool StepToNextNotifyInformation(FILE_NOTIFY_INFORMATION*& cur) {
    if (cur->NextEntryOffset == 0) return false;
    cur = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(
        reinterpret_cast<char*>(cur) + cur->NextEntryOffset
    );
    return true;
}

// A ReadDirectoryChanges support class
template<size_t Handles=1, size_t BufByteSize = 4096>
class DirectoryChangesReader {
public:
    static_assert(Handles > 0, "There must be room for at least 1 HANDLE");
    static_assert(BufByteSize >= sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH, "BufByteSize too small");
    static_assert(BufByteSize % sizeof(DWORD) == 0, "BufByteSize must be a multiple of sizeof(DWORD)");

    DirectoryChangesReader(const std::wstring& dirname) :
        hDir(dirname),
        ovl{},
        hEv{},
        handles{hEv},
        buffer{std::make_unique<DWORD[]>(BufByteSize/sizeof(DWORD))}
    {}

    // A function to fill in data to use with ReadDirectoryChangesW
    void EnqueueReadDirectoryChanges() {
        ovl = OVERLAPPED{};
        ovl.hEvent = hEv;;
        BOOL rdc = ::ReadDirectoryChangesW(
            hDir,
            buffer.get(),
            BufByteSize,
            TRUE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY,
            NULL,
            &ovl,
            NULL
        );
        if (rdc == 0) throw std::runtime_error("EnqueueReadDirectoryChanges failed");
    }

    // A function to get a vector of <Action>, <Filename> pairs
    std::vector<std::pair<wchar_t const*, std::wstring>>
    GetDirectoryChangesResultW() {
        std::vector<std::pair<wchar_t const*, std::wstring>> retval;

        FILE_NOTIFY_INFORMATION* fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer.get());

        DWORD ovlBytesReturned;
        if (::GetOverlappedResult(hDir, &ovl, &ovlBytesReturned, TRUE)) {
            do {
                retval.emplace_back(
                    get_action(fni->Action),
                    std::wstring{fni->FileName,
                                 fni->FileName + fni->FileNameLength / sizeof(wchar_t)}
                );
            } while (StepToNextNotifyInformation(fni));
        }
        return retval;
    }

    // wait for the handles in the handles array
    DWORD WaitForHandles() {
        return ::WaitForMultipleObjects(Handles, handles, false, INFINITE);
    }

    // access to the handles array
    HANDLE& operator[](size_t idx) { return handles[idx]; }
    constexpr size_t handles_count() const { return Handles; }
private:
    DirectoryHandleW hDir;
    OVERLAPPED ovl;
    EventHandle hEv;
    HANDLE handles[Handles];
    std::unique_ptr<DWORD[]> buffer; // DWORD-aligned
};

int main()
{
    try {
        DirectoryChangesReader dcr(L"C:\\Users\\Ted\\Testing");

        while (true) {
            dcr.EnqueueReadDirectoryChanges();

            DWORD rv = dcr.WaitForHandles();
            if (rv == WAIT_OBJECT_0) {
                auto res = dcr.GetDirectoryChangesResultW();

                std::wcout << L"Got " << res.size() << L" changes\n";
                for (auto const& [action, filename] : res) {
                    std::wcout << action << L" " << filename << L"\n";
                }
            }
            else if (rv > WAIT_OBJECT_0 && rv < WAIT_OBJECT_0 + dcr.handles_count()) {
                // some other event you waited on
                auto event_idx = rv - WAIT_OBJECT_0;
            }
            else {
                std::wcerr << L"Some kind of problem\n";
                break;
            }
        }
    }
    catch (const std::exception& ex) {
        std::cout << ex.what() << "\n";
    }
}


来源:https://stackoverflow.com/questions/58508771/why-is-getoverlappedresult-giving-0-bytes-result-for-readdirectorychangesw

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!