为什么 GetOverlappedResult 为 ReadDirectoryChangesW 提供 0 字节结果?
Why is GetOverlappedResult giving 0 bytes result for ReadDirectoryChangesW?
我已经为我们的项目编写了一个文件系统观察器。突然,它停止正确获取事件。我发现,在 GetOverlappedResult
returns 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
}
但是当我将文件复制到 watched 目录时突然触发了事件 - 但我可以在调试器中看到 _ovlBytesReturned
是 0
并且 _resultData
也只是零。
有没有我可以尝试更改的标志来解决这个问题?我很确定它曾经有效,我不知道会发生什么变化。
我已经在 GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE)
中尝试将 false 更改为 true,以防需要额外等待。没有任何效果。
FILE_NOTIFY_INFORMATION
至少有 16 个字节(对于 0 wchar_t
s 长文件名),你告诉 ReadDirectoryChangesW
你在缓冲区中只有 20 个字节(nBufferLength
) - 所以重叠的结果会出现问题。对于 nBufferLength
使用 sizeof(_resultData)
而不是 ResultDataSize
- 但我认为您应该大量增加缓冲区的大小。当事情开始发生时,16*20 字节并不多。
另请注意,您无法使用 _resultData[ index+1 ]
获取下一个结果。 FILE_NOTIFY_INFORMATION
是可变长度,下一个 FILE_NOTIFY_INFORMATION
是前面的 NextEntryOffset
个字节(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";
}
}
我已经为我们的项目编写了一个文件系统观察器。突然,它停止正确获取事件。我发现,在 GetOverlappedResult
returns 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
}
但是当我将文件复制到 watched 目录时突然触发了事件 - 但我可以在调试器中看到 _ovlBytesReturned
是 0
并且 _resultData
也只是零。
有没有我可以尝试更改的标志来解决这个问题?我很确定它曾经有效,我不知道会发生什么变化。
我已经在 GetOverlappedResult(GetDirectoryHandle(), &_ovl, &_ovlBytesReturned, FALSE)
中尝试将 false 更改为 true,以防需要额外等待。没有任何效果。
FILE_NOTIFY_INFORMATION
至少有 16 个字节(对于 0 wchar_t
s 长文件名),你告诉 ReadDirectoryChangesW
你在缓冲区中只有 20 个字节(nBufferLength
) - 所以重叠的结果会出现问题。对于 nBufferLength
使用 sizeof(_resultData)
而不是 ResultDataSize
- 但我认为您应该大量增加缓冲区的大小。当事情开始发生时,16*20 字节并不多。
另请注意,您无法使用 _resultData[ index+1 ]
获取下一个结果。 FILE_NOTIFY_INFORMATION
是可变长度,下一个 FILE_NOTIFY_INFORMATION
是前面的 NextEntryOffset
个字节(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";
}
}