1

我有一个问题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.hEventusing 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
  }
}

现在您可以看到我在处理数组ReadDirectoryChangesW 之前重新启动了请求。MyFileInfoStruct但问题是,如果复制了两个以上的文件,第二个文件在我处理前一个文件时被事件注册,但随后的更改被忽略,直到我“拾取”最后一个更改并重新启动事件。

我可以通过让第二个线程来处理来自 FILE_CHANGES部分的所有信息来部分解决这个问题。但这只会通过发出整个启动请求来减少错过事件的机会->等待->拾取->重新启动事件例程更快一些它实际上并没有提供 100% 的覆盖率,仍然存在没有ReadDirectoryChangesW待处理请求的时刻。

我在互联网上阅读了很多内容,发现经常提到两种解决方案:

  • 使用单独的线程来处理文件更改(我已经这样做了)
  • 增加FILE_NOTIFY_INFORMATION[]. 这对我不起作用,Windows 只在那里放置一个事件

因此问题是:我如何获得ReadDirectoryChangesWGetOverlappedResult继续在FILE_NOTIFY_INFORMATION[]缓冲区中添加文件更改,直到我通过调用“拾取”结果GetOverlappedResult?这甚至可能吗?有没有人设法将多个结果放入一个缓冲区?

4

1 回答 1

3

对于重命名文件,发生了两个操作:FILE_ACTION_RENAMED_OLD_NAMEFILE_ACTION_RENAMED_NEW_NAME. 这两个动作事件您可以通过一次调用来检索ReadDirectoryChangesGetOverlappedResult就像您已经完成的那样。

在此处输入图像描述

在此处输入图像描述

我有一个问题,即 ReadDirectoryChangesW 不断丢失事件。

要捕获复制两个文件和删除两个文件等事件,例如,我首先将TESTA.txtTESTB.txt复制到目录D:\testFolder,然后将它们都删除。我可以通过调用ReadDirectoryChanges和循环GetOverlappedResult来获取所有事件。while这两个函数都被四次事件调用了四次。

在此处输入图像描述

测试代码如下:

#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();
}

概括:

GetOverlappedResult函数:

GetOverlappedResult 函数报告的结果是指定句柄最后一次重叠操作的结果。

因此重命名文件是重叠(重命名)操作,复制文件是重叠(复制)操作。但是,复制两个文件是两个重叠(复制)操作。所以它需要调用GetOverlappedResult两次而不是一次。

于 2020-01-24T08:09:23.837 回答