Использование ReadDirectoryChangesW асинхронно в цикле

Я пытаюсь использовать ReadDirectoryChangesW асинхронно в цикле.

Ниже приведен фрагмент кода, который я пытаюсь достичь:

DWORD example()
{
DWORD error = 0;

OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

if (NULL == ovl.hEvent) return ::GetLastError();

char buffer[1024];

while(1)
{
process_list_of_existing_files();

error = ::ReadDirectoryChangesW(
m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);

// we have new files, append them to the list
if(error) append_new_files_to_the_list(buffer);
// just continue with the loop
else if(::GetLastError() == ERROR_IO_PENDING) continue;
// RDCW error, this is critical -> exit
else return ::GetLastError();
}
}

Я не знаю, как справиться со случаем, когда ReadDirectoryChangesW возвращается FALSE с GetLastError() код ERROR_IO_PENDING,

В этом случае я должен просто продолжить цикл и продолжать цикл до ReadDirectoryChangesW возвращается buffer Я могу обработать.

Я пытался использовать WaitForSingleObject(ovl.hEvent, 1000) но вылетает с ошибкой 1450 ERROR_NO_SYSTEM_RESOURCES, Ниже MVCE, который воспроизводит это поведение:

#include <iostream>
#include <Windows.h>

DWORD processDirectoryChanges(const char *buffer)
{
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;

do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);

switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << fileName << std::endl;
}
break;
default:
break;
}

::memset(fileName, '\0', sizeof(fileName));
offset += fni->NextEntryOffset;

} while (fni->NextEntryOffset != 0);

return 0;
}

int main()
{
HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();

OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

if (NULL == ovl.hEvent) return ::GetLastError();

DWORD error = 0, br;
char buffer[1024];

while (1)
{
error = ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);

if (0 == error)
{
error = ::GetLastError();

if (ERROR_IO_PENDING != error)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}

error = ::WaitForSingleObject(ovl.hEvent, 0);

switch (error)
{
case WAIT_TIMEOUT:
break;
case WAIT_OBJECT_0:
{
error = processDirectoryChanges(buffer);

if (error > 0)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}

if (0 == ::ResetEvent(ovl.hEvent))
{
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
break;
default:
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
break;
}
}

return 0;
}

Читая документацию, кажется, что мне нужно GetOverlappedResult с последним параметром, установленным в FALSE но я не знаю, как правильно использовать этот API.

Поскольку MVCE очень хорошо иллюстрирует то, что я пытаюсь сделать (напечатать имена вновь добавленных файлов), можете ли вы показать мне, что должно быть исправлено в while цикл для того, чтобы он работал?

Опять же, дело в том, чтобы использовать ReadDirectoryChangesW асинхронно, в цикле, как показано во фрагменте ВВЕДЕНИЯ.

0

Решение

Базовая структура вашей программы выглядит более или менее нормально, вы просто используете асинхронные вызовы ввода / вывода неправильно. Всякий раз, когда нет новых файлов, время ожидания для обработчика события сразу же истекает, и это нормально, но вы затем выдаете новый запрос ввода-вывода, который не отвечает.

Вот почему у вас заканчиваются системные ресурсы; вы отправляете запросы ввода-вывода на полный наклон, не дожидаясь завершения какого-либо из них. Вы должны только оформить новый запрос после того, как существующий запрос был выполнен.

(Кроме того, вы должны вызывать GetOverlappedResult, чтобы проверить, был ли ввод / вывод успешным или нет.)

Так что ваш цикл должен выглядеть примерно так:

    ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);

while (1)
{
DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);

switch (result)
{
case WAIT_TIMEOUT:

processBackgroundTasks();

break;

case WAIT_OBJECT_0:

::GetOverlappedResult(hDir, &ovl, &dw, FALSE);

processDirectoryChanges(buffer);

::ResetEvent(ovl.hEvent);

::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);

break;
}
}

Заметки:

  • Обработка ошибок была исключена для простоты; Я не проводил никакого тестирования и не проверял ваш код на наличие других проблем.

  • Если, возможно, нет каких-либо фоновых задач для выполнения, вы должны проверить для этого случая и установить время ожидания INFINITE, а не 0, когда это происходит, в противном случае вы будете вращаться.

  • Я хотел показать только минимальные изменения, необходимые для его работы, но вызов WaitForSingleObject с последующим GetOverlappedResult является избыточным; один вызов GetOverlappedResult может одновременно проверить, завершен ли ввод-вывод, и получить результаты, если это так.


По запросу модифицированная версия использует только GetOverlappedResult и с минимальной проверкой ошибок. Я также добавил пример того, как вы можете справиться со случаем, когда у вас закончилась работа; если какая-либо обработка, которую вы выполняете над файлами, действительно выполняется вечно, вам это не нужно.

    ::ResetEvent(ovl.hEvent);

