【问题标题】:ReadFileEx/WriteFileEx no need to lpCompletionRoutine and use of GetOverlappedResult?ReadFileEx/WriteFileEx 不需要 lpCompletionRoutine 和 GetOverlappedResult 的使用?
【发布时间】:2017-05-13 18:53:28
【问题描述】:

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

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

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

感谢您的理解。

【问题讨论】:

  • @RbMm, ReadFile 不保证总是异步的,有时当配置为异步时,它会同步读取,这可能是个问题。因此,我已切换到 ReadFileEx
  • 你错了。当您不使用 lpCompletionRoutine 时,您完全不了解 Windows 异步 io/ 和 ReadFileReadFileEx 绝对没有区别 - 这是 100%。异步或同步 io 仅取决于您如何打开文件。完全 - 如果您想要这种代码 - 只需将文件打开为 同步 - 为异步 I/O 打开它的内容是什么??跨度>
  • 对于在大多数情况下处理您的文件的驱动程序在您打开文件的模式下没有什么不同。 kernel 中的 i/o 子系统如果文件为同步 i/o 打开并且驱动程序返回挂起状态 - 操作完成后开始等待。如果为异步 i/o 打开文件 - i/o 子系统只会为您返回控制权,即使返回了挂起。以及您在待处理时要做什么?只需等待操作完成。在这种情况下,让 i/o 子系统执行此操作。你自己做什么?
  • @RbMm,我不这么认为。现在,我花了一周的时间终于弄清楚 ReadFile 如果已配置为异步,则不能保证它是异步的。读这个:support.microsoft.com/en-us/help/156932/… 和这个blogs.msdn.microsoft.com/oldnewthing/20110923-00/?p=9563
  • 你又错了。我在这个话题上有丰富的知识。 ReadFileReadFileExZwReadFile 的双壳,只需调用它即可。 ReadFilewait 如果您从 ZwReadFilelpOverlapped == 0 获得待处理状态。但你和想要等待。

标签: c winapi asynchronous


【解决方案1】:

正式的答案:

可以将 lpCompletionRoutine 参数设置为 NULL 吗?

没有。该参数是强制性的,不能为NULL。 lpCompletionRoutine 将在成功调用ReadFileEx 后在alertable 状态等待时调用。因此,如果您传递 NULL - 此地址将被调用。但是,如果您永远不会在 alertable 状态下等待,那么您可以并且不会捕获此错误。但是如果你不添加这种代码,或者系统本身将间接地等待这种状态(比如你调用了一些 api 或者在新的 Windows 版本中进行了一些更改) - 你突然崩溃了 - 与绝对无关的调用 ReadFileEx 相去甚远代码 - 并且将进行长期而艰苦的研究为什么会发生这种情况。

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

