使用管道重定向Windows cmd.exe I/O时的死锁问题求解
问题背景
我尝试用C语言编写程序,通过管道重定向cmd.exe进程的I/O,实现由程序输入命令并获取输出,同时让cmd.exe在后台运行(已知可通过cmd /C <command>处理任务,但不采用此方式)。
当前实现方案
- 创建两个管道,分别用于
stdin和stdout; - 通过
CreateProcess创建cmd.exe子进程,利用STARTUPINFO设置其stdin和stdout为对应管道的读写端; - 通过
WriteFile向stdin管道的写入端输入命令; - 通过
ReadFile从stdout管道的读取端读取输出。
演示代码
// NOTE: This code is for demonstration purposes only. // It is not optimized for performance or error handling. #include <windows.h> #include <stdio.h> #include <string.h> int main() { BOOL success = FALSE; PROCESS_INFORMATION ProcessInfo = {}; STARTUPINFO StartupInfo = {}; SECURITY_ATTRIBUTES SecurityAttr = {}; HANDLE StdinRead, StdinWrite; HANDLE StdoutRead, StdoutWrite; CHAR buffer[1024] = "echo HelloWorld!\n"; DWORD WrittenBytes = 0; DWORD ReadedBytes = 0; SecurityAttr.bInheritHandle = TRUE; if (!CreatePipe(&StdinRead, &StdinWrite, &SecurityAttr, 0)) { fprintf(stderr, "[!] Stdin pipe creation failed.\n"); exit(1); } if (!SetHandleInformation(StdinWrite, HANDLE_FLAG_INHERIT, 0)) { fprintf(stderr, "[!] Stdin SetHandleInformation failed.\n"); exit(1); } if (!CreatePipe(&StdoutRead, &StdoutWrite, &SecurityAttr, 0)) { fprintf(stderr, "[!] Stdout pipe creation failed.\n"); exit(1); } if (!SetHandleInformation(StdoutRead, HANDLE_FLAG_INHERIT, 0)) { fprintf(stderr, "[!] Stdout SetHandleInformation failed.\n"); exit(1); } StartupInfo.cb = sizeof(STARTUPINFO); StartupInfo.hStdError = StdoutWrite; StartupInfo.hStdOutput = StdoutWrite; StartupInfo.hStdInput = StdinRead; StartupInfo.dwFlags = STARTF_USESTDHANDLES; success = CreateProcess("C:\\Windows\\System32\\cmd.exe", NULL, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &StartupInfo, &ProcessInfo); if (!success) { fprintf(stderr, "[!] CreateProcess failed.\n"); exit(1); } CloseHandle(StdoutWrite); CloseHandle(StdinRead); success = WriteFile(StdinWrite, buffer, strlen(buffer), &WrittenBytes, NULL); if (!success) { fprintf(stderr, "[!] WriteFile failed.\n"); exit(1); } while (TRUE) { success = ReadFile(StdoutRead, buffer, sizeof(buffer), &ReadedBytes, NULL); if (!success) { fprintf(stderr, "[!] ReadFile failed.\n"); exit(1); } buffer[ReadedBytes] = 0; printf("[*] ReadFile output: %s\n", buffer); } return 0; }
遇到的死锁问题
当cmd.exe完成写入stdout且无更多数据时,ReadFile调用会导致死锁,程序输出如下:
[*] ReadFile output: Microsoft Windows 10.0.19043 [*] ReadFile output: D:cmd_stdio>HelloWorld! D:cmd_stdio>^C
必须通过CTRL+C才能关闭程序。
微软在《Creating a Child Process with Redirected Input and Output》文档中提到:
// Close the pipe handle so the child process stops reading.
但我不想关闭stdin管道的写入端。
尝试的解决方案
我曾想到添加结束标记的方案,但并不喜欢该方案,演示代码如下:
// NOTE: This code is for demonstration purposes only. // It is not optimized for performance or error handling. #include <stdio.h> #include <string.h> #include <windows.h> #define END_FLAG "[cmd is done]" int main() { // same code as above strcat(buffer, "; echo " END_FLAG "\n"); success = WriteFile(StdinWrite, buffer, strlen(buffer), &WrittenBytes, NULL); ZeroMemory(buffer, sizeof(buffer)); if (!success) { fprintf(stderr, "[!] WriteFile failed.\n"); exit(1); } int index = 0; while (TRUE) { success = ReadFile(StdoutRead, buffer + index, sizeof(buffer) - index, &ReadedBytes, NULL); if (!success) { fprintf(stderr, "[!] ReadFile failed.\n"); exit(1); } buffer[index + ReadedBytes] = 0; index += ReadedBytes; if (strstr(buffer, END_FLAG) != NULL) { printf("[*] Command execution completed.\n"); break; } } printf("[*] ReadFile output: %s\n", buffer); return 0; } // (you could create a 64-bit flag and XOR it with the last 8bytes of written data to the buffer, this code only for explaining my point)
我的问题是:有没有更好的方法判断子进程是否完成写入并准备好接收输入?
问题修正与正确方向
有评论指出:
"我正尝试编写一个小型终端模拟器"——请参考伪控制台会话的创建方法。另外,如果追求极致性能的话,OpenGL并不是合适的API。
——IInspectable,2024年10月28日15:45评论
我发现仅通过管道启动shell程序(如cmd.exe)并附加I/O无法实现需求,在了解到Windows的“伪控制台”(conPTY)后,意识到原问题方向有误。
原问题“有没有更好的方法判断子进程是否完成写入并准备好接收输入?”已不再成立,因为终端并非以此方式工作。更多信息可参考Aram Drevekenin的《Terminal Anatomy》一文。
内容的提问来源于stack exchange,提问作者PaNDa
相关产品推荐
相关产品推荐

