Возобновить уведомление о завершении порта после того, как они были остановлены - PullRequest
0 голосов
/ 27 января 2019

В документе MSDN для параметра lpOverlapped GetQueuedCompletionStatus говорится, что приложение может предотвратить уведомление порта завершения, установив младший бит элемента hEvent элемента OVERLAPPED состав. Но можно ли возобновить уведомления после их остановки?

Мне нужно использовать это для мониторинга сетевых папок на предмет изменений:

Когда GetQueuedCompletionStatus возвращает FALSE и GetLastError() возвращает ERROR_NETNAME_DELETED, я делаю это (работает):

di->Overlapped.hEvent = CreateEvent( NULL, FALSE, FALSE, di->lpszDirName );
reinterpret_cast<uintptr_t &>(di->Overlapped.hEvent) |= 0x1;

И когда проблема с сетью была решена, я попытался выполнить обратную операцию, но она НЕ Сработала:

reinterpret_cast<uintptr_t &>(di->Overlapped.hEvent) &= ~(0x1);

(Будет хорошо, если решение будет совместимо с Windows 7)

1 Ответ

0 голосов
/ 27 января 2019

прежде всего уведомление о завершении порта не может быть «приостановлено» или «возобновлено»

Даже если вы передали функцию дескриптор файла, связанный с портом завершения и действительной структурой OVERLAPPEDприложение может предотвратить завершение уведомления порта.Это делается путем указания допустимого дескриптора события для hEvent члена структуры OVERLAPPED и установки его младшего бита.Допустимый дескриптор события, чей младший бит установлен, удерживает завершение ввода-вывода от постановки в очередь на порт завершения.

это означает следующее - когда мы вызываем некоторый интерфейс ввода-вывода win32 (api, которыйвзять указатель на OVERLAPPED в качестве параметра in / out, такого как ReadFile, ReadDirectoryChangesW, LockFileEx и т. д.) и дескриптор файла (переданный в этот API-интерфейс), связанный с портом завершения - несмотря на это, мы можем предотвратить уведомление порта завершениядля это вызов по дескриптору события с младшим битом.это только для конкретного вызова API и не влияет на любые другие вызовы API.и все это не имеет отношения к GetQueuedCompletionStatus

(строго говоря, мы можем просто передать 1 вместо hEvent. Но в этом случае вопрос - как мы получим уведомление о завершении ввода-вывода, если API вернетсяв состоянии ожидания? да возможно ожидание и только для дескриптора файла, позвоните GetOverlappedResult., но это будет правильно только при отсутствии каких-либо других вызовов ввода-вывода для этого файла в параллельном режиме)

в любом случае необходимо понять, как этоэто внутренне работа.все нативные интерфейсы ввода / вывода имеют следующую сигнатуру:

NTSTATUS NTAPI SomeIoApi(
                         _In_ HANDLE FileHandle,
                         _In_opt_ HANDLE Event,
                         _In_opt_ PIO_APC_ROUTINE ApcRoutine,
                         _In_opt_ PVOID ApcContext,
                         _Out_ PIO_STATUS_BLOCK IoStatusBlock, 
                         ...
                         );

у всех есть эти общие 5 параметров в начале.для завершения ввода-вывода очереди в результате этого вызова должны быть выполнены несколько условий.конечно FileHandle должен быть связан с некоторым портом завершения (к этому порту и может быть отправлен пакет).но еще одно обязательное условие - ApcContext должно быть не равно нулю (ApcContext != 0).если эти 2 условия выполнены, и устройство не возвращает состояние ошибки (если FILE_SKIP_COMPLETION_PORT_ON_SUCCESS установлено в файле - должно быть только состояние ожидания) - при завершении ввода / вывода - указатель ApcContext будет перемещен в порт.и затем он может быть удален с помощью

