【问题标题】:Why is GetOverlappedResult giving 0 bytes result for ReadDirectoryChangesW?为什么 GetOverlappedResult 为 ReadDirectoryChangesW 提供 0 字节结果?
【发布时间】:2019-10-22 16:37:12
【问题描述】:

我为我们的项目编写了一个文件系统观察器。突然,它停止正确获取事件。我发现,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
}

但突然当我将文件复制到监视目录时,事件触发 - 但我可以在调试器中看到 _ovlBytesReturned0_resultData 也只是零。

我可以尝试更改任何标志来解决此问题吗?我很确定它曾经可以工作,但我不知道会发生什么变化。

我已经尝试将 GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE) 中的 false 更改为 true,以防需要额外等待。它没有任何效果。

【问题讨论】:

  • 我使用 WaitForMultipleObjects - 你不能将它用于 ReadDirectoryChangesW 的等待结果,因为你没有在 OVERLAPPED 中使用任何事件。所以你的代码已经错了
  • 结果无法放入只有 20 字节长的缓冲区。 FILE_NOTIFY_INFORMATION 是一个可变大小的结构,你不能声明它们的数组。

标签: c++ winapi readdirectorychangesw getoverlappedresult


【解决方案1】:

FILE_NOTIFY_INFORMATION 至少有 16 个字节(对于 0 个 wchar_ts 长文件名)并且您告诉 ReadDirectoryChangesW 您在缓冲区中只有 20 个字节(nBufferLength) - 所以重叠的结果会有问题适合.使用sizeof(_resultData) 而不是ResultDataSizenBufferLength - 但我认为你应该增加缓冲区的大小很多。当事情开始发生时,16*20 字节并不多。

另请注意,您不能使用_resultData[ index+1 ] 来获得下一个结果。 FILE_NOTIFY_INFORMATION 是可变长度,下一个 FILE_NOTIFY_INFORMATIONNextEntryOffset 前面的字节(0 表示您处于最后一个重叠的结果)。

您还需要在 OVERLAPPED 结构中创建和分配一个事件句柄 (hEvent) 以使 GetOverlappedResult() 工作,除非您使用完成例程 - 并且目录句柄必须始终打开否则你会错过活动。

伪代码:

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

下面是一个例子。

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

【讨论】:

  • @RbMm 4+4+4 + wchar_t[1] ... 哦,那是 16 ... 对。但是 OP 并没有告诉ReadDirectoryChangesW 缓冲区是 16*20 字节,只有 20 个。是的,它也可能有其他问题,真的。
  • 是的,抱歉,开始时不查看此内容。真的只有ResultDataSize 大小。我还要注意,OP 不能 我使用 WaitForMultipleObjects 来等待事件,因为没有重叠的事件
  • @RbMm 更新了您发现的内容。不幸的是,我现在无法对其进行测试,因此 OP 可能缺少更多内容。
  • 和 GetOverlappedResult() 应该不完全使用该句柄OVERLAPPED 中的事件,GetOverlappedResult 的第一个参数,并且必须是使用的正式文件句柄(不是事件)如果 I/O 尚未完成,我们希望等待并且内部没有事件重叠。主要在WaitForMultipleObjects 中使用此事件(我猜现在在这里使用了 GetDirectoryHandle()),但我同意现在缓冲区太小导致的直接错误
  • 感谢您这样深思熟虑的回答。实际上,我确实在重叠结构中创建了一个事件,只是忘了提及它。但是网上没有太多关于这个问题的好资源,所以我相信事件初始化部分很快就会帮助到其他人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多