CreateProcess cmd.exe读/写管道死锁

CreateProcess cmd.exe read/write pipes deadlock

本文关键字:管道 死锁 cmd exe CreateProcess      更新时间:2023-10-16

你好,我正试图为cmd.exe制作一个前端GUI,这样我就可以使它更宽,但我被卡住了。

我试着设计一个像这样的API

char* Directory = WriteCommand("dir");
printf("- %sn", Directory);

输出看起来和cmd窗口中的完全一样,只是我把它放在字符串中,所以它将是

DATE TIME FILESIZE FILENAME
etc etc etc

然后我可以发布

char* Up = WriteCommand ("cd ..");

它会给我上面的目录列表。所以我想要一个通过使用管道进行读写的终端控件。

我已经尝试了许多基于此MSDN示例代码的东西-https://msdn.microsoft.com/en-us/library/ms682499.aspx

但我认为,这段代码只适合发出一个命令,读取一个响应,因为在它之后,就会像这里描述的那样死锁——https://blogs.msdn.microsoft.com/oldnewthing/20110707-00/?p=10223

我在这里看到了其他几个问题,比如这个有类似问题的问题-如何使用CreateProcess()和CreatePipe()读取cmd.exe的输出,但没有发布适合我的解决方案。

这是我的代码。

#include <windows.h> 
#include <tchar.h>
#include <stdio.h> 
#include <strsafe.h>
#define BUFSIZE 4096 
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hInputFile = NULL;
void CreateChildProcess(void);
void WriteToPipe(char* Arg1);
void ReadFromPipe(void);
void ErrorExit(PTSTR);

int _tmain(int argc, TCHAR *argv[])
{
SECURITY_ATTRIBUTES saAttr;
printf("n->Start of parent execution.n");
// Set the bInheritHandle flag so pipe handles are inherited. 
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT. 
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
// Create a pipe for the child process's STDIN. 
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
// Ensure the write handle to the pipe for STDIN is not inherited. 
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
// Create the child process. 
CreateChildProcess();
// Get a handle to an input file for the parent. 
// This example assumes a plain text file and uses string output to verify data flow. 
/*if (argc == 1)
ErrorExit(TEXT("Please specify an input file.n"));
g_hInputFile = CreateFile(
argv[1],
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY,
NULL);
if (g_hInputFile == INVALID_HANDLE_VALUE)
ErrorExit(TEXT("CreateFile"));*/
// Write to the pipe that is the standard input for a child process. 
// Data is written to the pipe's buffers, so it is not necessary to wait
// until the child process is running before writing data.

// Read from pipe that is the standard output for child process. 

ReadFromPipe();
WriteToPipe("ipconfig");
// THIS IS WHERE DEADLOCK OCCURS, FROM HERE
// PROGRAM BECOMES UNRESPONSIVE - HOW TO FIX THIS?
ReadFromPipe();

printf("n->End of parent execution.n");
// The remaining open handles are cleaned up when this process terminates. 
// To avoid resource leaks in a larger application, close handles explicitly. 
return 0;
}
void CreateChildProcess()
// Create a child process that uses the previously created pipes for     STDIN and STDOUT.
{
TCHAR szCmdline[] = TEXT("cmd.exe /k");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure. 
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure. 
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process. 
bSuccess = CreateProcess(NULL,
"cmd.exe",     // command line 
NULL,          // process security attributes 
NULL,          // primary thread security attributes 
TRUE,          // handles are inherited 
0,             // creation flags 
NULL,          // use parent's environment 
NULL,          // use parent's current directory 
&siStartInfo,  // STARTUPINFO pointer 
&piProcInfo);  // receives PROCESS_INFORMATION 
// If an error occurs, exit the application. 
if (!bSuccess)
ErrorExit(TEXT("CreateProcess"));
else
{
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example. 
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
}
void WriteToPipe(char* Command)
// Read from a file and write its contents to the pipe for the    child's STDIN.
// Stop when there is no more data. 
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
if (bSuccess == FALSE)
printf("write failn");
printf("written = %in", dwWritten);

//for (;;)
//{
//bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
//if (!bSuccess || dwRead == 0) break;
//bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
//if (bSuccess == FALSE)
//printf("write failn");
//printf("written = %in", dwWritten);
//}
// Close the pipe handle so the child process stops reading. 
//if (!CloseHandle(g_hChildStd_IN_Wr))
//ErrorExit(TEXT("StdInWr CloseHandle"));
}
void ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT. 
// Stop when there is no more data. 
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
int i;
for (i = 0; i < 4; i++)
{
/*DWORD dwAvail = 0;
if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &dwAvail, NULL)) {
// error, the child process might have ended
break;
}
if (!dwAvail) {
// no data available in the pipe
break;
}*/
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0) break;
/*bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) break;*/
chBuf[dwRead] = '';
printf("%i - %sn", i, chBuf);
}
printf("donen");
}

