【发布时间】:2016-03-13 11:35:54
【问题描述】:
如何使用 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;
}
【问题讨论】:
-
代码只完成了一半,死锁在所难免。 ReadFile() 将在进程填充其标准输出缓冲区之前完成,因此需要刷新或关闭句柄。缓冲区不会被填充,因为它只包含提示。并且没有添加任何内容,也不会关闭句柄,因为您没有告诉它做任何事情。不读取 stderr 也是一个可能导致死锁的错误,否则很容易通过告诉它合并 stdout 和 stderr 输出来避免。嗯,这就是为什么,修复它不是很有用。
-
我该怎么做?我应该创建另一个线程吗?我该如何冲洗?
-
不能强制刷新,是cmd.exe的stdout缓冲区。除非您将“exit\r”发送到标准输入。该程序设计为在您使用 /k 时以交互方式使用,重定向仅在您使用 /c 时才有效。修复此问题需要使用重叠 I/O,以便您可以同时异步读取 stderr 和 stdout,并使用 WaitForMultipleObjects() 以便您可以等待所有三个句柄。
-
请注意,
dwRead == 0不是错误条件。来自ReadFiledocumentation:“如果在管道上 ReadFile 返回 TRUE 时 lpNumberOfBytesRead 参数为零,则管道的另一端调用 WriteFile 函数,并将 nNumberOfBytesToWrite 设置为零。”