是的,你可以这样做(如果 hEvent 来自OVERLAPPED== 0)。 GetOverlappedResult 如果操作仍未完成,当您调用 GetOverlappedResult 时(它通过将 OVERLAPPED 的成员 InternalSTATUS_PENDING 进行比较来确定这一点) - 函数调用 WaitForSingleObject[Ex] on hEvent 来自OVERLAPPED 如果它不为 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. ReadFilehEventOVERLAPPED 传递到 ZwReadFile (Event [in, optional] ) 但ReadFile[Ex] 总是在适当的位置传递 0 EventZwReadFile
  2. ApcRoutine [in, optional] - 显然 ReadFile 在这里传递了 0,但 ReadFileEx 在这里传递了一些内部例程 BaseIoCompletion[Simply](总是,即使你的 lpCompletionRoutine 为 0)。此例程将 NTSTATUS 代码从 PIO_STATUS_BLOCK第二个参数)转换为 win32 错误(通过 致电RtlNtStatusToDosError)。而不是打电话给你 lpCompletionRoutine它来自ApcContext - 第一个参数)。如果它是 0 - 那么 0 并且将被调用。与众所周知的 结果。
  3. 重要提示 - 如果 IOCP 端口绑定到您的文件(通过调用 BindIoCompletionCallback, CreateThreadpoolIo, CreateIoCompletionPort 或通过设置 FileCompletionInformation - 你不能使用ApcRoutine (非 0)- 这是 I/O 完成的互斥方法。和 你从 i/o 子系统得到STATUS_INVALID_PARAMETER
  4. ApcContext [in, optional] - ReadFile 将指针传递给您的 lpOverlapped 在这里。因此您可以在FileIOCompletionRoutineIoCompletionCallback 中取回它(指针),或者 直接致电ZwRemoveIoCompletion

    ReadFileEx 在此处传递您的 lpCompletionRoutine。从而 BaseIoCompletion[Simply] stub 把它作为 ApcContext 和 用来调用你原来的lpCompletionRoutine(即使它是0)

  5. IoStatusBlock ReadFileEx 这里将指针传递给您的lpOverlapped 无条件ReadFile 传递你的 lpOverlapped 如果它不是 0。否则它在堆栈中分配它,作为局部变量 并使用这个指针。由此得出ReadFile 在这种情况下 (lpOverlapped==0) 直到操作未完成才能返回 - 因为IO_STATUS_BLOCK 在 i/o 未完成时必须有效, 但函数返回后局部变量无效。
  6. 传递给Buffer [out]Length [in]的东西很明显
  7. ByteOffset [in, optional] - 如果 lpOverlapped 不为 0,则分配局部变量 LARGE_INTEGER ByteOffset(好吧,当然 总是声明(如此分配),只是我的意思是它在此使用 case) 并从 OffsetOffsetHigh 成员初始化 重叠。如果 lpOverlapped 为 0(如果是 ReadFile)则为 NULL 指针传递为 ByteOffset
  8. 重要提示。如果文件以异步模式打开(使用 FILE_FLAG_OVERLAPPED 标志如果我们使用 CreateFileWSA_FLAG_OVERLAPPED 如果我们使用 WSASocket( 或 socket) 或者如果我们不使用 FILE_SYNCHRONOUS_IO_NONALERTFILE_SYNCHRONOUS_IO_ALERT ZwCreateFile 中的标志或 ZwOpenFile ) 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] 时我们无法控制它。如果需要,请查看 ZwLockFile Key 参数 了解这个参数。 win32 外壳LockFileEx 是 受限。

ZwReadFile 被调用并返回后NTSTATUS 状态 -

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

    如果 lpOverlapped 不是 NULL - ReadFile 不等待自己。它 如果不是NT_SUCCESS(status) 和 返回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 - 没有任何问题 - 我们在 IO_STATUS_BLOCK 中得到了 STATUS_TIMEOUT。并且会确切地知道超时是什么。

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

所以读取的同步或异步行为不是由选择ReadFileReadFileEx 决定的,而是您以哪种模式(使用哪些标志)打开文件。 (我所说的驱动程序有时可以忽略异步句柄并同步处理 i/o)。并由 lpOverlapped (0 或非 0)以防 ReadFile(但这里都是完全确定的)


调用SetCommTimeoutsIOCTL_SERIAL_SET_TIMEOUTSSetCommTimeouts 只需将此ioctl 发送给驱动程序)对文件有效。在这之后绝对没有什么不同 - 使用你 ReadFileReadFileEx - 在超时的情况下都会给你相同的结果。


最后 - 如果您只想在等待返回后等待操作完成 - 这实际上是同步 I/O。在这种情况下,您不需要以异步模式打开文件 - 这种代码毫无意义。只需同步打开并使用ReadFile

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • In any case, the completion routine is the only way for you tell that the I/O is complete - 这不完全是。即使没有事件、完成例程和文件上的 IOCP - i/o 管理器 set 嵌入在文件对象 Event 中。因此我们可以在hFile 上等待,或者在 i/o 完成时使用GetOverlappedResult 等待。
    • @RbMm,我忽略了这个选项。我同意这会起作用,但不建议这样做。我认为主要原因是如果在同一个句柄上有多个 I/O 操作未完成,它将无法正常工作,这是使用异步 I/O 时的常见情况。
    • @HarryJohnston,你说得对,ReadFileReadFileEx 都受到影响。原来是串口设备驱动问题,因为我试过通过USB连接两个不同的手机调制解调器。 Sony Ericsson phone modem 没有问题,LG phone modem 根本不考虑超时,它的 ReadFile 函数立即返回 0读取的字节,就好像它正在同步工作一样。我应该放弃使用 SetCommTimeouts 并开始寻找替代方案。
    • 如果没有数据在等待并且端口设置为非阻塞,则立即返回读取 0 字节是正确的行为。您应该考虑使用minimal reproducible example 代码发布另一个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 2012-11-16
    • 1970-01-01
    • 2011-06-13
    • 2012-11-24
    • 1970-01-01
    相关资源
    最近更新 更多