如何使用 CreateProcess() 和 CreatePipe() 从 cmd.exe 读取输出
我一直在尝试创建一个执行 cmd.exe
的子进程,命令行指定 /K dir
。目的是使用管道将命令的输出读回父进程。
我已经 CreateProcess()
工作,但是涉及管道的步骤给我带来了麻烦。使用管道,新的控制台窗口没有显示(就像以前一样),并且父进程卡在对 ReadFile()
的调用中。
有谁知道我做错了什么?
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#define BUFFSZ 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;
int wmain(int argc, wchar_t* argv[])
{
int result;
wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /?
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
printf("Starting...\n");
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
// Create one-way pipe for child process STDOUT
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) {
printf("CreatePipe() error: %ld\n", GetLastError());
}
// Ensure read handle to pipe for STDOUT is not inherited
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) {
printf("SetHandleInformation() error: %ld\n", GetLastError());
}
// Create one-way pipe for child process STDIN
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) {
printf("CreatePipe() error: %ld\n", GetLastError());
}
// Ensure write handle to pipe for STDIN is not inherited
if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) {
printf("SetHandleInformation() error: %ld\n", GetLastError());
}
si.cb = sizeof(STARTUPINFO);
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdOutput = g_hChildStd_OUT_Wr;
si.hStdInput = g_hChildStd_IN_Rd;
si.dwFlags |= STARTF_USESTDHANDLES;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
// Pipe handles are inherited
sa.bInheritHandle = true;
// Creates a child process
result = CreateProcess(
TEXT("C:\\Windows\\System32\\cmd.exe"), // Module
aCmd, // Command-line
NULL, // Process security attributes
NULL, // Primary thread security attributes
true, // Handles are inherited
CREATE_NEW_CONSOLE, // Creation flags
NULL, // Environment (use parent)
NULL, // Current directory (use parent)
&si, // STARTUPINFO pointer
&pi // PROCESS_INFORMATION pointer
);
if (result) {
printf("Child process has been created...\n");
}
else {
printf("Child process could not be created\n");
}
bool bStatus;
CHAR aBuf[BUFFSZ + 1];
DWORD dwRead;
DWORD dwWrite;
// GetStdHandle(STD_OUTPUT_HANDLE)
while (true) {
bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
if (!bStatus || dwRead == 0) {
break;
}
aBuf[dwRead] = '\0';
printf("%s\n", aBuf);
}
// Wait until child process exits
WaitForSingleObject(pi.hProcess, INFINITE);
// Close process and thread handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
printf("Stopping...\n");
return 0;
}
原文由 Shuzheng 发布,翻译遵循 CC BY-SA 4.0 许可协议
解决问题的微妙方法是确保关闭不需要的管道末端。
您的父进程有四个句柄:
g_hChildStd_IN_Wr
g_hChildStd_OUT_Rd
g_hChildStd_IN_Rd
g_hChildStd_OUT_Wr
您的父进程只需要每个管道的 一端:
g_hChildStd_IN_Wr
g_hChildStd_OUT_Rd
启动子进程后:确保关闭不再需要的管道末端:
CloseHandle(g_hChildStd_IN_Rd)
CloseHandle(g_hChildStd_OUT_Wr)
离开:
或者更全面:
等待子进程(也就是等待发生的死锁)
大多数解决方案的常见问题是 人们试图 等待进程句柄。
那是错误的。 那都是错的。
这些想法有很多问题;主要的是:
如果孩子试图通过管道向您发送输出,而您正在
INFINITE
等待,那么您并没有清空管道的末端。最终,孩子正在写信的管道变满了。当孩子尝试写入已满的管道时,它的WriteFile
调用 等待( 即 块)管道有一些空间。结果, 子 进程 永远不会 终止;你让一切都陷入僵局。
正确的方法 - 让客户做这件事
正确的解决方案是 简单地从管道中读取。
CloseHandle
。ERROR_BROKEN_PIPE
)。所有这些都没有危险的 MsgWaitForSingleObject - 这很容易出错,难以正确使用,并且会导致您想要避免的错误。
完整示例
我们都知道我们使用它的目的:运行一个子进程,并捕获它的控制台输出。
这是一些示例 Delphi 代码: