关于C ++:CreateProcess cmd.exe读/写管道死锁

CreateProcess cmd.exe read/write pipes deadlock

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

我尝试设计这样的API

1
2
3
char* Directory = WriteCommand("dir");
printf("- %s\
"
, Directory);

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

1
2
DATE TIME FILESIZE FILENAME
etc etc etc

然后我可以发出

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

它会给我上面的目录清单。因此,我希望通过使用管道进行读取和写入的终端控件。

我已经基于此MSDN示例代码尝试了很多事情-https://msdn.microsoft.com/zh-cn/library/ms682499.aspx

但是我认为此代码仅是发出一个命令并读取一个响应的好方法,因为它紧接着如此处所述死锁-https://blogs.msdn.microsoft.com/oldnewthing/20110707-00/?p=10223

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

这是我的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#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("\
->Start of parent execution.\
"
);

    // 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.\
"));

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("\
->End of parent execution.\
"
);

// 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\
"
);

    printf("written = %i\
"
, 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\
");


    //printf("written = %i\
", 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\
"
, i, chBuf);
}

printf("done\
"
);
}

我发出了初始的" cmd.exe"命令,该命令为我提供了命令提示符的开始。我现在想发出" ipconfig"(或任何其他命令)以获取网络信息。程序陷入僵局,无法响应。我无法再读取子进程的输出。我怎样才能解决这个问题?谢谢你的帮助。


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

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

  • 我们需要使用CreateNamedPipeW创建服务器管道句柄
    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时调用回调
    结束了。在这里,我们有3个变体-使用
    BindIoCompletionCallback-最简单的方法或使用
    CreateThreadpoolIo。我们也可以自己创建IOCP
    线程池,但是对于重定向子进程输出,通常是这种方式
    不需要。
  • 创建子进程之后-我们需要关闭客户端管道句柄
    (我们复制给子对象),然后在管道上调用ReadFile
    处理。此ReadFile完成后-我们需要再次致电
    ReadFile从回调等等,直到我们没有收到来自
    ReadFile完成(通常是ERROR_BROKEN_PIPE)。所以我们需要
    一直有来自管道的活动读取请求,直到断开连接。
  • 我们随时随地都可以免费拨打WriteFile
    导致死锁,因为我们使用异步io。
  • 如果我们需要读取时进行复杂的处理,则需要一些时间(很少)
    数据(基于以前的结果和状态),这更容易
    在普通过程中处理但不在回调中处理,我们可以创建纤程
    为此任务(CreateFiber)和工作线程回调,
    阅读完成后-首次调用ConvertThreadToFiber(如果我们
    对于同一工作线程多次调用此命令-将出错
    在第二个和下一个呼叫中ERROR_ALREADY_FIBER,但是可以。但
    所有这些工作仅从Vista开始。关于XP错误)。记得
    当前的光纤,到需要退回的位置(GetCurrentFiber())和
    致电SwitchToFiber(我们专用于读取光纤)-
    我们可以处理读取结果,然后通过调用返回
    SwitchToFiber(使用光纤作为工作线)。但是这一切
    在非常罕见和特定的情况下确实需要。通常
    处理所有是与管道句柄相关的对象中具有状态的回调-绰绰有余。
  • cmd的简单示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    #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\
    "
    , _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\
    \
    exit\
    \
    "
    );

                    U_IRP* p;

                    if (p = new U_IRP(&obj))
                    {
                        p->Read();
                        p->Release();
                    }

                    obj.EndIo();

                    //++ simulate user commands
                    static PCSTR commands[] = {"help\
    \
    "
    ,"ver\
    \
    "
    ,"dir\
    \
    "
    ,"exit\
    \
    "
    };
                    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);
        }
    }