прежде всего уведомление о завершении порта не может быть «приостановлено» или «возобновлено»
Даже если вы передали функцию дескриптор файла, связанный с портом завершения и действительной структурой 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
для файла