gpt4 book ai didi

c++ - 在某些情况下重定向子进程的 STDOUT 时应用程序卡住

转载 作者:太空宇宙 更新时间:2023-11-04 13:28:14 25 4
gpt4 key购买 nike

好的,所以我的基本任务是创建一个函数来运行一个进程并从中获取控制台输出(stdout 和 stderr),并可能指定创建的进程执行超时。

第一种方法是创建临时文件(使用FILE_FLAG_DELETE_ON_CLOSE)并将其指定为hStdOutput/hStdError for STARTUPINFO,等待处理并读取文件的内容。这很好用。但我不喜欢创建临时文件的想法。我从 msdn 中找到了使用管道重定向控制台输出的示例(请参阅 here)。这是重写/修复的代码(作为测试的命令行工具):

#include <Windows.h>
#include <tchar.h>

#include <cassert>

#include <string>
#include <stdexcept>
#include <memory>
#include <type_traits>
#include <iostream>


using string_t = std::basic_string<TCHAR>;

#define THROW_E(X) throw std::runtime_error{(X) + std::string(": ") + std::to_string(::GetLastError())};
#define THROW(X) throw std::runtime_error{(X)};

namespace {

// From
// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
void ArgvQuote(
const string_t& Argument,
string_t& CommandLine,
bool Force)
{
if (Force == false &&
Argument.empty () == false &&
Argument.find_first_of (_T(" \t\n\v\"")) == Argument.npos)
{
CommandLine.append (Argument);
}
else {
CommandLine.push_back (_T('"'));

for (auto It = Argument.begin () ; ; ++It) {
unsigned NumberBackslashes = 0;

while (It != Argument.end () && *It == _T('\\')) {
++It;
++NumberBackslashes;
}

if (It == Argument.end ()) {
CommandLine.append (NumberBackslashes * 2, _T('\\'));
break;
}
else if (*It == _T('"')) {
CommandLine.append (NumberBackslashes * 2 + 1, _T('\\'));
CommandLine.push_back (*It);
}
else {
CommandLine.append (NumberBackslashes, _T('\\'));
CommandLine.push_back (*It);
}
}
CommandLine.push_back (_T('"'));
}
}
using handle_ptr = std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)>;

handle_ptr CreateHandlePtr(HANDLE h)
{
return handle_ptr{h, &::CloseHandle};
}

handle_ptr CreateChildProcess(string_t cmd_line, handle_ptr&& std_out, const string_t& dir)
{
assert(!cmd_line.empty());
assert(std_out.get());

PROCESS_INFORMATION pi = {};
STARTUPINFO si = {};
si.cb = sizeof(STARTUPINFO);
si.hStdError = std_out.get();
si.hStdOutput = std_out.get();
si.wShowWindow = SW_HIDE;
si.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

LPTSTR p_cmd_line = &cmd_line[0];
auto created = CreateProcess(NULL,
p_cmd_line,
nullptr,
nullptr,
TRUE, // Handles are inherited
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment
dir.empty() ? nullptr : dir.c_str(),
&si,
&pi);
if(!created)
THROW_E("CreateProcess()");

std_out.reset();
::CloseHandle(pi.hThread);
return CreateHandlePtr(pi.hProcess);
}

std::string ReadFromPipe(handle_ptr&& std_out)
{
std::string content;
DWORD read = 0;
CHAR buf[1 * 1024] = {};
BOOL success = FALSE;
for(;;)
{
success = ::ReadFile(std_out.get(), buf, _countof(buf), &read, nullptr);
if(!success && (::GetLastError() != ERROR_BROKEN_PIPE))
THROW_E("ReadFile()");

if(!success || (read == 0))
break;
content.append(buf, read);
}

return content;
}

} // namespace

std::string ConsoleOutFromExec(const string_t& cmd_line, std::size_t timeout_msecs = INFINITE,
const string_t& directory = string_t{})
{
auto child_stdout_r = CreateHandlePtr(nullptr);
auto child_stdout_w = CreateHandlePtr(nullptr);
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;

HANDLE raw_child_stdout_r = nullptr;
HANDLE raw_child_stdout_w = nullptr;
if(!::CreatePipe(&raw_child_stdout_r, &raw_child_stdout_w, &sa, 0))
THROW_E("CreatePipe()");
child_stdout_r = CreateHandlePtr(raw_child_stdout_r);
child_stdout_w = CreateHandlePtr(raw_child_stdout_w);

if(!::SetHandleInformation(child_stdout_r.get(), HANDLE_FLAG_INHERIT, 0))
THROW_E("SetHandleInformation()");
}

auto child_proc = CreateChildProcess(cmd_line, std::move(child_stdout_w), directory);
auto status = ::WaitForSingleObject(child_proc.get(), timeout_msecs);
switch(status)
{
case WAIT_OBJECT_0:
break;
default:
THROW("WaitForSingleObject()");
}

return ReadFromPipe(std::move(child_stdout_r));
}

