Windows: 检查命名管道是否存在

Windows: Check existence of named pipe

我正在编写一个在 Windows 上使用命名管道的 C++ 程序。我可以很好地创建和使用它们。这个难题唯一缺少的部分是检查管道是否存在的函数。

来自 Unix 世界,我最初尝试 std::filesystem::exists("\\.\pipe\myPipe") 但这并不可靠,并且 ERROR_PIPE_BUSY 经常出错。

在寻找检查管道是否存在的替代方法时,我在 GitHub(Boost 进程)上偶然发现了 this issue,从那里我认为 Boos 进程通过使用一个特殊的命名方案和一个计数器,然后在内部跟踪它(不过似乎只适用于通过 Boost 进程创建的管道)。

此外,根据 How can I get a list of all open named pipes in Windows?,似乎有一些方法可以列出现有的命名管道。这些解决方案虽然没有使用 C++,但我没有找到移植它的方法。

阅读 documentation of CreateNamedPipe 之后,我现在针对我的问题整理了以下解决方案:

bool NamedPipe::exists(const std::filesystem::path &pipePath) {
    if (pipePath.parent_path() != "\\.\pipe") {
        // This can't be a pipe, so it also can't exist
        return false;
    }

    // Attempt to create a pipe with FILE_FLAG_FIRST_INSTANCE so that the creation will fail
    // if the pipe already exists
    HANDLE pipeHandle = CreateNamedPipe(pipePath.string().c_str(),
                                        PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
                                        PIPE_TYPE_BYTE | PIPE_WAIT,
                                        1,   // # of allowed pipe instances
                                        0,   // Size of outbound buffer
                                        0,   // Size of inbound buffer
                                        0,   // Use default wait time
                                        NULL // Use default security attributes
    );

    if (pipeHandle == INVALID_HANDLE_VALUE) {
        // Creation has failed
        // It has failed (most likely) due to there alredy existing a pipe with
        // that name
        return true;
    } else {
        // The creation has succeeded
        if(!CloseHandle(pipeHandle)) {
            throw PipeException< DWORD >(GetLastError(), "CheckExistance");
        }

        return false;
    }
}

然而,尝试创建一个命名管道只是为了检查是否已经存在一个具有该名称的管道似乎已经是很多不必要的开销。此外,我不确定此解决方案是否普遍适用,或者仅在测试的管道也是使用 FILE_FLAG_FIRST_PIPE_INSTANCE.

创建时才有效

因此我的问题是:有没有更好的方法来检查 Windows 中是否已经存在具有给定名称的命名管道?

std::filesystem::exists("\\.\pipe\myPipe") 返回 ERROR_PIPE_BUSY 意味着它正在使用 CreateFile() 实际连接到管道。 exists() 实现尝试打开请求的文件以检查其存在并非不合理。

根据 CreateFile() documentation:

If there is at least one active pipe instance but there are no available listener pipes on the server, which means all pipe instances are currently connected, CreateFile fails with ERROR_PIPE_BUSY.

这意味着该管道在技术上确实存在,此时它还没有准备好接收新客户端。

In the link you provided, many of the solutions provided suggest using .NET's System.IO.Directory.GetFiles() method to iterate though the contents of "\.\pipe\". This answer 显示该调用如何使用 FindFirstFile()FindNextFile() 转换为 Win32 API 调用。您可以在 C++ 中轻松执行相同的 API 调用,例如:

bool NamedPipe::exists(const std::filesystem::path &pipePath)
{
    std::string pipeName = pipePath.string();
    if ((pipeName.size() < 10) ||
        (pipeName.compare(0, 9, "\\.\pipe\") != 0) ||
        (pipeName.find('\', 9) != std::string::npos))
    {
        // This can't be a pipe, so it also can't exist
        return false;
    }
    pipeName.erase(0, 9);

    WIN32_FIND_DATA fd;
    DWORD dwErrCode;

    HANDLE hFind = FindFirstFileA("\\.\pipe\*", &fd);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        dwErrCode = GetLastError();
    }
    else
    {
        do
        {
            if (pipeName == fd.cFileName)
            {
                FindClose(hFind);
                return true;
            }
        }
        while (FindNextFileA(hFind, &fd));

        dwErrCode = GetLastError();
        FindClose(hFind);
    }

    if ((dwErrCode != ERROR_FILE_NOT_FOUND) &&
        (dwErrCode != ERROR_NO_MORE_FILES))
    {
        throw PipeException< DWORD >(dwErrCode, "CheckExistance");
    }

    return false;
}

UPDATE:或者,使用 std::wstring 和 Unicode APIs,因为文件系统在 Windows 上是原生 Unicode:

bool NamedPipe::exists(const std::filesystem::path &pipePath)
{
    std::wstring pipeName = pipePath;
    if ((pipeName.size() < 10) ||
        (pipeName.compare(0, 9, L"\\.\pipe\") != 0) ||
        (pipeName.find(L'\', 9) != std::string::npos))
    {
        // This can't be a pipe, so it also can't exist
        return false;
    }
    pipeName.erase(0, 9);

    WIN32_FIND_DATAW fd;
    DWORD dwErrCode;

    HANDLE hFind = FindFirstFileW(L"\\.\pipe\*", &fd);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        dwErrCode = GetLastError();
    }
    else
    {
        do
        {
            if (pipeName == fd.cFileName)
            {
                FindClose(hFind);
                return true;
            }
        }
        while (FindNextFileW(hFind, &fd));

        dwErrCode = GetLastError();
        FindClose(hFind);
    }

    if ((dwErrCode != ERROR_FILE_NOT_FOUND) &&
        (dwErrCode != ERROR_NO_MORE_FILES))
    {
        throw PipeException< DWORD >(dwErrCode, "CheckExistance");
    }

    return false;
}