【问题标题】:Is it possible to change HANDLE that has been opened for synchronous I/O to be opened for asynchronous I/O during its lifetime?是否可以将已为同步 I/O 打开的 HANDLE 更改为在其生命周期内为异步 I/O 打开?
【发布时间】:2010-03-19 07:53:07
【问题描述】:

现在,我在 Windows 中的大部分日常编程工作都围绕着各种 I/O 操作(管道、控制台、文件、套接字……)。我很清楚从/向不同类型的句柄读取和写入的不同方法(同步、异步等待事件完成、等待文件句柄、I/O 完成端口和可警报 I/O)。我们使用其中的许多。

对于我们的某些应用程序,只有一种方法来处理所有句柄会非常有用。我的意思是,程序可能不知道它收到了什么样的句柄,我们想使用,比如说,所有的 I/O 完成端口。

所以首先我要问:

假设我有一个句柄:

HANDLE h;

我的进程从某个地方收到了 I/O。有没有简单可靠的方法来找出它是用什么标志创建的?有问题的主要标志是FILE_FLAG_OVERLAPPED

到目前为止,我知道的唯一方法是尝试将此类句柄注册到 I/O 完成端口(使用 CreateIoCompletionPort())。如果成功,则使用 FILE_FLAG_OVERLAPPED 创建句柄。但随后只能使用 I/O 完成端口,因为在不关闭 HANDLE h 本身的情况下无法从其中注销句柄。

如果有一种简单的方法可以确定FILE_FLAG_OVERLAPPED 的存在,那么我的第二个问题就会出现:

有什么办法可以将这样的标志添加到已经存在的句柄中?这将使最初为同步操作打开的句柄为异步打开。有没有办法创建相反的(删除FILE_FLAG_OVERLAPPED以从异步创建同步句柄)?

在阅读 MSDN 和谷歌搜索后,我没有找到任何直接的方法。至少会有一些技巧可以做到这一点吗?就像使用CreateFile() 函数或类似的东西以同样的方式重新创建句柄?甚至部分记录或根本没有记录的内容?

我需要这个的主要地方是确定进程应该从第三方应用程序发送给它的句柄中读取/写入的方式(或更改方式)。我们无法控制第三方产品如何创建句柄。

尊敬的 Windows 专家:请帮助!

关于

马丁

【问题讨论】:

  • 请注意,CreateIoCompletionPort() 技巧实际上非常简洁,假设您确实想使用该特定端口的句柄。没想到!

标签: winapi asynchronous synchronous createfile overlapped-io


【解决方案1】:

我知道我是 MSDN 的糟糕读者:/ 我完全错过了功能 ReOpenFile(),它可能早在 2003 年 6 月就已在 Windows Server 2003 中引入(根据 this article)。至少为自己辩护一点:我希望CreateFile() 描述与ReOpenFile() 描述交叉引用。 ReOpenFile() 页面上有对CreateFile() 页面的引用,但反之则不然。

这个功能似乎正好满足了我的需要:通过创建具有所需属性的新句柄,在现有句柄中添加或删除FILE_FLAG_OVELRAPPED! :-D 不过,我还没有测试过。遗憾的是,它仅适用于 Windows 2003 Server、Windows Vista 及更高版本。有关以前操作系统版本的问题已回答here。在 Windows 2003 Server 之前的操作系统中,公共 API 中不存在该功能。它由底层实现使用,但对那些系统上的开发人员不可用(不支持)。

这实际上意味着我至少在未来几年内没有希望,直到我们放弃对旧 Windows 平台的支持。这也意味着关于 I/O 的情况在 Windows Vista 之前的操作系统上非常糟糕。另一个完全缺失的痛苦部分是在那些旧系统上取消同步和异步 I/O 的可能性。

此外,我仍然错过了答案的一部分:是否可以通过任何方式测试标志的存在?我还没有找到这样做的功能。这意味着如果我们想保证文件对象中存在一些标志,那么文件总是必须重新打开。

