Reputation: 3516
I am writing a C++ program that makes use of named pipes on Windows. I can create and work with them quite fine. The only piece missing to the puzzle is a function to check for a pipe's existence.
Coming from the Unix world I originally tried std::filesystem::exists("\\\\.\\pipe\\myPipe")
but this is not reliable and often errors with ERROR_PIPE_BUSY
.
While searching for an alternative way to check for a pipe's existence, I stumbled upon this issue on GitHub (Boost process) and from there I take it that Boos process circumvents the problem by using a special naming scheme and a counter and then keeping track of that internally (only seems to work for pipes created via Boost process though).
Furthermore according to How can I get a list of all open named pipes in Windows? it seems that there are ways to list the existing named pipes. These solutions are not using C++ though and I did not find a way to port that over.
After having read the documentation of CreateNamedPipe, I now assembled the following solution to my problem:
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;
}
}
However attempting to create a named pipe only to check whether there already exists one with that name already seems like a lot of unnecessary overhead. Furthermore I am unsure of whether this solution is universally applicable or only works if the pipe tested for was also created with FILE_FLAG_FIRST_PIPE_INSTANCE
.
Therefore my question is: Is there a better way to check whether a named pipe with the given name already exists in Windows?
Upvotes: 2
Views: 3530
Reputation: 596497
std::filesystem::exists("\\\\.\\pipe\\myPipe")
returning ERROR_PIPE_BUSY
means it is using CreateFile()
to actually connect to the pipe. It is not unreasonable for an exists()
implementation to attempt to open the requested file to check its existance.
Per the 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 withERROR_PIPE_BUSY
.
Which means the pipe does technically exist, it is not ready to receive a new client at that moment.
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 shows how that call translates into Win32 API calls using FindFirstFile()
and FindNextFile()
. You can easily do the same API calls in C++, eg:
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: or, using std::wstring
with Unicode APIs instead, since the filesystem is natively Unicode on Windows:
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;
}
Upvotes: 4