if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}

while (1)
{
BOOL wait;

result = process_list_of_existing_files();

if (result == MORE_WORK_PENDING)
{
wait = FALSE;
}
else if (result == NO_MORE_WORK_PENDING)
{
wait = TRUE;
}

if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
{
error = GetLastError();
if (error == ERROR_IO_INCOMPLETE) continue;
fail();
}

processDirectoryChanges(buffer);

::ResetEvent(ovl.hEvent);

if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
}
3

Другие решения

вариант косвенного использования IOCP

  1. создать унаследованный класс / структуру (содержащий) OVERLAPPED (или
    IO_STATUS_BLOCK), счетчик ссылок, дескриптор каталога и данные, которые
    тебе нужно
  2. вызов BindIoCompletionCallback (RtlSetIoCompletionCallback) за
    дескриптор каталога, для настройки вашего обратного вызова
  3. иметь DoRead() рутина, которую мы впервые вызываем из основного потока
    а затем от обратного вызова
  4. в DoRead() перед каждым звонком ReadDirectoryChangesW вызов
    AddRef(); потому что мы передаем ссылку (через OVERLAPPED) нашим
    структура к ядру
  5. main (скажем, в GUI-потоке) может продолжать выполнять свою задачу после первоначального вызова
    DoRead()В отличие от варианта APC ему не нужно ждать в аварийном состоянии
  6. в обратном вызове мы получили указатель на нашу структуру из унаследованных (содержащих)
    ПЕРЕКРЫВАЕТСЯ. делать какие-либо задачи (processDirectoryChanges) ,если нужно
    продолжить шпионский звонок DoRead() и, наконец, вызвать Release ()
  7. если ReadDirectoryChangesW от DoRead() ошибка (в результате не будет обратного вызова) — нам нужен прямой обратный вызов
    с кодом ошибки
  8. для остановки мы можем просто закрыть дескриптор каталога — в результате мы получили
    STATUS_NOTIFY_CLEANUP в обратном вызове

==================================

//#define _USE_NT_VERSION_

class SPYDATA :
#ifdef _USE_NT_VERSION_
IO_STATUS_BLOCK
#else
OVERLAPPED
#endif
{
HANDLE _hFile;
LONG _dwRef;
union {
FILE_NOTIFY_INFORMATION _fni;
UCHAR _buf[PAGE_SIZE];
};

void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};

buf = _buf;

for (;;)
{
DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);

ULONG NextEntryOffset = pfni->NextEntryOffset;

if (!NextEntryOffset)
{
break;
}

pb += NextEntryOffset;
}
}

#ifdef _USE_NT_VERSION_
static VOID WINAPI _OvCompRoutine(
_In_    NTSTATUS dwErrorCode,
_In_    ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PIO_STATUS_BLOCK Iosb
)
{
static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
}
#else
static VOID WINAPI _OvCompRoutine(
_In_    DWORD dwErrorCode, // really this is NTSTATUS
_In_    DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
}
#endif

VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
{
DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);

if (0 <= status)
{
if (status != STATUS_NOTIFY_CLEANUP)
{
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
process_list_of_existing_files();// so hard do this here ?!?
DoRead();
}
else
{
DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
}
}

Release();
MyReleaseRundownProtection();
}

~SPYDATA()
{
Cancel();
}

public:

void DoRead()
{
if (MyAcquireRundownProtection())
{
AddRef();
#ifdef _USE_NT_VERSION_
NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
if (NT_ERROR(status))
{
OvCompRoutine(status, 0);
}
#else
if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
{
OvCompRoutine(RtlGetLastNtStatus(), 0);
}
#endif
}
}

SPYDATA()
{
_hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
_dwRef = 1;
#ifndef _USE_NT_VERSION_
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
}

void AddRef()
{
InterlockedIncrement(&_dwRef);
}

void Release()
{
if (!InterlockedDecrement(&_dwRef))
{
delete this;
}
}

BOOL Create(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
if (0 <= status)
{
return
#ifdef _USE_NT_VERSION_
0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
}
return FALSE;
}

void Cancel()
{
if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
{
NtClose(hFile);
}
}
};

void DemoF()
{
if (MyInitializeRundownProtection())
{
STATIC_OBJECT_ATTRIBUTES(oa, "<SOME_DIRECTORY>");

if (SPYDATA* p = new SPYDATA)
{
if (p->Create(&oa))
{
p->DoRead();
}

//++ GUI thread run
MessageBoxW(0, L"wait close program...", L"", MB_OK);
//-- GUI thread end

p->Cancel();

p->Release();
}

MyWaitForRundownProtectionRelease();
}
}
1

По вопросам рекламы [email protected]