【讨论】:

  • 事实上,如果我搜索我的本地 MSDN 副本,我发现该功能绝对没有在任何地方提及(引用)!它仅列在文​​件管理功能列表中。谷歌也出奇地少命中。它对其可靠性提出了一些质疑。我渴望进行一些初步测试。例如,我希望看到包含 OVERLAPPED 标志的控制台句柄。
  • "是否可以通过任何方式测试标志的存在?"好问题,有答案吗?
  • 读者注意:ReOpenFile() 可以很好地处理文件,因为您可以重新打开文件流并向新句柄添加异步 I/O 支持,但这不适用于匿名管道(总是返回ERROR_PIPE_BUSY).
【解决方案2】:

3 年过去了,Windows 8 已经发布。感谢在 Windows 8 中实现控制台时引入的回归,我必须对触发这个问题的问题做一些事情。所以我终于尝试使用 ReOpenFile() 函数调用。

一句话:对我来说是没用的。

ReOpenFile() API 用于“获取现有文件句柄并获取具有不同访问权限集的另一个句柄”。至少在original article 中有说明。

我尝试在控制台输入句柄上使用 ReOpenFile():

  stdin_in = GetStdHandle(STD_INPUT_HANDLE);
  stdin_in_operlapped = ReOpenFile(stdin_in, GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ, FILE_FLAG_OVERLAPPED);
  if (stdin_in_operlapped ==  INVALID_HANDLE_VALUE)
    {
      my_debug("failed to ReOpen stdin handle with OVERLAPPED flag: %d", GetLastError());
      exit(1);
    }

我得到的是:错误 1168:“未找到元素”。 “谢谢微软”。我什至不会尝试将它用于匿名管道,因为文档指出:

“匿名管道不支持异步(重叠)读写操作。这意味着您不能将 ReadFileEx 和 WriteFileEx 函数用于匿名管道。另外,ReadFile 和 WriteFile 的 lpOverlapped 参数在与匿名管道一起使用时会被忽略。”

谢谢大家,谢谢大家的建议。当从句柄异步读取时,还必须准备好操作可以同步完成这一事实。我知道的。我问这个问题的主要原因是:

当对某些对象(至少是匿名管道和 Windows 8 中的控制台输入)发出同步读取时,从同一句柄上的另一个线程调用 CloseHandle() 将失败或挂起,直到 ReadFile()完成;这意味着它在许多情况下会无限期挂起。这就是我想用异步替换同步句柄的原因。

现在我很清楚,在 Windows 操作系统中根本不可能以直接的方式取消某些读取操作。从同步句柄读取时,即使 ReadFile() 仍在某个线程中从句柄读取,也只需退出应用程序,因为根本不可能可靠地唤醒这样的读取操作。知道... 在较新的操作系统中,可以取消该操作。但是,无法知道线程是否已经在 ReadFile() 调用中,或者还没有。如果尚未调用 ReadFile(),则没有取消操作,后续读取将挂起。唯一的方法是关闭句柄,但该操作在某些对象和某些操作系统上挂起或失败。唯一合适的解决方案是异步 I/O。但是,正如我在开头提到的,我们的 APP 是由第三方应用程序启动的,我们不能强制它们总是创建命名管道,并为 stdio 设置重叠标志。

我放弃并打算实施令人讨厌的丑陋黑客...我们将不得不继续阅读已使用 OVERLAPPED 标志创建的 HANDLE 中没有 OVERLAPPED 结构,以及泄漏的句柄和线程......

【讨论】:

  • 我的建议是:永远不要使用匿名管道! Win32 API 函数与创建具有随机名称 (CreateNamedPipe) 后跟 ConnectNamedPipeCreateFile 的管道侦听器具有基本相同的行为(为了安全起见,您应该在管道上传递一个随机数以确认另一个进程没有连接)。使用您自己的 CreatePipe2 包装器可以让您控制管道是否重叠。而且,如果我正确阅读文档,生成的管道句柄可以用ReOpenFile 重新打开以更改重叠模式。
  • 失败,因为您使用的是 CONSOLE 句柄,而不是文件句柄。
  • @NicholasWilson 那是因为匿名管道实现为引擎盖下的命名管道!