我发出了初始的"cmd.exe"命令,该命令将启动命令提示符。我现在想发出"ipconfig"(或任何其他命令)来获取网络信息。程序死锁并变得没有响应。我无法再读取子进程的输出。我该怎么解决这个问题?谢谢你的帮助。

避免任何死锁的最强大、最有效的解决方案-使用异步io。永远不要等待io(读、写、ioctl)到位,而是在回调中处理。

还请注意使用管道重定向输出-这是非常常见的错误,我们需要为STDINSTDOUT使用不同的句柄,并需要创建两个不同的管道对-一个用于STDIN,另一个用于STDOUT。这是错误的。我们可以对STDINSTDOUT使用单个管道句柄(和STD错误)。

  1. 我们需要使用CreateNamedPipeW创建服务器管道句柄PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED旗帜。通过使用CCD_ 3创建双向管道,因此,服务器和客户端进程都可以读取和写入到管道。和FILE_FLAG_OVERLAPPED作为异步模式此外,我们不使此句柄可继承,因此不需要调用上面的SetHandleInformation
  2. 我们由CreateFileW创建的客户端句柄FILE_GENERIC_READ|FILE_GENERIC_WRITE访问-此功能将其同时分配给stdinstdout。因为客户(比如cmd.exe)通常假定同步io-我们不使用此处为FILE_FLAG_OVERLAPPED。此外,通过使用lpSecurityAttributes,我们只需使此句柄可继承即可
  3. 我们需要将服务器句柄绑定到一些IOCP,以便在io时调用回调结束。这里我们有三种变体-使用BindIoCompletionCallback-最简单的方式或使用CCD_ 10。我们也可以自己创建IOCP线程池,但对于重定向子进程输出,这种方式通常不需要
  4. 创建子进程后,我们需要关闭客户端管道句柄(我们将其复制到child),然后在管道上调用ReadFile手柄当这个ReadFile完成时,我们需要再次调用来自回调的ReadFile等等-直到我们没有从ReadFile(通常为ERROR_BROKEN_PIPE)。所以我们需要一直有来自管道的活动读取请求,直到断开连接
  5. 我们在任何时间、任何地点免费拨打WriteFile导致死锁,因为我们使用异步io
  6. 如果我们需要对读取进行复杂的处理,则需要一段时间(非常非常罕见)数据(基于以前的结果和状态)在普通过程中处理,但不在回调中处理,我们可以创建光纤对于该任务(CreateFiber)和工作线程回调,读取完成时-首先调用ConvertThreadToFiber(如果为同一个工作线程多次调用此函数-将出错ERROR_ALREADY_FIBER在第二次和下一次通话中,但这没关系所有这些工作都只是从远景开始的。此处出现xp错误)。回想起当前光纤,到需要重新布线的位置(GetCurrentFiber()),以及呼叫SwitchToFiber(使用我们的专用读取光纤)-其中我们可以处理读取结果,然后通过调用返回SwitchToFiber(加工螺纹用纤维)。但是所有这些在非常罕见和特殊的情况下确实需要。通常handle-all是回调,对象中的状态与管道句柄相关——绰绰有余

cmd 的简单示例

#define _XP_SUPPORT_
struct IO_COUNT 
{
HANDLE _hFile;
HANDLE _hEvent;
LONG _dwIoCount;
IO_COUNT()
{
_dwIoCount = 1;
_hEvent = 0;
}
~IO_COUNT()
{
if (_hEvent)
{
CloseHandle(_hEvent);
}
}
ULONG Create(HANDLE hFile);
void BeginIo()
{
InterlockedIncrement(&_dwIoCount);
}
void EndIo()
{
if (!InterlockedDecrement(&_dwIoCount))
{
SetEvent(_hEvent);
}
}
void Wait()
{
WaitForSingleObject(_hEvent, INFINITE);
}
};