std::size_t ParseTimeout(const string_t& str)
{
std::size_t timeout = INFINITE;
if(str == _T("INFINITE"))
return timeout;
auto p_begin = str.c_str();
TCHAR* p_end = nullptr;
timeout = static_cast<std::size_t>(_tcstol(p_begin, &p_end, 10));
if((timeout == 0) || !p_end || (*p_end != _T('\0')))
THROW("Invalid timeout string");
return timeout;
}

int _tmain(int argc, TCHAR* argv[])
{
if(argc <= 2)
{
std::cout << "Invalid command line:" << "\n";
std::cout << "\t" << "<timeout msecs> (or INFINITE) <exe> <arg0> <arg1> ..." << "\n";
return EXIT_FAILURE;
}

try {
std::size_t timeout = ParseTimeout(argv[1]);
string_t cmd_line;
if(_tcslen(argv[2]) == 0)
THROW("Executable path is empty");

for(int i = 2; i < argc; ++i)
{
#if(1)
ArgvQuote(argv[i], cmd_line, true);
#else
cmd_line.append(argv[i]);
#endif
if(i < (argc - 1))
cmd_line.push_back(_T(' '));
}

auto content = ConsoleOutFromExec(cmd_line, timeout);
std::cout << content << "\n";
} catch(const std::exception& e) {
std::cout << "EXCEPTION: " << e.what() << "\n";
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

如您所见,没有什么新内容(与上面的链接相比),只是使用 RAII 以获得更清晰的代码并且没有标准输入重定向(ArgvQuote() 来自 this link)。

接下来,如果我要运行:

  • child_read.exe INFINITE cmd.exe/c whoami
  • child_read.exe INFINITE cmd.exe/c ipconfig

一切正常。但是下一个命令:

  • child_read.exe INFINITE cmd.exe/c ipconfig/all

将卡住执行。调试器显示问题是 ::WaitForSingleObject() 永远不会返回。而且,实际上,ipconfig 进程处于事件状态: ipconfig /all execution

Windows 8.1 及更高版本会发生这种情况,但在 Windows 7 上一切正常,无需 ipconfig 卡住!!为什么 ?顺便说一句,如果我删除等待进程并开始从管道读取 - 一切正常,我将有应用程序输出(但我需要有执行超时)!

有人可以解释为什么会这样吗?这是仅与 ipconfig/all 命令有关,还是我的代码有问题,这也可能发生在任何其他应用程序上?

(如果 stdin 也被重定向,我在 google/stackoverflow 中发现的所有内容都与卡住有关...)

最佳答案

非常感谢@Cheers 和 hth。 - 阿尔夫。正如他在评论中所说:

You need to replace the infinite wait wait a wait-and-read loop. A pipe doesn't have an infinite buffer. –

为了避免::ReadFile() 在子进程控制台上没有数据时阻塞,需要使用PeekNamedPipe()。函数,它将告诉我们管道中有多少字节可用(在我们的例子中是子进程控制台)。因此,这是我的问题中的固定 ReadFromPipe() 函数:

std::string ReadFromPipe(
handle_ptr&& std_out, // handle to child console
handle_ptr&& wait_on, // handle to child process
std::size_t timeout_msecs,
bool* timeout)
{
std::string content;
DWORD read = 0;
DWORD bytes_available = 0;
DWORD total_read = 0;
CHAR buf[1 * 1024] = {};
BOOL done = FALSE;
std::size_t current_waittime = 0;

while(!done)
{
bytes_available = static_cast<DWORD>(-1);
switch(::WaitForSingleObject(wait_on.get(), 1))
{
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
if(!::PeekNamedPipe(std_out.get(), nullptr, 0, nullptr, &bytes_available, nullptr) &&
(::GetLastError() != ERROR_BROKEN_PIPE))
THROW_E("PeekNamedPipe()");
break;
}

total_read = 0;
while(total_read < bytes_available)
{
DWORD count = (std::min)(static_cast<DWORD>(_countof(buf)), static_cast<DWORD>(bytes_available - total_read));
if(!::ReadFile(std_out.get(), buf, count, &read, nullptr) &&
(::GetLastError() != ERROR_BROKEN_PIPE))
THROW_E("ReadFile()");
else if(read == 0)
{
done = TRUE;
break;
}

content.append(buf, read);
total_read += read;
}

if(++current_waittime >= timeout_msecs)
{
*timeout = true;
content.clear();
break;
}
}

return content;
}

这里是所有代码:

#include <Windows.h>
#include <tchar.h>

#include <cassert>

#include <string>
#include <stdexcept>
#include <memory>
#include <type_traits>
#include <iostream>
#include <algorithm>

using string_t = std::basic_string<TCHAR>;

#define THROW_E(X) THROW((X) + std::string(": ") + std::to_string(::GetLastError()))
#define THROW(X) throw std::runtime_error{(X)}

namespace {

using handle_ptr = std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)>;

handle_ptr CreateHandlePtr(HANDLE h)
{
return handle_ptr{h, &::CloseHandle};
}

handle_ptr CreateChildProcess(string_t cmd_line, handle_ptr&& std_out, const string_t& dir)
{
assert(!cmd_line.empty());
assert(std_out.get());

PROCESS_INFORMATION pi = {};
STARTUPINFO si = {};
si.cb = sizeof(STARTUPINFO);
si.hStdError = std_out.get();
si.hStdOutput = std_out.get();
si.wShowWindow = SW_HIDE;
si.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

LPTSTR p_cmd_line = &cmd_line[0];
auto created = CreateProcess(NULL,
p_cmd_line,
nullptr,
nullptr,
TRUE, // Handles are inherited
CREATE_NEW_CONSOLE,
NULL, // Use parent's environment
dir.empty() ? nullptr : dir.c_str(),
&si,
&pi);
if(!created)
THROW_E("CreateProcess()");

std_out.reset();
::CloseHandle(pi.hThread);
return CreateHandlePtr(pi.hProcess);
}

std::string ReadFromPipe(
handle_ptr&& std_out, // handle to child console
handle_ptr&& wait_on, // handle to child process
std::size_t timeout_msecs,
bool* timeout)
{
std::string content;
DWORD read = 0;
DWORD bytes_available = 0;
DWORD total_read = 0;
CHAR buf[1 * 1024] = {};
BOOL done = FALSE;
std::size_t current_waittime = 0;

while(!done)
{
bytes_available = static_cast<DWORD>(-1);
switch(::WaitForSingleObject(wait_on.get(), 1))
{
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
if(!::PeekNamedPipe(std_out.get(), nullptr, 0, nullptr, &bytes_available, nullptr) &&
(::GetLastError() != ERROR_BROKEN_PIPE))
THROW_E("PeekNamedPipe()");
break;
}

total_read = 0;
while(total_read < bytes_available)
{
DWORD count = (std::min)(static_cast<DWORD>(_countof(buf)), static_cast<DWORD>(bytes_available - total_read));
if(!::ReadFile(std_out.get(), buf, count, &read, nullptr) &&
(::GetLastError() != ERROR_BROKEN_PIPE))
THROW_E("ReadFile()");
else if(read == 0)
{
done = TRUE;
break;
}

content.append(buf, read);
total_read += read;
}

if(++current_waittime >= timeout_msecs)
{
*timeout = true;
content.clear();
break;
}
}

return content;
}

} // namespace

std::string ConsoleOutFromExec(const string_t& cmd_line, std::size_t timeout_msecs = INFINITE,
const string_t& directory = string_t{})
{
auto child_stdout_r = CreateHandlePtr(nullptr);
auto child_stdout_w = CreateHandlePtr(nullptr);
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;

HANDLE raw_child_stdout_r = nullptr;
HANDLE raw_child_stdout_w = nullptr;
if(!::CreatePipe(&raw_child_stdout_r, &raw_child_stdout_w, &sa, 0))
THROW_E("CreatePipe()");
child_stdout_r = CreateHandlePtr(raw_child_stdout_r);
child_stdout_w = CreateHandlePtr(raw_child_stdout_w);

if(!::SetHandleInformation(child_stdout_r.get(), HANDLE_FLAG_INHERIT, 0))
THROW_E("SetHandleInformation()");
}

auto child_proc = CreateChildProcess(cmd_line, std::move(child_stdout_w), directory);

bool timeout = false;
auto content = ReadFromPipe(std::move(child_stdout_r), std::move(child_proc), timeout_msecs, &timeout);
if(timeout)
THROW("Execution timeout");
return content;
}