【解决方案3】:

如果我了解您的需求,我建议您不要关心是否使用重叠标志打开。我相信您可以在同步和异步情况下安全地传入OVERLAPPED 结构。您的代码需要能够处理返回falseReadFile() 和返回ERROR_IO_PENDINGGetLastError()。您还需要向GetOverlappedResult()WaitForSingleObject() 等添加适当的调用。

ReadFile() 上的 MSDN 文章在“同步和文件位置”部分的“使用同步文件句柄的注意事项”和“使用异步文件句柄的注意事项”下提供了一些很好的信息。

【讨论】:

  • “我相信”,嗯..你能确认一下吗? :)
【解决方案4】:

测试句柄标志应该与测试创建句柄的权限相同。 尝试一下。如果 API 失败,请尝试回退。如果失败,返回错误。

我认为真正能说明问题的是 ReadFile 的文档说“如果使用 FILE_FLAG_OVERLAPPED 打开 hFile,...函数可能会错误地报告读取操作已完成。”

我对错误的解释是,(您需要问自己的问题是):如果可以检查文件句柄的重叠状态,为什么 ReadFile 不进行检查然后相应地验证 OVERLAPPED 结构, 如果使用重叠句柄以非重叠方式调用,则显式失败?

【讨论】:

  • 我在问这个问题和许多类似的问题。不幸的是,微软没有提供直接的答案。这就是我在这里问的原因。我还没有找到任何其他方法来测试是否存在 OVERLAPPED 标志然后上述描述。
【解决方案5】:

我不知道如何确定句柄的标志和使用 ReOpen api 的副作用,但因为你的目标是

只有一种方法来处理所有句柄会非常有用

如果你想要同步行为(我的意思是对非重叠句柄使用同步 api,并使用 OVERLAPPED 结构提供异步 api 并随后等待重叠事件)你总是可以使用异步 api如果手柄以非重叠模式打开,如@Brett
所述 我可以确认这有效(至少对于命名管道)es:

void connectSynchronous(HANDLE hPipeThatWeDontKnowItsFlag){
    ...
    BOOL bRet = ::ConnectNamedPipe(hPipeThatWeDontKnowItsFlag, pOverlapped);

    if(bRet == FALSE){
        DWORD dwLastErr = ::GetLastError();

        if(dwLastErr == ERROR_IO_PENDING){
            //The handle was opened for asynchronous IO so we have to wait for the operation
            ...waitFor on the overlapped hEvent;

        }else if(dwLastErr == ERROR_PIPE_CONNECTED){
            //The handle was opened for synchronous IO and the client was already connected before this call: that's OK!
            return;
        }else{
            throw Error(dwLastErr);
        }
    }/*else{
        //The handle was opened for synchronous IO and the client has connected: all OK
    }*/
}

【讨论】:

    【解决方案6】:

    CreateIoCompletionPort hack 的替代方法是使用 NULL lpOverlapped 执行零字节 ReadFile。如果它失败并显示 ERROR_INVALID_PARAMETER 假设它是使用 FILE_FLAG_OVERLAPPED 打开的。

    【讨论】:

      【解决方案7】:

      NtSetInformationFile, FileModeInformation, 清除 FILE_SYNCHRONOUS_IO_NONALERT 标志。

      行为因“文件系统”而异。 (例如管道由 npfs 提供)

      我在 Windows XP 上尝试了匿名管道,但失败了。

      【讨论】:

        猜你喜欢
        • 2018-09-21
        • 1970-01-01
        • 2012-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-19
        • 1970-01-01
        • 2019-07-02
        相关资源
        最近更新 更多