struct U_IRP : OVERLAPPED 
{
enum { read, write };
IO_COUNT* _pIoObject;
ULONG _code;
LONG _dwRef;
char _buffer[256];
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
U_IRP(IO_COUNT* pIoObject) : _pIoObject(pIoObject)
{
_dwRef = 1;
pIoObject->BeginIo();
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
}
~U_IRP()
{
_pIoObject->EndIo();
}
ULONG CheckIoResult(BOOL fOk)
{
if (fOk)
{
#ifndef _XP_SUPPORT_
OnIoComplete(NOERROR, InternalHigh);
#endif
return NOERROR;
}
ULONG dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_IO_PENDING)
{
OnIoComplete(dwErrorCode, 0);
}
return dwErrorCode;
}
ULONG Read()
{
_code = read;
AddRef();
return CheckIoResult(ReadFile(_pIoObject->_hFile, _buffer, sizeof(_buffer), 0, this));
}
ULONG Write(const void* pvBuffer, ULONG cbBuffer)
{
_code = write;
AddRef();
return CheckIoResult(WriteFile(_pIoObject->_hFile, pvBuffer, cbBuffer, 0, this));
}
VOID OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
{
switch (_code)
{
case read:
if (dwErrorCode == NOERROR)
{
if (dwNumberOfBytesTransfered)
{
if (int cchWideChar = MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, 0, 0))
{
PWSTR wz = (PWSTR)alloca(cchWideChar * sizeof(WCHAR));
if (MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, wz, cchWideChar))
{
if (int cbMultiByte = WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, 0, 0, 0, 0))
{
PSTR sz = (PSTR)alloca(cbMultiByte);
if (WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, sz, cbMultiByte, 0, 0))
{
DbgPrint("%.*s", cbMultiByte, sz);
}
}
}
}
}
Read();
}
break;
case write:
break;
default:
__debugbreak();
}
Release();
if (dwErrorCode)
{
DbgPrint("[%u]: error=%un", _code, dwErrorCode);
}
}
static VOID WINAPI _OnIoComplete(
DWORD dwErrorCode,
DWORD_PTR dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
)
{
static_cast<U_IRP*>(lpOverlapped)->OnIoComplete(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
};
ULONG IO_COUNT::Create(HANDLE hFile)
{
_hFile = hFile;
// error in declaration LPOVERLAPPED_COMPLETION_ROUTINE : 
// second parameter must be DWORD_PTR but not DWORD
return BindIoCompletionCallback(hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)U_IRP::_OnIoComplete, 0) && 
#ifndef _XP_SUPPORT_
SetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) &&
#endif
(_hEvent = CreateEvent(0, TRUE, FALSE, 0)) ? NOERROR : GetLastError();
}
void ChildTest()
{
static const WCHAR name[] = L"\\?\pipe\somename";
HANDLE hFile = CreateNamedPipeW(name, 
PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED, 
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
IO_COUNT obj;
if (obj.Create(hFile) == NOERROR)
{
BOOL fOk = FALSE;
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = CreateFileW(name, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 
FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0);
if (si.hStdError != INVALID_HANDLE_VALUE)
{
si.hStdInput = si.hStdOutput = si.hStdError;
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (CreateProcessW(ApplicationName, 0, 0, 0, TRUE, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
fOk = TRUE;
}
}
CloseHandle(si.hStdError);
}
if (fOk)
{
STATIC_ASTRING(help_and_exit, "helprnexitrn");
U_IRP* p;
if (p = new U_IRP(&obj))
{
p->Read();
p->Release();
}
obj.EndIo();
//++ simulate user commands
static PCSTR commands[] = { "helprn", "verrn", "dirrn", "exitrn" };
ULONG n = RTL_NUMBER_OF(commands);
PCSTR* psz = commands;
do 
{
if (MessageBoxW(0,0, L"force close ?", MB_YESNO) == IDYES)
{
DisconnectNamedPipe(hFile);
break;
}
if (p = new U_IRP(&obj))
{
PCSTR command = *psz++;
p->Write(command, (ULONG)strlen(command) * sizeof(CHAR));
p->Release();
}    
} while (--n);
//--
obj.Wait();
}
}
CloseHandle(hFile);
}
}

我知道它有点老了,所以你可能不再需要这个答案了。但对于那些来StackOverflow寻求相同问题解决方案的人来说,我在构建类似项目时遇到了相同的问题,我找到了解决方案。

基本上,只需添加"\n〃;命令末尾的换行符。这是为了模拟";ENTER";按钮被按下。否则,WriteFile()可以工作,但ReadFile()仍在等待,因为该命令从未在子进程cmd.exe中执行过,因此没有任何可读内容,导致它挂在那里。

所以修改后的代码是(我没有测试运行以下代码,只是根据原作者发布的示例进行了修改):

void WriteToPipe(char* Command)
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data. 
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
// Fix for the issue
strcat_s(command, strlen(command) + 1, "n", 1);
bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
if (bSuccess == FALSE)
printf("write failn");
printf("written = %in", dwWritten);

//for (;;)
//{
//bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
//if (!bSuccess || dwRead == 0) break;
//bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
//if (bSuccess == FALSE)
//printf("write failn");
//printf("written = %in", dwWritten);
//}
// Close the pipe handle so the child process stops reading. 
//if (!CloseHandle(g_hChildStd_IN_Wr))
//ErrorExit(TEXT("StdInWr CloseHandle"));
}