gpt4 book ai didi

c++ - 如何使用 CreateProcess() 和 CreatePipe() 从 cmd.exe 读取输出

转载 作者:可可西里 更新时间:2023-11-01 13:06:10 25 4
gpt4 key购买 nike

How to read output from cmd.exe using CreateProcess() and CreatePipe()

我一直在尝试创建一个子进程来执行 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;
}

最佳答案

解决问题的微妙方法是确保关闭不需要的管道末端。

您的父进程有四个句柄:

  • 其中两个是您的 pipe 的末端
    • g_hChildStd_IN_Wr
    • g_hChildStd_OUT_Rd
  • 其中两个是管道的 child
    • g_hChildStd_IN_Rd
    • g_hChildStd_OUT_Wr

╔══════════════════╗                ╔══════════════════╗
║ Parent Process ║ ║ Child Process ║
╠══════════════════╣ ╠══════════════════╣
║ ║ ║ ║
║ g_hChildStd_IN_Wr╟───────────────>║g_hChildStd_IN_Rd ║
║ ║ ║ ║
║g_hChildStd_OUT_Rd║<───────────────╢g_hChildStd_OUT_Wr║
║ ║ ║ ║
╚══════════════════╝ ╚══════════════════╝

您的父进程只需要每个管道的端:

  • 子输入管道的可写端:g_hChildStd_IN_Wr
  • 子输出管道的可读端:g_hChildStd_OUT_Rd

启动子进程后:确保关闭不再需要的管道末端:

  • CloseHandle(g_hChildStd_IN_Rd)
  • CloseHandle(g_hChildStd_OUT_Wr)

离开:

╔══════════════════╗                ╔══════════════════╗
║ Parent Process ║ ║ Child Process ║
╠══════════════════╣ ╠══════════════════╣
║ ║ ║ ║
║ g_hChildStd_IN_Wr╟───────────────>║ ║
║ ║ ║ ║
║g_hChildStd_OUT_Rd║<───────────────╢ ║
║ ║ ║ ║
╚══════════════════╝ ╚══════════════════╝

或更完整:

STARTUP_INFO si;
PROCESS_INFO pi;
result = CreateProcess(..., ref si, ref pi);

//Bonus chatter: A common bug among a lot of programmers:
// they don't realize they are required to call CloseHandle
// on the two handles placed in PROCESS_INFO.
// That's why you should call ShellExecute - it closes them for you.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

/*
We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the child process.
When the child processes closes, it will close the pipe, and
your call to ReadFile will fail with error code:
109 (The pipe has been ended).

That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)
*/
CloseHandle(g_hChildStd_OUT_Wr);
g_hChildStd_OUT_Wr = 0;
CloseHandle(g_hChildStd_IN_Rd);
g_hChildStd_OUT_Wr = 0;

等待子进程(也就是等待发生的死锁)

大多数解决方案的共同问题是 people try等待进程句柄。

  • 他们创建事件对象
  • 他们尝试 MsgWait 等待事件发出信号
  • 他们尝试 MsgWait 以等待子进程结束

这是错误的。 都错了

这些想法有很多问题;主要的是:

  • 如果你试图等待 child 终止
  • child 将永远无法终止

如果 child 试图通过管道向您发送输出,而您正在INFINITE 等待,那么您并没有清空管道的末端。最终, child 正在写入的管道变满了。当子进程尝试写入已满的管道时,它的 WriteFile 调用会等待( block )让管道有一些空间。

  • 你被阻止等待 child
  • child 尝试写入管道
  • 你被阻止等待 child ,所以你没有从管道中读取数据
  • 管道变满了
  • child 在等你
  • parent 和 child 都被阻止等待对方
  • 僵局

因此,进程将永不终止;你让一切都陷入僵局。

正确的方法——让客户做这件事

正确的解决方案来自简单地从管道中读取

  • 一旦子进程终止,
  • 它将在管道的它的CloseHandle
  • 下次尝试从管道读取数据时
  • 您会被告知管道已关闭 (ERROR_BROKEN_PIPE)。
  • 这就是您知道流程已完成并且您没有更多内容可读的方式。

String outputText = "";

//Read will return when the buffer is full, or if the pipe on the other end has been broken
while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null)
outputText = outputText + Copy(aBuf, 1, bytesRead);

//ReadFile will either tell us that the pipe has closed, or give us an error
DWORD le = GetLastError;

//And finally cleanup
CloseHandle(g_hChildStd_IN_Wr);
CloseHandle(g_hChildStd_OUT_Rd);

if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended."
RaiseLastOSError(le);

所有这些都没有危险的 MsgWaitForSingleObject - 它容易出错,难以正确使用,并且会导致您想要避免的错误。

完整示例

我们都知道我们用它做什么:运行一个子进程,并捕获它的控制台输出。

这是一些示例 Delphi 代码:

function ExecuteAndCaptureOutput(CommandLine: string): string;
var
securityAttributes: TSecurityAttributes;
stdOutRead, stdOutWrite: THandle;
startupInfo: TStartupInfo;
pi: TProcessInformation;
buffer: AnsiString;
bytesRead: DWORD;
bRes: Boolean;
le: DWORD;
begin
{
Execute a child process, and capture it's command line output.
}
Result := '';

securityAttributes.nlength := SizeOf(TSecurityAttributes);
securityAttributes.bInheritHandle := True;
securityAttributes.lpSecurityDescriptor := nil;

if not CreatePipe({var}stdOutRead, {var}stdOutWrite, @securityAttributes, 0) then
RaiseLastOSError;
try
// Set up members of the STARTUPINFO structure.
startupInfo := Default(TStartupInfo);
startupInfo.cb := SizeOf(startupInfo);

// This structure specifies the STDIN and STDOUT handles for redirection.
startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESTDHANDLES; //The hStdInput, hStdOutput, and hStdError handles will be valid.
startupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //don't forget to make it valid (zero is not valid)
startupInfo.hStdOutput := stdOutWrite; //give the console app the writable end of the pipe
startupInfo.hStdError := stdOutWrite; //give the console app the writable end of the pipe

// We also want the console window to be hidden
startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESHOWWINDOW; //The nShowWindow member member will be valid.
startupInfo.wShowWindow := SW_HIDE; //default is that the console window is visible

// Set up members of the PROCESS_INFORMATION structure.
pi := Default(TProcessInformation);

//WARNING: The Unicode version of CreateProcess can modify the contents of CommandLine.
//Therefore CommandLine cannot point to read-only memory.
//We can ensure it's not read-only with the RTL function UniqueString
UniqueString({var}CommandLine);

bRes := CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, nil, startupInfo, {var}pi);
if not bRes then
RaiseLastOSError;

//CreateProcess demands that we close these two populated handles when we're done with them. We're done with them.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

{
We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app.
When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended).
That's how we'll know the console app is done. (no need to wait on process handles)
}
CloseHandle(stdOutWrite);
stdOutWrite := 0;

SetLength(buffer, 4096);

//Read will return when the buffer is full, or if the pipe on the other end has been broken
while ReadFile(stdOutRead, buffer[1], Length(buffer), {var}bytesRead, nil) do
Result := Result + string(Copy(buffer, 1, bytesRead));

//ReadFile will either tell us that the pipe has closed, or give us an error
le := GetLastError;
if le <> ERROR_BROKEN_PIPE then //"The pipe has been ended."
RaiseLastOSError(le);
finally
CloseHandle(stdOutRead);
if stdOutWrite <> 0 then
CloseHandle(stdOutWrite);
end;
end;

关于c++ - 如何使用 CreateProcess() 和 CreatePipe() 从 cmd.exe 读取输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35969730/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com