С++ Мониторинг изменений файлов с помощью ReadDirectoryChangesW не запускает все действия?

У меня есть приложение, которое должно отслеживать любые изменения в определенном каталоге. Я использую следующий код, однако, когда я, например, переименовываю файл, действие FILE_ACTION_RENAMED_NEW_NAME не запускается. На самом деле, срабатывает UNDISCOVERED ACTION get, что приводит к неожиданному поведению? Что я здесь делаю неправильно?

char Dir[] = "DIRPATH";
HANDLE hDir = CreateFile(
    Dir,
    FILE_LIST_DIRECTORY,
    FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS,
    NULL);
int nCounter = 0;
FILE_NOTIFY_INFORMATION strFileNotifyInfo[1024];
FILE_NOTIFY_INFORMATION *fni = NULL;

while (TRUE)
{
    //strFileNotifyInfo = NULL;
    DWORD dwBytesReturned = 0;
    if (ReadDirectoryChangesW(hDir, (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL) == 0)
    {
        Exit(GetLastErrorAsString());
    }
    else
    {
        char fileName[MAX_PATH] = ""; 
        DWORD offset = 0;
        do {
            fni = (FILE_NOTIFY_INFORMATION*)(&strFileNotifyInfo[offset]);
            int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName, fni->FileNameLength / sizeof(WCHAR), fileName, sizeof(fileName), NULL, NULL);
            string Test = Dir;
            Test += "\\";
            Test += fileName;
            switch (fni->Action) {
                case FILE_ACTION_ADDED:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory added: " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File added: " << Dir << "\\" << fileName << endl;
                        }
                    break;
                case FILE_ACTION_MODIFIED:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory modified: " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File modified: " << Dir << "\\" << fileName << endl;
                        }
                    break;
                case FILE_ACTION_REMOVED:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory removed: " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File removed: " << Dir << "\\" << fileName << endl;
                        }
                    break;
                case FILE_ACTION_RENAMED_NEW_NAME:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory renamend (NEW): " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File renamed (NEW): " << Dir << "\\" << fileName << endl;
                        }
                    break;
                case FILE_ACTION_RENAMED_OLD_NAME:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory renamed (OLD): " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File renamed (OLD): " << Dir << "\\" << fileName << endl;
                        }
                    break;
                default:
                    if (boost::filesystem::is_directory(Test)) {
                        cout << "Directory UNDISCOVERED ACTION: " << Dir << "\\" << fileName << endl;
                        }
                    else {
                        cout << "File UNDISCOVERED ACTION: " << Dir << "\\" << fileName << endl;
                        }
                    break;
                }
            ::memset(fileName, '\0', sizeof(fileName));
            offset += fni->NextEntryOffset;
            }
        while (fni->NextEntryOffset != 0);
        cout << "Loop: " << nCounter++ << endl;
    }
}

Некоторый пример вывода при переименовании файла с file.txt на file2.txt на карте с именем: MAP:

File renamed (OLD): DIRPATH\MAP\file.txt
Directory UNDISCOVERED ACTION: DIRPATH
Loop: 0
Directory modified: DIRPATH\MAP
Loop: 1

person TVA van Hesteren    schedule 22.07.2017    source источник
comment
Прочтите The Old New Thing: blogs.msdn.microsoft. com/oldnewthing/20110812-00/?p=9913   -  person Richard Critten    schedule 22.07.2017
comment
Особенно обратите внимание на абзац в конце статьи, на которую ссылается Ричард: Read­Directory­ChangesW может выйти из строя с ERROR_NOTIFY_ENUM_DIR, если во внутреннем буфере не хватит места. В этом случае вы можете вернуться к циклу FindFirstFile/FindNextFile.   -  person Michael Geary    schedule 22.07.2017


Ответы (1)


FILE_NOTIFY_INFORMATION не является структурой фиксированного размера. Он представляет собой заголовок фиксированного размера, за которым следует имя файла — строка переменного размера.

&strFileNotifyInfo[1] указывает на некоторое смещение в середине имени файла, следующего за strFileNotifyInfo[0]. Его значения — это просто неправильно истолкованные участки памяти, заполненные символами имени файла; по сути случайный мусор.

Вместо этого вы должны использовать FILE_NOTIFY_INFORMATION::NextEntryOffset для поиска следующего экземпляра FILE_NOTIFY_INFORMATION в буфере.

Ваш код будет выглядеть примерно так:

BYTE buffer[4096];
ReadDirectoryChangesW(hDir, buffer, sizeof(buffer), ...);
BYTE* p = buffer;
for (;;) {
  FILE_NOTIFY_INFORMATION* info =
      reinterpret_cast<FILE_NOTIFY_INFORMATION*>(p);

  // Work with `info` as necessary

  if (!info->NextEntryOffset) break;  // this was last entry
  p += info->NextEntryOffset;
}
person Igor Tandetnik    schedule 22.07.2017
comment
Ну, я использую NextEntryOffset для перехода к следующему экземпляру FILE_NOTIFY_INFORMATION, верно? увидеть конец do-loop? Пожалуйста, дайте мне знать, если я вас неправильно понял? - person TVA van Hesteren; 22.07.2017
comment
NextEntryOffset — это смещение в байтах, а не в FILE_NOTIFY_INFORMATION элементах. Однако strFileNotifyInfo[offset] интерпретирует это как последнее. Другими словами, буфер, который вы передаете ReadDirectoryChangesW, представляет собой массив байтов. По возвращении он содержит FILE_NOTIFY_INFORMATION заголовков с нерегулярными интервалами, а между ними фрагменты текста переменной длины. Это не массив из FILE_NOTIFY_INFORMATION элементов, расположенных рядом. - person Igor Tandetnik; 22.07.2017
comment
Спасибо за объяснение. Я провел небольшое исследование и, кажется, понял, что вы имеете в виду. Однако, как я могу решить эту проблему? - person TVA van Hesteren; 22.07.2017
comment
Добавил пример. - person Igor Tandetnik; 22.07.2017
comment
@IgorTandetnik, спасибо за пример! info.NextEntryOffset должно стать info->NextEntryOffset правильно? Я проверяю, или я могу заставить его работать с вашим примером - person TVA van Hesteren; 22.07.2017
comment
@IgorTandetnik, здорово! Теперь у меня все работает, как и ожидалось! Большое спасибо. Возможно, вы также можете помочь мне в том, как отслеживать несколько каталогов одновременно? - person TVA van Hesteren; 22.07.2017
comment
Либо создайте по одному потоку для каждого каталога, либо научитесь использовать перекрывающийся ввод-вывод. - person Igor Tandetnik; 22.07.2017