You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用管道重定向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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:15:55