ReadFileEx / WriteFileEx是否需要lpCompletionRoutine并使用GetOverlappedResult?

时间:2017-05-13 18:53:29

标签: c winapi asynchronous

我想对串行端口上的异步I / O使用 ReadFileEx / WriteFileEx 函数,我不需要 lpCompletionRoutine 参数APC。

1- lpCompletionRoutine参数是否可以设置为 NULL

2-我可以使用 GetOverlappedResult 并将 bWait 参数设置为 TRUE 来阻止,直到 ReadFileEx / WriteFileEx 完成而不是使用 WaitForSingleObjectEx ,因为我已经使用 SetCommTimeouts 设置了通讯超时!!! ???

感谢您的理解。

2 个答案:

答案 0 :(得分:2)

正式答案:

  

lpCompletionRoutine参数是否可以设置为NULL?

即可。此参数是必需的,不能为NULL。如果您在成功通话ReadFileEx后等待警报状态,则会调用 lpCompletionRoutine 。所以如果你传递NULL - 这个地址将被调用。但是,如果您永远不会在警报状态下等待,那么您可以抓住这个错误。但是如果你要添加这种代码,或者系统本身将在这种状态下间接等待(比如你在新的Windows版本中调用一些api或一些更改) - 你突然崩溃了 - 离调用ReadFileEx很远绝对不相关的代码 - 并且将长期而艰苦地研究为什么会发生这种情况。

  

我可以使用GetOverlappedResult并将bWait参数设置为TRUE   阻止,直到ReadFileEx / WriteFileEx完成而不是使用   WaitForSingleObjectEx

是的,您可以执行此操作(如果来自OVERLAPPED == 0 hEvent )。 GetOverlappedResult如果操作仍未完成,则在您致电GetOverlappedResult时(通过Internal成员OVERLAPPEDSTATUS_PENDING 进行比较来确定) - 来自OVERLAPPED hEvent 的函数调用WaitForSingleObject[Ex]如果它不为NULL ,否则它将等待 hFile

但来自ReadFileEx

  

ReadFileEx函数忽略OVERLAPPED结构的hEvent   构件。

这意味着它不会将其传递给ZwReadFile(作为第二个参数 Event ),并且当i / o完成时,内核不会设置此事件。但是当ReadFileEx忽略 hEvent 成员时,GetOverlappedResult不会忽略它并使用,如果它不是NULL。所以它必须是NULL。在这种情况下,GetOverlappedResult将与您的 hFile 一起工作(等待)并且一切正常(当操作完成时,i / o子系统在FILE_OBJECT中设置内部事件)

