停止后恢复完成端口通知

时间:2019-01-27 13:55:08

标签: c++ windows asynchronous winapi overlapped-io

在MSDN文档中,GetQueuedCompletionStatuslpOverlapped参数表明,应用程序可以通过设置{的hEvent成员的低位来防止完成端口通知。 {1}}结构。但是,在通知停止后是否可以恢复?

我需要使用它来监视网络文件夹的更改:

OVERLAPPED返回GetQueuedCompletionStatus并且FALSE返回GetLastError()时,我这样做(有效):

ERROR_NETNAME_DELETED

当网络问题解决后,我尝试执行反向操作-但它不起作用:

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

(如果该解决方案与Windows 7兼容,那就很好了

1 个答案:

答案 0 :(得分:2)

首先不能将“完成”端口通知“挂起”或“恢复”

  

即使您已通过函数传递了与   完成端口和有效的OVERLAPPED结构,应用程序可以   阻止完成端口通知。通过指定一个   hEvent结构的OVERLAPPED成员的有效事件句柄,   并设置其低位。一个有效的事件句柄,其低序   设置该位可防止I / O完成从排队到完成   端口。

这意味着下一个-当我们调用某些win32 I / O api(将指向OVERLAPPED的指针作为输入/输出参数的api,例如ReadFileReadDirectoryChangesW,{{ 1}}等)和与完成端口相关联的文件句柄(传递给此api)-尽管如此,我们仍可以通过事件句柄以低序位阻止 this 调用的完成端口通知。这仅用于具体的api调用,不会影响其他任何api调用。与LockFileEx

无关的所有

(严格地说,我们也可以在地方GetQueuedCompletionStatus处简单地传递1。但是在这种情况下,问题是-如果api返回未决状态,我们如何获得有关I / O的通知?仅在文件句柄上,调用hEvent。但这仅在同时不对该文件进行任何其他I / O调用的情况下才是正确的)

在任何情况下都需要了解其内部工作方式。所有本机I / O api都具有下一个签名:

GetOverlappedResult

在开始时都具有这个共同的5个参数。对于此调用导致的队列I / O完成,必须满足几个条件。当然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, ... ); 必须与某个完成端口关联(到该端口,并且可以分组发送)。但是一个强制条件-FileHandle必须不为零(ApcContext)。如果满足这2个条件并且设备未返回错误状态(如果在文件上设置了FILE_SKIP_COMPLETION_PORT_ON_SUCCESS-仅必须处于待处理状态)-当I / O完成时-ApcContext != 0指针将被推到端口。然后可以通过

将其删除
ApcContext

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

对于未将数据包发送到端口的解决方案(即使文件句柄也与完成端口相关联)-设置GetQueuedCompletionStatus。 win32层以另一种方式(伪代码)执行此操作:

ApcContext = 0

它检查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的低位-如果已置位,则在原位传递0 OVERLAPPED,否则传递ApcContext(指向lpOverlapped的指针)作为上下文(OVERLAPPED

请注意,nt层让任何ApcContext = lpOverlapped;指针作为void*传递。但是win32层始终在此处传递指向ApcContext结构或0的指针。因为this和OVERLAPPED将此指针返回为GetQueuedCompletionStatus(与_Out_ LPOVERLAPPED *lpOverlapped比较-返回为{{1} })

无论如何,该技巧仅影响具体的单个win32 I / O调用,并且如果您从重叠的NtRemoveIoCompletion中延迟_Out_ PVOID *ApcContext中的低位重新设置,则已经无效-0位置hEvent已通过。

从一般的角度来看,当我们将文件句柄与完成端口相关联但不希望在某些调用中使用它时,这种情况很少发生。通常这是另一个api调用。例如,我们可以创建异步文件句柄,并将其与完成端口关联。并在调用reinterpret_cast<uintptr_t &>(di->Overlapped.hEvent) &= ~(0x1);中使用端口通知,但是在开始写入之前,我们可以通过ApcContext设置/删除文件压缩。由于文件是异步的,因此WriteFile也可以完成异步,但是我们可以希望阻止此ioctl的完成端口通知,而是就地(在事件上)等待它完成。在这种情况下,可以使用此技巧。

,在大多数情况下,应用程序(如果不是具有大量I / O请求的服务器)可以改为手动调用FSCTL_SET_COMPRESSION,通过FSCTL_SET_COMPRESSIONGetQueuedCompletionStatus将回调绑定到文件。作为您创建iocp的结果系统,将在此iocp上侦听线程池(通过BindIoCompletionCallbackCreateThreadpoolIo),然后调用回调。这非常简化了您的src代码和逻辑


发现:

  • 我几乎确定(尽管不查看代码)根本不需要 将技巧与事件低序位一起使用
  • 如果您在某些I / O请求中使用此技巧(例如GetQueuedCompletionStatus) 仅影响此特定请求
  • 在发生事件时无法通过重置低位来更改行为 发送请求后或通过其他方式处理
  • 您通常不需要使用NtRemoveIoCompletion和自线程 池。相反,只需调用ReadDirectoryChangesW获取文件