ReadDirectoryChangesW помещает только одно событие в буфер FILE_NOTIFY_INFORMATION - PullRequest
0 голосов
/ 23 января 2020

У меня проблема с тем, что 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
  }
}

Теперь вы видите, что я перезапускаю запрос на ReadDirectoryChangesW до Я обрабатываю массив MyFileInfoStruct , Но проблема в том, что если копируется более двух файлов, второй файл регистрируется событием, пока я обрабатываю предыдущий, но последующие изменения игнорируются, пока я не "заберу" последнее изменение и перезапущу событие.

Я мог бы частично это сделать, имея второй поток для обработки всей информации из части FILE_CHANGES . Но это только снижает вероятность пропуска событий, поскольку весь запрос на запуск -> wait -> забирает -> событие перезапуска рутина немного быстрее. На самом деле он не обеспечивает 100% покрытия, все еще есть момент, когда запрос ReadDirectoryChangesW не рассматривается.

Я много читал о inte rnet и нашел два решения, которые часто упоминаются:

  • Использовать отдельный поток для обработки изменений файла (я это уже сделал)
  • Увеличить размер FILE_NOTIFY_INFORMATION[]. это не работает для меня, windows помещает туда только одно событие

Таким образом, вопрос заключается в следующем: как мне получить ReadDirectoryChangesW и GetOverlappedResult, чтобы продолжать добавлять изменения файлов в FILE_NOTIFY_INFORMATION[] Буфер, пока я "забрать" результаты, позвонив GetOverlappedResult? это вообще возможно? Кому-нибудь удалось получить несколько результатов в один буфер?

1 Ответ

2 голосов
/ 24 января 2020

Для переименования файла произошло два действия: FILE_ACTION_RENAMED_OLD_NAME и FILE_ACTION_RENAMED_NEW_NAME. Эти два события действия вы можете получить, вызвав один раз ReadDirectoryChanges и GetOverlappedResult, как вы уже сделали.

enter image description here

enter image description here

У меня проблема с тем, что ReadDirectoryChangesW хранит пропущенные события.

Для захвата событий, таких как копия два файлы и удалить два файла, например, сначала я копирую TESTA.txt и TESTB.txt в каталог D:\testFolder, затем удаляю их обоих. Я могу получить все события, позвонив ReadDirectoryChanges и GetOverlappedResult в while l oop. Обе функции вызываются четыре раза для событий четверки.

enter image description here

Тестовый код выглядит следующим образом:

#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 два раза вместо одного.

...