在其生命周期内,是否可以将已用于同步 I/O 的 HANDLE 更改为用于异步 I/O?

15

我日常在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 大师:救命啊!

致意

Martin


请注意,假设您确实想要将句柄与特定端口一起使用,那么“CreateIoCompletionPort()”技巧实际上非常巧妙。我之前从未想过这个! - André Caron
7个回答

6
我看到我对MSDN的阅读不够仔细 :/ 我完全错过了函数ReOpenFile(),它很可能早在2003年6月就被引入Windows Server 2003(根据这篇文章)。为了至少保护自己一点:我期望CreateFile()的描述交叉引用到ReOpenFile()的描述。在ReOpenFile()页面上有一个对CreateFile()页面的引用,但反之则没有。

这个函数似乎可以精确地实现我需要的功能:通过创建具有所需属性的新句柄,添加或删除已存在的句柄中的FILE_FLAG_OVELRAPPED!:-D 不过我还没有测试过它。不幸的是,它只可用于Windows 2003 Server、Windows Vista及其更高版本。关于以前的操作系统版本的问题已经在这里得到了回答。该函数在Windows 2003 Server之前的操作系统中不存在于公共API中。它被底层实现所使用,但在这些系统上对开发人员不可用(不支持)。

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

此外,我仍然缺少一个答案的一部分:是否可以通过任何方式测试标志的存在?我没有找到执行此操作的函数。这意味着如果我们想要保证某个标志在文件对象中存在,则必须重新打开文件。


事实上,如果我在本地的MSDN副本中搜索,我发现这个函数根本没有被提及(引用)!它只列在文件管理功能列表中。而且,谷歌提供了令人惊讶地少的结果。这引起了一些关于其可靠性的疑虑。我迫不及待地想运行一些初步测试。例如,我想看到包含OVERLAPPED标志的Console处理。 - Martin Dobšík
“存在标志的存在可以通过任何方式进行测试吗?”好问题,有答案吗? - elmarco
3
读者注意:ReOpenFile() 函数对于文件运作得很好,因为它可以重新打开一个文件流并为新句柄添加异步 I/O 支持,但是这对于匿名管道不起作用(总是返回 ERROR_PIPE_BUSY)。 - André Caron

3

已经过去了3年,Windows 8已经发布。由于在Windows 8中实现控制台时引入的回归,我需要解决触发此问题的问题。因此,我最终尝试使用ReOpenFile()函数调用。

简而言之,对于我的目的来说,它是无用的。

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

我尝试在控制台输入句柄上使用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。但是,正如我在开头提到的那样,我们的应用程序是由第三方应用程序启动的,我们不能强制它们始终为stdio创建具有重叠标志集的命名管道。

我放弃了,打算实现丑陋的hack...我们将不得不从已经使用OVERLAPPED标志创建的HANDLE中继续读取,而泄漏句柄和线程....


我的建议是:永远不要使用匿名管道!Win32 API函数的行为与创建具有随机名称的管道侦听器(CreateNamedPipe)后跟ConnectNamedPipeCreateFile几乎相同(出于安全考虑,您应该通过管道传递一个nonce来确认另一个进程没有连接)。使用自己的CreatePipe2包装器可以让您控制管道是否重叠。而且,如果我正确地阅读文档,则生成的管道句柄可以使用ReOpenFile重新打开以更改重叠模式。 - Nicholas Wilson
这个失败的原因是你正在使用一个控制台句柄,而不是文件句柄。 - Demi
@NicholasWilson 这是因为匿名管道在内部实际上被实现为命名管道! - Demi

2
如果我理解你的意思,我建议您不必关心是否使用了重叠标志打开。我相信您可以在同步和异步情况下都安全地传递一个OVERLAPPED结构。您的代码需要能够处理ReadFile()返回false并且GetLastError()返回ERROR_IO_PENDING的情况。您还需要添加适当的调用GetOverlappedResult()、WaitForSingleObject()等函数。
MSDN关于ReadFile()的文章在“考虑使用同步文件句柄时的注意事项”和“考虑使用异步文件句柄时的注意事项”中有一些关于这方面的好信息,位于“同步和文件位置”部分。

我相信,嗯...你能确认一下吗? :) - elmarco

1
我不知道确定句柄标志和使用ReOpen API的副作用的方法,但因为您的目标是“只有一种处理所有句柄的方式将非常有用”,如果您需要同步行为(我的意思是对于非重叠句柄使用同步API,并使用带后续等待于重叠事件的OVERLAPPED结构提供异步API),则您始终可以使用异步API,即使句柄以非重叠方式打开,正如@Brett已经说明的那样。 我可以确认这适用于(至少)命名管道,例如:
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
    }*/
}

1

测试句柄标志应该与测试创建句柄的权限方式相同。 尝试一下。如果API失败,请尝试备用方案。如果备用方案也失败了,则返回错误。

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

我的解释是(你需要问自己的问题是):如果可以检查文件句柄的重叠状态,为什么ReadFile不进行该检查,然后根据情况验证OVERLAPPED结构,以在非重叠方式下调用具有重叠句柄的情况下明确失败?


我正在询问这个问题和许多类似的问题。不幸的是,微软没有提供直接的答案。这就是为什么我在这里提问的原因。我没有找到其他测试OVERLAPPED标志存在的方法,除了上述描述的方法。 - Martin Dobšík

0
一个替代CreateIoCompletionPort的方法是使用带有NULL lpOverlapped的零字节ReadFile。如果它返回ERROR_INVALID_PARAMETER,则假定它是使用FILE_FLAG_OVERLAPPED打开的。

0

NtSetInformationFileFileModeInformation,清除FILE_SYNCHRONOUS_IO_NONALERT标志。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接