Windows `cmd.exe` 使用管道重定向 io

问题描述 投票:0回答:1

我正在尝试从c中的程序创建重定向io到

cmd.exe
进程,并使用管道来处理io以处理我的程序的输入命令并返回输出。

我想让

cmd.exe
进程在后台运行,(我知道每次需要处理某些内容时我都可以使用
cmd /C <command>
)。


这是我现在的方法。

  1. 创建两个管道,一个用于
    stdin
    ,另一个用于
    stdout
  2. 使用
    cmd.exe
    CreateProcess
    创建为子进程,并使用
    STARTUPINFO
    将进程
    stdin
    stdout
    设置为
    stdin
    管道的写入端和
    stdout
    管道的读取端.
  3. 使用
    stdin
    将命令输入到
    WriteFile
    管道的写入端。
  4. 使用
    stdout
    ReadFile
    管道的读取端读取输出。

这是我现在脑子里的代码。

// 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;
}

问题在于,当

ReadFile
写入标准输出并且没有更多数据可供读取时,
cmd.exe
调用会生成死锁。

程序的输出是这样的。

[*] 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

// 关闭管道句柄,以便子进程停止读取。

就我而言,我不想关闭

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);
  }

  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)

我的问题是:是否有更好的方法来知道子进程是否已完成写入并准备好接收输入?

c windows winapi
1个回答
0
投票

Microsoft 文章(和您的代码)的设置方式,正在使用同步管道和同步 IO。标准输出管道保持打开状态,直到子进程终止,从而结束阻塞

ReadFile

在没有

cmd
/C
的情况下,当用户关闭控制台窗口时会发生这种情况,但您没有创建控制台窗口,因此
ReadFile
无限期地阻塞(除非您发送另一个命令),因为管道永远不会关闭(除非有人杀死
cmd
进程)。

因此,要么使用

cmd /C
,以便在命令完成时进程结束,要么使用异步管道,以便可以在管道读取上设置超时。

© www.soinside.com 2019 - 2024. All rights reserved.