std::size_t ParseTimeout(const string_t& str)
{
std::size_t timeout = INFINITE;
if(str == _T("INFINITE"))
return timeout;
auto p_begin = str.c_str();
TCHAR* p_end = nullptr;
timeout = static_cast<std::size_t>(_tcstol(p_begin, &p_end, 10));
if((timeout == 0) || !p_end || (*p_end != _T('\0')))
THROW("Invalid timeout string");
return timeout;
}

int _tmain(int argc, TCHAR* argv[])
{
if(argc <= 2)
{
std::cout << "Invalid command line:" << "\n";
std::cout << "\t" << "<timeout msecs> (or INFINITE) <exe> <arg0> <arg1> ..." << "\n";
return EXIT_FAILURE;
}

try {
std::size_t timeout = ParseTimeout(argv[1]);
string_t cmd_line;
if(_tcslen(argv[2]) == 0)
THROW("Executable path is empty");

for(int i = 2; i < argc; ++i)
{
// TODO: quote arguments correctly
cmd_line.append(argv[i]);
if(i < (argc - 1))
cmd_line.push_back(_T(' '));
}

auto content = ConsoleOutFromExec(cmd_line, timeout);
std::cout << content << "\n";
} catch(const std::exception& e) {
std::cout << "EXCEPTION: " << e.what() << "\n";
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

再次,非常感谢@Cheers 和 hth。 - 阿尔夫的帮助!

关于c++ - 在某些情况下重定向子进程的 STDOUT 时应用程序卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32562909/

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