ReadDirectoryChangesW 仅将单个事件放入 FILE_NOTIFY_INFORMATION 缓冲区
ReadDirectoryChangesW only places single event in the FILE_NOTIFY_INFORMATION buffer
我有一个问题,ReadDirectoryChangesW
总是丢失事件。
我做了很多谷歌搜索,根据我的搜索,波纹管函数参数似乎是正确的,但没有人能确定。我开始这样看。
BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
_ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);
_directoryHandle = ::CreateFileA("some path here", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
// This should be quite enough to fit multiple file events
static constexpr DWORD ResultDataLength = 10000;
// Byte size used for winapi calls and memcpy during move operation
static constexpr DWORD ResultDataByteSize = ResultDataLength * sizeof(FILE_NOTIFY_INFORMATION);
FILE_NOTIFY_INFORMATION _resultData[ResultDataLength] = { 0 };
_watchRequestResult = ::ReadDirectoryChangesW(
_directoryHandle,
(LPVOID)_resultData,
ResultDataByteSize,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL,
&_ovl,
NULL
);
在上面之后,我等待 _ovl.hEvent
使用 WaitForMultipleObjects
。我使用多个对象,因为总是有事件告诉监视线程退出。
如果 ovl.hEvent
收到通知,我会这样做:
DWORD _ovlBytesReturned = 0;
// Imagine some struct that I use to pass the file info, not important how it looks
std::vector<MyFileInfoStruct> results;
if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
{
int byteIndex = 0;
bool previousWasRename = false;
const int minSize = min(ResultDataLength, _ovlBytesReturned);
while (byteIndex < minSize)
{
FILE_NOTIFY_INFORMATION* info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
byteIndex += info->NextEntryOffset;
// read the stuff in the info
results.push_back(MyFileInfoStruct::FromFileInfo(info));
// If next entry index is 0, it means there is no next entry
if (info->NextEntryOffset == 0)
{
break;
}
}
}
// if file is renamed, merge new name and old name to same result. However rename works to give me two FILE_NOTIFY_INFORMATION that both contain expected data
MergeResultRename(results)
// results is always 1 item long
此时我应该注意,info->NextEntryOffset
并不总是 0 - 如果我重命名一个文件,我会在 _resultData
中正确地得到两个条目,一个用于新文件名,一个用于旧文件名。
永远不会得到的是每个事件的多个文件更改。这是一个问题,整个代码看起来像这样(伪代码)
Let EVENTS be an array of HANDLE event objects
Let FILE_CHANGES be a buffer of file changes
while(shouldBeWatching)
{
Wait for events from previous iteration stored in EVENTS array. Skip on first iteration.
if(event has fired)
{
if(event that fired is ovl.hEvent)
{
Put file changes from the event that fired into FILE_CHANGES array (seen in 2nd code sample above)
Delete and close all handles related to the event:
Close directory handle
Close ovl.hEvent
}
else
{
Close everything and quit thread.
}
}
Start new request (seen above in 1st code sample)
if(FILE_CHANGES is not empty)
{
Process all info from FILE_CHANGES
}
}
现在您可以看到,在处理 MyFileInfoStruct
数组之前,我重新启动了 ReadDirectoryChangesW
的请求。但问题是,如果复制了两个以上的文件,第二个文件在我处理前一个文件时被事件注册,但后续的更改将被忽略,直到我 "pick up" 最后一个更改并重新启动事件。
我可以通过让第二个线程执行 处理来自 FILE_CHANGES 部分的所有信息来部分解决这个问题。但这只会通过使整个 start request -> wait -> pick up[=58] 来减少错过事件的机会=] -> restart event 程序快一点。它实际上并没有提供 100% 的覆盖率,仍然有一段时间没有 ReadDirectoryChangesW
请求待处理。
我在网上看了很多,发现经常提到两个解决方案:
- 使用单独的线程来处理文件更改(我已经这样做了)
- 增加
FILE_NOTIFY_INFORMATION[]
的大小。这对我不起作用,windows 只在那里放置一个事件
因此问题是:如何让 ReadDirectoryChangesW
和 GetOverlappedResult
继续在 FILE_NOTIFY_INFORMATION[]
缓冲区中添加文件更改,直到我 "pick up" 通过调用 GetOverlappedResult
?这甚至可能吗?有没有人设法将多个结果放入一个缓冲区?
要重命名文件,需要执行两个操作:FILE_ACTION_RENAMED_OLD_NAME
and FILE_ACTION_RENAMED_NEW_NAME
. These two action events you can retrieve via once calling of ReadDirectoryChanges
and GetOverlappedResult
正如您已经完成的那样。
I have a problem that ReadDirectoryChangesW keeps missing events.
要捕获复制两个文件和删除两个文件的事件,例如,首先我复制TESTA.txt和TESTB.txt 到目录 D:\testFolder
,然后将它们都删除。我可以通过在 while
循环中调用 ReadDirectoryChanges
and GetOverlappedResult
来获取所有事件。对于四次事件,这两个函数被调用了四次。
测试代码如下:
#include <windows.h>
#include <vector>
using namespace std;
typedef struct TEST_INFO {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[100];
}_TEST_INFO;
int main()
{
BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
_ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);
HANDLE _directoryHandle = ::CreateFileA("d:\testFolder", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
// This should be quite enough to fit multiple file events
static constexpr DWORD ResultDataSize = 100;
_TEST_INFO _resultData[ResultDataSize] = { 0 };
while (true)
{
_watchRequestResult = ::ReadDirectoryChangesW(
_directoryHandle,
(LPVOID)_resultData,
ResultDataSize * sizeof(_TEST_INFO),
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL,
&_ovl,
NULL
);
DWORD _ovlBytesReturned = 0;
if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
{
int byteIndex = 0;
while (TRUE)
{
_TEST_INFO* info = reinterpret_cast<_TEST_INFO*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
byteIndex += info->NextEntryOffset;
wprintf(L"File name: %s, ", info->FileName);
printf("Action: ");
switch (info->Action)
{
case FILE_ACTION_ADDED:
printf("Added \n");
break;
case FILE_ACTION_REMOVED:
printf("Removed \n");
break;
case FILE_ACTION_MODIFIED:
printf("Modified \n");
break;
case FILE_ACTION_RENAMED_OLD_NAME:
printf("Rename old name \n");
break;
case FILE_ACTION_RENAMED_NEW_NAME:
printf("Rename new name \n");
break;
}
// If next entry index is 0, it means there is no next entry
if (info->NextEntryOffset == 0)
{
break;
}
}
}
}
getchar();
}
总结:
The results reported by the GetOverlappedResult function are those of
the specified handle's last overlapped operation.
所以重命名文件是重叠(重命名)操作,复制文件是重叠(复制)操作。但是复制两个文件是两个重叠(复制)操作。所以它需要调用 GetOverlappedResult
两次而不是一次。
我有一个问题,ReadDirectoryChangesW
总是丢失事件。
我做了很多谷歌搜索,根据我的搜索,波纹管函数参数似乎是正确的,但没有人能确定。我开始这样看。
BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
_ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);
_directoryHandle = ::CreateFileA("some path here", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
// This should be quite enough to fit multiple file events
static constexpr DWORD ResultDataLength = 10000;
// Byte size used for winapi calls and memcpy during move operation
static constexpr DWORD ResultDataByteSize = ResultDataLength * sizeof(FILE_NOTIFY_INFORMATION);
FILE_NOTIFY_INFORMATION _resultData[ResultDataLength] = { 0 };
_watchRequestResult = ::ReadDirectoryChangesW(
_directoryHandle,
(LPVOID)_resultData,
ResultDataByteSize,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL,
&_ovl,
NULL
);
在上面之后,我等待 _ovl.hEvent
使用 WaitForMultipleObjects
。我使用多个对象,因为总是有事件告诉监视线程退出。
如果 ovl.hEvent
收到通知,我会这样做:
DWORD _ovlBytesReturned = 0;
// Imagine some struct that I use to pass the file info, not important how it looks
std::vector<MyFileInfoStruct> results;
if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
{
int byteIndex = 0;
bool previousWasRename = false;
const int minSize = min(ResultDataLength, _ovlBytesReturned);
while (byteIndex < minSize)
{
FILE_NOTIFY_INFORMATION* info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
byteIndex += info->NextEntryOffset;
// read the stuff in the info
results.push_back(MyFileInfoStruct::FromFileInfo(info));
// If next entry index is 0, it means there is no next entry
if (info->NextEntryOffset == 0)
{
break;
}
}
}
// if file is renamed, merge new name and old name to same result. However rename works to give me two FILE_NOTIFY_INFORMATION that both contain expected data
MergeResultRename(results)
// results is always 1 item long
此时我应该注意,info->NextEntryOffset
并不总是 0 - 如果我重命名一个文件,我会在 _resultData
中正确地得到两个条目,一个用于新文件名,一个用于旧文件名。
永远不会得到的是每个事件的多个文件更改。这是一个问题,整个代码看起来像这样(伪代码)
Let EVENTS be an array of HANDLE event objects
Let FILE_CHANGES be a buffer of file changes
while(shouldBeWatching)
{
Wait for events from previous iteration stored in EVENTS array. Skip on first iteration.
if(event has fired)
{
if(event that fired is ovl.hEvent)
{
Put file changes from the event that fired into FILE_CHANGES array (seen in 2nd code sample above)
Delete and close all handles related to the event:
Close directory handle
Close ovl.hEvent
}
else
{
Close everything and quit thread.
}
}
Start new request (seen above in 1st code sample)
if(FILE_CHANGES is not empty)
{
Process all info from FILE_CHANGES
}
}
现在您可以看到,在处理 MyFileInfoStruct
数组之前,我重新启动了 ReadDirectoryChangesW
的请求。但问题是,如果复制了两个以上的文件,第二个文件在我处理前一个文件时被事件注册,但后续的更改将被忽略,直到我 "pick up" 最后一个更改并重新启动事件。
我可以通过让第二个线程执行 处理来自 FILE_CHANGES 部分的所有信息来部分解决这个问题。但这只会通过使整个 start request -> wait -> pick up[=58] 来减少错过事件的机会=] -> restart event 程序快一点。它实际上并没有提供 100% 的覆盖率,仍然有一段时间没有 ReadDirectoryChangesW
请求待处理。
我在网上看了很多,发现经常提到两个解决方案:
- 使用单独的线程来处理文件更改(我已经这样做了)
- 增加
FILE_NOTIFY_INFORMATION[]
的大小。这对我不起作用,windows 只在那里放置一个事件
因此问题是:如何让 ReadDirectoryChangesW
和 GetOverlappedResult
继续在 FILE_NOTIFY_INFORMATION[]
缓冲区中添加文件更改,直到我 "pick up" 通过调用 GetOverlappedResult
?这甚至可能吗?有没有人设法将多个结果放入一个缓冲区?
要重命名文件,需要执行两个操作:FILE_ACTION_RENAMED_OLD_NAME
and FILE_ACTION_RENAMED_NEW_NAME
. These two action events you can retrieve via once calling of ReadDirectoryChanges
and GetOverlappedResult
正如您已经完成的那样。
I have a problem that ReadDirectoryChangesW keeps missing events.
要捕获复制两个文件和删除两个文件的事件,例如,首先我复制TESTA.txt和TESTB.txt 到目录 D:\testFolder
,然后将它们都删除。我可以通过在 while
循环中调用 ReadDirectoryChanges
and GetOverlappedResult
来获取所有事件。对于四次事件,这两个函数被调用了四次。
测试代码如下:
#include <windows.h>
#include <vector>
using namespace std;
typedef struct TEST_INFO {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[100];
}_TEST_INFO;
int main()
{
BOOL _watchRequestResult = false;
OVERLAPPED _ovl = { 0 };
_ovl.hEvent = ::CreateEventA(NULL, TRUE, FALSE, NULL);
HANDLE _directoryHandle = ::CreateFileA("d:\testFolder", FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
// This should be quite enough to fit multiple file events
static constexpr DWORD ResultDataSize = 100;
_TEST_INFO _resultData[ResultDataSize] = { 0 };
while (true)
{
_watchRequestResult = ::ReadDirectoryChangesW(
_directoryHandle,
(LPVOID)_resultData,
ResultDataSize * sizeof(_TEST_INFO),
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL,
&_ovl,
NULL
);
DWORD _ovlBytesReturned = 0;
if (::GetOverlappedResult(_directoryHandle, &_ovl, &_ovlBytesReturned, TRUE))
{
int byteIndex = 0;
while (TRUE)
{
_TEST_INFO* info = reinterpret_cast<_TEST_INFO*>(reinterpret_cast<char*>(&_resultData[0]) + byteIndex);
byteIndex += info->NextEntryOffset;
wprintf(L"File name: %s, ", info->FileName);
printf("Action: ");
switch (info->Action)
{
case FILE_ACTION_ADDED:
printf("Added \n");
break;
case FILE_ACTION_REMOVED:
printf("Removed \n");
break;
case FILE_ACTION_MODIFIED:
printf("Modified \n");
break;
case FILE_ACTION_RENAMED_OLD_NAME:
printf("Rename old name \n");
break;
case FILE_ACTION_RENAMED_NEW_NAME:
printf("Rename new name \n");
break;
}
// If next entry index is 0, it means there is no next entry
if (info->NextEntryOffset == 0)
{
break;
}
}
}
}
getchar();
}
总结:
The results reported by the GetOverlappedResult function are those of the specified handle's last overlapped operation.
所以重命名文件是重叠(重命名)操作,复制文件是重叠(复制)操作。但是复制两个文件是两个重叠(复制)操作。所以它需要调用 GetOverlappedResult
两次而不是一次。