Почему GetOverlappedResult дает 0-байтовый результат для ReadDirectoryChangesW? - PullRequest
0 голосов
/ 22 октября 2019

Я написал наблюдатель за файловой системой для нашего проекта. Внезапно он перестал получать события должным образом. Я обнаружил, что после GetOverlappedResult возвращает true, данные результата пусты и возвращаются байты.

Вот как я создаю дескриптор файла для просмотра каталога:

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

Вот как я начинаю смотреть:

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

После того, как я использую WaitForMultipleObjects для ожидания события (их больше одного), вот как я пытаюсь получить результаты:

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

Но внезапно, когда я копирую файл в просматриваемый каталог, происходит событие - но я вижу в отладчике, что _ovlBytesReturned равно 0 и _resultData также просто нули.

Есть ли флаг? Я мог бы попробовать изменить, чтобы это исправить? Я вполне уверен, что раньше это работало, я понятия не имею, что могло измениться.

Я уже пытался изменить значение false на true в GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE) на случай, если возникнет дополнительная необходимость в ожидании. Это не имело никакого эффекта.

1 Ответ

3 голосов
/ 22 октября 2019

FILE_NOTIFY_INFORMATION составляет не менее 16 байтов (для длинных имен файлов 0 wchar_t s), и вы говорите ReadDirectoryChangesW, что у вас есть только 20 байтов в буфере (nBufferLength) - поэтому перекрывающиеся результаты будут иметь проблемы с подгонкой,Используйте sizeof(_resultData) вместо ResultDataSize для nBufferLength - но я думаю, вам следует значительно увеличить размер буфера. 16 * 20 байт не так много, когда вещи начинают происходить.

Также обратите внимание, что вы не можете использовать _resultData[ index+1 ], чтобы перейти к следующему результату. FILE_NOTIFY_INFORMATION - переменная длина, следующий FILE_NOTIFY_INFORMATION на NextEntryOffset байтов вперед (0 означает, что вы достигли последнего перекрывающегося результата).

Вам также необходимо создать и назначить дескриптор события (hEvent) в вашей структуре OVERLAPPED, чтобы GetOverlappedResult() работал.


Вот пример с этими вещами. Я использую WaitForSingleObject, так как у меня есть только это, чтобы ждать, но эта часть должна быть наименьшей из проблем.

#include <Windows.h>

#include <iomanip>
#include <iostream>
#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))
            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 function to get a vector of "Action, Filename" pairs
std::vector<std::pair<wchar_t const*, std::wstring>> 
GetDirectoryChangesResultW(HANDLE hDir, OVERLAPPED& ovl, DWORD* buffer) {
    std::vector<std::pair<wchar_t const*, std::wstring>> retval;

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

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

            if (fni->NextEntryOffset == 0) break; // last entry

            fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(
                reinterpret_cast<char*>(fni) + fni->NextEntryOffset
            );
        } while (true);
    }
    return retval;
}

int main()
{
    try {
        DWORD buffer[4062]; // DWORD-aligned
        DirectoryHandleW hDir(L"C:\\Users\\Ted\\Testing");
        EventHandle hEv;

        while (true) {
            OVERLAPPED ovl{};
            ovl.hEvent = hEv;

            BOOL rdc = ::ReadDirectoryChangesW(
                hDir,
                buffer,
                std::size(buffer),
                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) {
                if (WaitForSingleObject(hEv, INFINITE) == WAIT_OBJECT_0) {

                    auto res = GetDirectoryChangesResultW(hDir, ovl, buffer);
                    for (auto const&[action, filename] : res) {
                        std::wcout << action << L" " << filename << L"\n";
                    }
                }
            }
            else {
                std::wcout << L"ReadDirectoryChangesW failed\n" << std::endl;
            }
        }
    }
    catch (const std::exception& ex) {
        std::cout << ex.what() << "\n";
    }
}
...