NTSTATUS
NTAPI
NtRemoveIoCompletion(
    _In_ HANDLE IoCompletionHandle,
    _Out_ PVOID *KeyContext,
    _Out_ PVOID *ApcContext,
    _Out_ PIO_STATUS_BLOCK IoStatusBlock,
    _In_opt_ PLARGE_INTEGER Timeout
    );

или с помощью win32 shell GetQueuedCompletionStatus.

, поэтому решение для не отправленного пакета в порт (даже если дескриптор файла связан с портом завершения) -установить ApcContext = 0.Слой win32 делает это следующим образом (псевдокод):

BOOL WINAPI SomeWin32Api(
                         HANDLE FileHandle,
                         LPOVERLAPPED lpOverlapped,
                         LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
             )
{

    HANDLE hEvent = lpOverlapped->hEvent;
    PVOID ApcContext = lpOverlapped;

    if ((ULONG_PTR)hEvent & 1)
    {
        reinterpret_cast<uintptr_t&>(hEvent) &= ~1;
        ApcContext = 0;
    }

    NTSTATUS status = SomeIoApi(
        FileHandle, 
        hEvent, 
        lpCompletionRoutine, // not exactly, but by sense
        ApcContext, 
        (PIO_STATUS_BLOCK)lpOverlapped,...);
}

он проверяет младший бит hEvent в OVERLAPPED - если он установлен - передает 0 на место ApcContext в противном случае передает lpOverlapped (указатель на OVERLAPPED) в качестве контекста (ApcContext = lpOverlapped;)

обратите внимание, что слой nt пропускает любой указатель void* как ApcContext.но слой win32 всегда передает здесь указатель на OVERLAPPED структуру или 0. потому что this и GetQueuedCompletionStatus возвращают этот указатель обратно как _Out_ LPOVERLAPPED *lpOverlapped (сравните с NtRemoveIoCompletion - возвращайте как _Out_ PVOID *ApcContext)

в любом случае этоТрюк влияет только на конкретный одиночный вызов ввода-вывода win32, и если вы поздно сбрасываете младший бит в hEvent из перекрывающегося (reinterpret_cast<uintptr_t &>(di->Overlapped.hEvent) &= ~(0x1);), это уже не может иметь никакого эффекта - 0 на месте ApcContext уже пройдено.

также в общем виде это редко случается, когда мы связываем дескриптор файла с портом завершения, но не хотим использовать его при некоторых вызовах.обычно это еще один вызов API.например, мы можем создать дескриптор асинхронного файла, связать его с портом завершения.и использовать уведомления порта в вызове WriteFile, но перед началом записи мы можем установить / удалить сжатие файла через FSCTL_SET_COMPRESSION.Поскольку файл является асинхронным, FSCTL_SET_COMPRESSION также может завершаться асинхронно, но мы можем запретить уведомление о завершении порта для этого ioctl, вместо этого ждать на месте (по событию) для его завершения.для такой ситуации и может быть использован этот трюк.

и в большинстве случаев приложения (если это не сервер с огромным количеством запросов ввода / вывода) могут вместо этого вручную вызвать GetQueuedCompletionStatus, связать обратный вызов с файлом через BindIoCompletionCallback или CreateThreadpoolIo.В качестве системы результатов вы создаете iocp, пул потоков, который будет прослушивать этот iocp (через GetQueuedCompletionStatus или NtRemoveIoCompletion) и затем вызывать ваш обратный вызов.это очень упрощает ваш код и логику src


выводы:

  • я почти уверен (несмотря на то, что не вижу ваш код), что вам вообще не нужно использовать трюк с низким уровнем событиябит порядка 1082
  • , если вы используете этот трюк в каком-либо запросе ввода / вывода (скажем, ReadDirectoryChangesW)это влияет только на этот конкретный запрос
  • Вы не можете изменить поведение путем сброса младшего бита в дескрипторе события после отправки запроса или любым другим способом
  • , который вам вообще не нужен GetQueuedCompletionStatus и сам поток потоков вообще.вместо этого просто позвоните BindIoCompletionCallback для файла
...