Не удается прочитать из дочернего процесса трубы
Я отчаянно пытаюсь создать дочерний процесс и перенаправить его вывод в новые каналы и читать из этих каналов, но я просто не могу заставить его работать. Я очень новый Win32API, пожалуйста, будьте добры ко мне.:)
После неудачного использования Win32API "нормально", я создал оболочки, чтобы сосредоточиться на поиске ошибки в логике и / или порядке вызовов API. Вы можете найти интерфейс для оберток ниже. Поскольку большинство методов напрямую преобразуются в вызовы Win32API, это (надеюсь) не должно быть препятствием для ответа на этот вопрос.
Я получаю такое же поведение при использовании классов-обёрток, как и раньше.
Я читал много интернет-ресурсов на эту тему, и один говорит что-то другое, чем другой. Самым полезным до сих пор был https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx, особенно эта информация (выделено мной):
Родительский процесс использует противоположные концы этих двух каналов для записи во входные данные дочернего процесса и чтения из выходных данных дочернего процесса. Как указано в структуре STARTUPINFO, эти дескрипторы также наследуются. Однако эти дескрипторы не должны наследоваться. Поэтому перед созданием дочернего процесса родительский процесс использует функцию SetHandleInformation, чтобы гарантировать, что дескриптор записи для стандартного ввода дочернего процесса и дескриптор чтения для стандартного ввода дочернего процесса не могут быть унаследованы. Для получения дополнительной информации см. Трубы.
Прежде чем я нашел эту тему и закрыл концы, которые я не использую со стороны родительского процесса, я возглавляю ReadFile()
навсегда блокирует стандартный выходной дескриптор чтения дочернего процесса. Теперь всегда сразу возвращается, что труба сломана.
Вот как я создаю Трубы и Процесс:
Popen(const String& command, const String& args,
Bool use_current_pipes = false, Bool merge_stderr = true)
{
Bool ok = true;
_error = 0;
ZeroMemory(&_pi, sizeof(_pi));
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
if (!use_current_pipes) {
// Create pipes for standard input, output and error.
_stdin = Pipe(true);
_stdout = Pipe(true);
if (_stdout && merge_stderr)
_stderr = _stdout.Duplicate();
else
_stderr = Pipe(true);
if (_stdin && _stdout && _stderr) {
_stdin.w.SetInheritable(false);
_stderr.r.SetInheritable(false);
_stdout.r.SetInheritable(false);
si.hStdInput = _stdin.r.Get();
si.hStdOutput = _stdout.w.Get();
si.hStdError = _stderr.w.Get();
si.dwFlags |= STARTF_USESTDHANDLES;
}
else {
ok = false;
}
}
else {
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
}
// Create the process. Enclose the actual command in quotes.
ok = ok && CreateProcess(
nullptr, // command might contain whitespace, pass it quoted in arg 2 instead.
AutoString("\"" + command + "\" " + args),
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
true, // handles are inherited
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO
&_pi); // Pointer to PROCESS_INFORMATION
// Something went wrong? Well, bad.
if (!ok) {
_error = GetLastError();
}
// Close the handles that have been inherited by the child process
// and to which we don't need access to, otherwise they will not
// close when the child exits.
_stdin.r.Close();
_stdout.w.Close();
_stderr.w.Close();
}
И вот как я читаю из стандартного вывода (_stdout.r
):
UInt Read(UInt num_bytes, char* buffer) {
if (!_stdout.r) return 0;
DWORD bytes_read = 0;
if (!ReadFile(_stdout.r.Get(), buffer, num_bytes - 1, &bytes_read, nullptr)) {
_error = GetLastError();
ConsoleOut("[ERROR]: ReadFile() : " + String::IntToString((Int32) _error));
if (_error == ERROR_BROKEN_PIPE) {
ConsoleOut("No Wait, the Pipe is just broken.");
_error = 0; // that's fine
}
return 0;
}
buffer[bytes_read] = '\0';
return bytes_read;
}
Когда я закомментирую последние строки Popen
конструктор (закрытие дескрипторов канала, которые не используются из родительского процесса) ReadFile()
блоки навсегда. Если эти строки включены, конвейер всегда сразу прерывается (дочерний процесс завершается довольно быстро).
Вопрос
- Может кто-то увидеть, что не так в моем коде / логике?
- Если нет, то я был бы признателен, если бы был полный рабочий пример открытия дочернего процесса и чтения его результатов.
Интерфейс оболочки
struct Handle {
HANDLE h;
explicit Handle();
explicit Handle(HANDLE h);
Handle(Handle&& other);
Handle& operator = (Handle&& other);
~Handle();
void Close();
HANDLE Get();
HANDLE Release();
Handle Duplicate(DWORD options = DUPLICATE_SAME_ACCESS, HANDLE src_proc = nullptr, HANDLE dst_proc = nullptr) const;
DWORD GetInfo() const; // uses GetHandleInformation
void SetInheritable(bool inheritable) const; // uses SetHandleInformation
bool GetInheritable() const;
operator bool() const;
explicit Handle(const Handle&) = delete;
Handle* operator = (const Handle&) = delete;
};
struct Pipe {
Handle r, w;
DWORD error;
explicit Pipe();
explicit Pipe(bool inheritable);
Pipe(Pipe&& other);
Pipe& operator = (Pipe&& other);
~Pipe();
void Close();
Pipe Duplicate(DWORD options = DUPLICATE_SAME_ACCESS, HANDLE src_proc = nullptr, HANDLE dst_proc = nullptr) const;
operator bool() const;
explicit Pipe(const Pipe&) = delete;
Pipe* operator = (const Pipe&) = delete;
};
1 ответ
Без использования потоков или перекрывающихся операций ввода-вывода вы рискуете зайти в тупик. Дочерний процесс может пытаться читать из его stdin
или ждет места в своем stdout
буфер, чтобы он мог писать, вы не можете сказать, какой, и когда вы выбираете неправильно, вы получаете наблюдаемое поведение. Блокировка чтения на выходе ребенка означает, что вы угадали, и он фактически ожидает ввода.
Прочитайте статью Рэймонда Чена в блоге. Будьте осторожны, перенаправляя как stdin, так и stdout процесса в каналы, поскольку вы можете легко зайти в тупик, который я также связал в своем предыдущем вопросе сегодня. В нем конкретно говорится об ужасном нарушении в том же примере, который вы указали в своем вопросе.