CreateProcess cmd.exe读/写管道死锁

时间:2017-10-06 17:41:11

标签: c++ winapi cmd

您好我正在尝试为cmd.exe创建一个前端GUI,这样我可以让它更宽,但我卡住了。

我尝试设计像这样的API

char* Directory = WriteCommand("dir");
printf("- %s\n", 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

我在这里看到其他几个问题,比如这个有类似问题的问题 - How to read output from cmd.exe using CreateProcess() and CreatePipe()但没有找到解决方案。

所以这是我的代码。

#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”(或任何其他命令)来获取网络信息。该程序陷入僵局并变得无法响应。我再也看不到子进程的输出了。我怎样才能解决这个问题?谢谢你的帮助。

1 个答案:

答案 0 :(得分:2)

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

还要注意使用管道进行重定向输出 - 我们需要为 STDIN STDOUT 使用不同的句柄,并且需要创建2个不同的管道对 - 一个用于< em> STDIN 和 STDOUT 的另一个。这是错误的。我们可以为 STDIN STDOUT (以及 STDERROR )使用单个管道句柄。

  1. 我们需要使用CreateNamedPipeW创建服务器管道句柄 PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED 标志。通过使用PIPE_ACCESS_DUPLEX我们创建双向管道, 因此,服务器和客户端进程都可以读取和写入 到管道。和FILE_FLAG_OVERLAPPED给予异步 模式。我们也不会让这个句柄继承,所以不需要调用 SetHandleInformation就可以了
  2. 我们通过CreateFileW创建的客户端句柄 FILE_GENERIC_READ|FILE_GENERIC_WRITE访问 - 这赋予了能力 将它分配给 stdin stdout 。因为客户(比如 cmd.exe)通常假设同步io - 我们不使用 FILE_FLAG_OVERLAPPED在这里。也可以使用 lpSecurityAttributes 只需使这个句柄可以继承。
  3. 我们需要将绑定服务器句柄绑定到某些IOCP,以便在io时调用回调 结束了。这里我们有3个变种 - 使用 BindIoCompletionCallback - 最简单的方式或用途 CreateThreadpoolIo。我们也可以自己创建IOCP 线程池,但对于重定向子进程输出,通常这种方式 不需要。
  4. 创建子进程后 - 我们需要关闭客户端管道句柄 (我们将其复制到子级)并在我们的管道上调用ReadFile 处理。当ReadFile完成时 - 我们需要再次打电话 来自回调的ReadFile等等 - 直到我们没有收到错误 ReadFile已完成(通常为ERROR_BROKEN_PIPE)。所以我们需要 所有时间都有来自管道的有效读取请求,直到断开连接。
  5. 我们随时随地免费致电WriteFile - 这绝不会 导致死锁,因为我们使用异步io。
  6. 如果我们需要在读取时进行复杂的处理,那么一段时间(非常非常罕见) 数据(基于以前的结果和状态),这更容易 处理普通程序而不是回调,我们可以创建光纤 对于此任务(CreateFiber)和工作线程回调, 阅读完成后 - 首先致电ConvertThreadToFiber(如果我们 对同一个工作线程多次调用它 - 将是错误 第二次和下次通话ERROR_ALREADY_FIBER,但这没关系。但 所有这些工作都只从vista开始。关于这里的xp错误)。记得 当前的光纤,到需要重新定位的地方(GetCurrentFiber())和 致电SwitchToFiber(我们专用于读取光纤) - 其中 我们可以处理读取结果,然后通过调用返回 SwitchToFiber(用于加工螺纹的纤维)。但这一切 在非常罕见和特定的场景中真的需要。平时 处理所有与管道相关的对象状态的回调 - 绰绰有余。
  7. 只是使用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);
        }
    }