问题描述
你好,我正在尝试为cmd.exe制作一个前端GUI,以便可以使其更宽一些,但我被卡住了。
Hello I am trying to make a front end GUI for cmd.exe so I can make it wider but I got stuck.
我尝试设计类似以下的API此
I try to design an API like this
char* Directory = WriteCommand("dir");
printf("- %s\n", Directory);
和输出看起来就像在cmd窗口中一样,除了我在字符串中,
and the output look exactly like it would in a cmd window, except I have it in a string, so it would be
DATE TIME FILESIZE FILENAME
etc etc etc
然后我可以发出
char* Up = WriteCommand ("cd ..");
,它将显示上面的目录列表。因此,我希望通过使用管道进行读取和写入的终端控件。
and it will give me the above directory listing. So I want a terminal control through using pipes to read and write.
我已经基于此MSDN示例代码尝试了许多事情-
I have tried many things based on this MSDN sample code - https://msdn.microsoft.com/en-us/library/ms682499.aspx
但我认为此代码仅是发出一个命令并读取一个响应的好方法,因为紧接在此处,死锁如下所述-
But I think this code is only good to issue one command, and read one response, because right after it deadlocks as described here - https://blogs.msdn.microsoft.com/oldnewthing/20110707-00/?p=10223
我在这里看到了其他几个问题,例如类似的问题-,但是没有解决方案对我有用。
I see several other questions here, like this one with similar problems - How to read output from cmd.exe using CreateProcess() and CreatePipe() but no solutions posted work for me.
所以这是我的代码。
#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 fail\n");
printf("written = %i\n", 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 fail\n");
//printf("written = %i\n", 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] = '\0';
printf("%i - %s\n", i, chBuf);
}
printf("done\n");
}
我发出初始的 cmd.exe命令,该命令使我开始命令提示符。我现在想发出 ipconfig(或任何其他命令)以获取网络信息。程序陷入僵局,无法响应。我无法再读取子进程的输出。我怎样才能解决这个问题?谢谢您的帮助。
I issue the initial "cmd.exe" command which gives me the start of the command prompt. I now want to issue "ipconfig" (or any other command) to get networking info. The program deadlocks and becomes unresponsive. I can no longer read output of child process. How can I fix this? Thanks for your help.
推荐答案
避免死锁的最强大,最有效的解决方案-使用异步io。永远不要等待IO(读取,写入,ioctl)完成,而要在回调中处理。
the most power and effective solution for avoid any deadlocks - use asynchronous io. never wait for IO (read,write,ioctl) complete in place, but handle this in callbacks.
还请注意使用管道进行重定向输出-非常常见的错误,我们需要为 STDIN 和 STDOUT ,并需要创建2个不同的管道对-一个用于 STDIN ,另一个用于 STDOUT 。这是错误的。我们可以对 STDIN 和 STDOUT (和 STDERROR )使用单个管道句柄。
also note about use pipes for redirect output - very common errancy that we need use different handles for STDIN and STDOUT and need create 2 different pipes pair - one for STDIN and another for STDOUT. this is false. we can use single pipe handle for both STDIN and STDOUT (and STDERROR).
- 我们需要使用和
PIPE_ACCESS_DUPLEX | FILE_READ_DATA | FILE_WRITE_DATA | FILE_FLAG_OVERLAPPED
标志。通过使用PIPE_ACCESS_DUPLEX
,我们创建了双向管道,
,因此服务器和客户端进程都可以从管道中读取和写入
。和FILE_FLAG_OVERLAPPED
设为异步
模式。同样,我们也不使该句柄可继承,因此不需要在其上调用
SetHandleInformation
- 我们由
CreateFileW
还具有
FILE_GENERIC_READ | FILE_GENERIC_WRITE
的访问权限-这使能力
都将其都分配给 stdin 和 stdout 。因为客户端(例如
cmd.exe)通常采用同步io-我们在这里不使用
FILE_FLAG_OVERLAPPED
。也通过使用 lpSecurityAttributes 我们
只是使该句柄可继承。 - 我们需要将服务器句柄绑定到某些IOCP,以便在io $ b $时调用回调b结束。这里我们有3种变体-使用
-最简单的方法或使用
。我们也可以自己创建IOCP并拥有
线程池,但是对于重定向子进程输出,这种方式通常不需要
。 - 创建子进程后-需要关闭客户端管道句柄
(我们复制到子对象),只需在我们的管道
句柄上调用ReadFile
即可。当此ReadFile
完成时-我们需要从回调中再次调用
ReadFile
,依此类推-直到没有得到来自
ReadFile
完成的错误(通常ERROR_BROKEN_PIPE
)。因此,我们一直需要
一直有来自管道的活动读取请求,直到断开连接为止。 - ,我们可以在任意位置免费调用
WriteFile
时间和任何地方-永远不会
导致死锁,因为我们使用异步io。 - 如果需要对读取
数据进行复杂处理,则需要花费一些时间(非常少) (基于先前的结果和状态),并且在普通过程中使用此
更加轻松,但在回调中不易处理,我们可以为此任务创建光纤
()并从工作线程回调中读取完成后的
-首先调用(如果我们
对于同一个工作线程多次调用此命令,则会出错
在第二个和下一个呼叫中ERROR_ALREADY_FIBER
,但是可以。 t
所有这些工作仅从Vista开始。关于XP错误)。记住
当前的光纤,到需要退回的位置()和
呼叫(我们供读者阅读光纤)-我们可以在其中处理
的读取结果,并在此之后通过调用
返回a (使用光纤作为工作线程)。但是在非常罕见和特定的情况下,确实需要所有这些
。通常
句柄全部是与管道句柄相关的对象中具有状态的回调-绰绰有余。
- we need create server pipe handle by using
CreateNamedPipeW
withPIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED
flags. by usingPIPE_ACCESS_DUPLEX
we create bi-directional pipe,as result both server and client processes can read from and writeto the pipe. andFILE_FLAG_OVERLAPPED
give to as asynchronousmode. also we not make this handle inheritable, so not need callSetHandleInformation
on it - client handle we create by
CreateFileW
also withFILE_GENERIC_READ|FILE_GENERIC_WRITE
access - this give abilityassign it both to stdin and stdout. because clients (likecmd.exe) usually assume synchronous io - we not useFILE_FLAG_OVERLAPPED
here. also by using lpSecurityAttributes wejust make this handle inheritable. - we need bind server handle to some IOCP, for callback called when iois ended. here we have 3 variants - use
BindIoCompletionCallback
- the most simply way or useCreateThreadpoolIo
. also we can create IOCP yourself and ownthread pool, but for redirect child process output, this way usuallynot need. - after we create child process - we need close client pipe handle(which we duplicate to child) and just call
ReadFile
on our pipehandle. when thisReadFile
complete - we need again callReadFile
from callback and so on - until we not got error fromReadFile
in completion (usuallyERROR_BROKEN_PIPE
). so we needall time have active read request from pipe, until disconnect. - and we free call
WriteFile
at any time and any place - this nevercause deadlock, because we use asynchronous io. - some time (very very rarely) if we need complex processing on readdata(based on previous results and state) and this much more easyhandle in plain procedure but not in callbacks, we can create fiberfor this task (
CreateFiber
) and from working thread callback,when read complete - first callConvertThreadToFiber
(if wecall this more than once for same working thread - will be errorERROR_ALREADY_FIBER
on second and next calls, but this is ok. butall this work begin from vista only. on xp error here). remembercurrent fiber, to where need retirn (GetCurrentFiber()
) andcallSwitchToFiber
(with our dedicated for read fiber)- wherewe can handle read result and after this return back by callSwitchToFiber
(with fiber for worked thread). but all thisreally can be need in in very rare and specific scenarios. usuallyhandle all is callbacks with state in object related to pipe handle - more than enough.
简单示例为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=%u\n", _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, "help\r\nexit\r\n");
U_IRP* p;
if (p = new U_IRP(&obj))
{
p->Read();
p->Release();
}
obj.EndIo();
//++ simulate user commands
static PCSTR commands[] = { "help\r\n", "ver\r\n", "dir\r\n", "exit\r\n" };
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);
}
}
这篇关于CreateProcess cmd.exe读/写管道死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!