ReadFileExReadFile之间的区别是什么?这两个函数都是ZwReadFile上的瘦外壳 - ,你可以直接从用户调用它 - 它更强大。你如何看待ZwReadFile有更多参数比较ReadFile [Ex]。这些参数如何从ReadFile[Ex]传递到ZwReadFile

  1. ReadFilehEvent的{​​{1}}次传递OVERLAPPED ZwReadFile )但Event [in, optional]始终传递0 ReadFile[Ex] Event
  2. ZwReadFile - 显然ApcRoutine [in, optional]在这里传递0,但是ReadFile在这里传递了一些内部例程 ReadFileEx(总是,即使你的 lpCompletionRoutine 为0)。此例程将 BaseIoCompletion[Simply] 代码从PIO_STATUS_BLOCK第二个参数)转换为win32错误(通过 致电RtlNtStatusToDosError)。而不是打电话给你 lpCompletionRoutine 它取自NTSTATUS - 第一个参数)。如果它为0 - 那么0并将被调用。众所周知 结果
  3. 重要提示 - 如果 IOCP 端口绑定到您的文件(通过调用 BindIoCompletionCallbackCreateThreadpoolIoCreateIoCompletionPort或通过设置 FileCompletionInformation - 您无法使用ApcContext (不是0) - 这是I / O完成的互斥方法。和 你从i / o子系统获得了ApcRoutine
  4. STATUS_INVALID_PARAMETER - 指向您的ApcContext [in, optional]传递指针 lpOverlapped 在这里。因此你可以把它(指针)放回FileIOCompletionRoutineIoCompletionCallback,或者 直接致电ReadFile

    ZwRemoveIoCompletion lpCompletionRoutine 传递给您。从那以后 ReadFileEx存根将其作为 ApcContext 返回 用来调用原来的 lpCompletionRoutine (即使它是0)

  5. BaseIoCompletion[Simply] IoStatusBlock此处将指针传递给 lpOverlapped 无条件。如果不是0,则ReadFileEx传递 lpOverlapped 。否则将其分配到堆栈中,作为局部变量 并使用此指针。由此得出ReadFile在这种情况下 ( lpOverlapped == 0 )在操作未完成之前无法返回 - 因为IO_STATUS_BLOCK必须有效,而i / o不完整, 但是函数返回后局部变量无效。
  6. 传递给 ReadFile Buffer [out] 的内容显然是
  7. Length [in] - 如果 lpOverlapped 不是0,则分配了本地变量ByteOffset [in, optional](好吧,当然 声明(如此分配)总是,我只是说它使用 case)并从 Offset OffsetHigh 成员初始化 重叠。如果 lpOverlapped 为0(如果LARGE_INTEGER ByteOffset)则为NULL 指针传递为 ReadFile
  8. 重要提示。如果文件以异步模式打开(使用  如果我们使用CreateFile或使用ByteOffset,则会FILE_FLAG_OVERLAPPED标记  WSA_FLAG_OVERLAPPED如果我们使用WSASocket(或  socket)或者我们不使用 FILE_SYNCHRONOUS_IO_NONALERT  ZwCreateFileZwOpenFile中的FILE_SYNCHRONOUS_IO_ALERT个标记  ZwLockFile ByteOffset 强制性参数  (命名管道和邮件槽文件类型除外)。如果它是0 - i / o  子系统返回STATUS_INVALID_PARAMETER。结果   lpOverlapped 参数不能为NULL - 因为(7)。这在ReadFile

    的文档中说
      

    如果使用FILE_FLAG_OVERLAPPED打开hFile,则lpOverlapped   参数不能为NULL。它必须指向有效的OVERLAPPED   结构

  9. Key [in, optional] - 始终为0,我们在调用ReadFile[Ex]时无法控制此项。如果需要,请查看LockFileEx 密钥参数 理解这个参数。 win32 shell NT_ERROR(status)是 限制。
  10. 在调用ZwReadFile并返回 NTSTATUS状态后 -

    • ReadFileEx检查NT_WARNING(status)是否属实 转换并设置win32错误并返回 FALSE 否则返回 TRUE 。所以说STATUS_PENDING这个函数返回TRUE。更有意思的是NT_SUCCESS(status)(这是真的 错误状态)它也返回TRUE。但是我从来没有看过案例 ZwReadFile返回此范围的状态,但是一些自定义驱动程序 当然可以做到这一点
    • ReadFile必须具有STATUS_PENDING的特殊情况 lpOverlapped 是0如何在(5)中解释。所以它调用ZwWaitForSingleObject(hFile..)等待操作完成 而不是使用IO_STATUS_BLOCK

      中的状态

      如果 lpOverlapped 不是NULL - ReadFile不等待自己。它 如果不是SetCommTimeouts,则将状态转换为win32错误 返回FALSE。它还检查STATUS_PENDING并返回 在这种情况下也FALSE(并将最后一个错误设置为ERROR_IO_PENDING )。否则返回TRUE。错误状态存在特殊情况 - STATUS_END_OF_FILE

        

      当同步读取操作到达文件末尾时,ReadFile   返回TRUE并将* lpNumberOfBytesRead设置为零。   但是为什么这个特例呢?为什么不将其转换为ERROR_HANDLE_EOF并返回FALSE

    请注意,如果是串行文件句柄,当我们调用SetCommTimeouts - 串行驱动程序时,如果超时已到期 - 取消读取操作并返回STATUS_TIMEOUT作为最终状态。但这不是错误状态。结果ReadFileReadFileEx 丢失此状态。它会返回TRUE并且不会将ERROR_TIMEOUT设置为上次错误,或者不会使用此代码调用FileIOCompletionRoutine。但使用NOERROR代码。因此,如果使用win32 api,则不会本机确定您的读取操作以超时结束。需要检查字节读取。但是,如果使用ZwReadFile - 没有任何问题 - 我们在STATUS_TIMEOUT中获得了IO_STATUS_BLOCK。并且将确切地知道超时是。

    这是什么结论?主读操作将在内核模式下,在驱动程序代码中。它是同步还是同步 in driver - 我们几乎无法控制。司机可以忽略FO_SYNCHRONOUS_IO中的FILE_OBJECT标记。但是,即使您以同步模式打开文件,大多数驱动程序也会异步处理I / O操作(包括读取)。并返回STATUS_PENDING。 i / o子系统已检查此特殊状态,等待如果挂起,则返回以同步模式打开文件。以及ZwReadFile内部的所有这一切。在内核中。因此,ReadFileReadFileEx之间没有区别。只有 ZwReadFile之后返回 - ReadFileEx只会返回给您。当ReadFile等待返回并且 lpOverlapped 为0.但是我们通过 lpOverlapped 完全控制此行为时 - 将其设置为指向有效OVERLAPPED结构的指针 - 而ReadFile永远不会等待。

    所以读取的同步或异步行为不是由select ReadFileReadFileEx确定,而是在哪种模式(使用哪些标志)打开文件。 (驱动程序我怎么说有时可以忽略异步句柄并同步处理i / o)。在ReadFile的情况下通过 lpOverlapped (0或不是0)(但这里完全确定了)

    调用IOCTL_SERIAL_SET_TIMEOUTS或{{3}}(SetCommTimeouts只是将此ioctl发送给驱动程序)对文件有效。并且在此之后绝对没有区别 - 使用你ReadFileReadFileEx - 在超时的情况下都会给你相同的结果。

    和last - 如果你想在挂起返回后等待操作完成 - 这实际上就是同步I / O.在这种情况下,你不需要在异步模式下打开文件 - 这对于这种代码来说是毫无意义的。只需打开同步并使用ReadFile

答案 1 :(得分:1)

在调用ReadFileEx时,您必须提供有效的完成例程,因为如果您提供的地址无效,您的程序将在处理完APC后立即崩溃。 (或者,如果您不处理APC,则存在太多排队的I / O完成可能最终会导致问题。除了其他任何内容之外,在调用APC之前,您无法合法地重用I / O缓冲区。 )

在任何情况下,完成例程是告诉I / O完成的唯一方法。如文档中所述,ReadFileEx函数忽略OVERLAPPED结构的hEvent成员。当然,您总是可以将事件对象放在hEvent中,并将ReadFileEx指向一个除了设置事件之外什么都不做的完成例程。

然而,您的原始前提(使用ReadFileEx将避免同步读取的可能性)不正确。确实,在某些情况下,Windows将同步完成名义上的异步I / O操作,但这是由于I / O驱动程序的限制并影响ReadFile {{1以完全相同的方式。

我不清楚你在评论中所说的实际情况是什么,或者你认为是由于意外同步I / O而遇到的问题。串行端口驱动程序可能比大多数人更可能表现出这种行为,因此这可能是导致问题的原因,但您需要找到另一种解决方案。例如,您可以使用ReadFileEx来检测输入是否可用,并设置超时以使I / O无阻塞,这将使I / O语义与Linux提供的语义类似。

如果您不确定如何继续,我建议您提出另一个问题,包括您要实现的目标以及究竟出现了什么问题的详细信息。