Я работаю над своей библиотекой, которая должна перехватывать и обрабатывать стандартный вывод (и ошибку) дочернего процесса во время его работы.Проблема возникает, когда ReadFile используется для чтения выходных данных, он не возвращается после завершения процесса (уничтожается или завершается).
Похоже, ReadFile
не может обнаружитьчто другой конец канала (дескриптор записи) закрыт.Согласно документации он должен вернуть FALSE
и установить последнюю ошибку на ERROR_BROKEN_PIPE
:
Если используется анонимный канал и дескриптор записи был закрыт, когда ReadFile пытается прочитать с использованиемсоответствующий дескриптор чтения канала, функция возвращает FALSE, а GetLastError возвращает ERROR_BROKEN_PIPE.
Вот мой код, я удалил нерелевантные биты: (ПРИМЕЧАНИЕ. Я обновил allium_start
, чтобы следоватьпредлагаемые изменения, я сохраняю оригинал для справки, пожалуйста, используйте новый код функции, чтобы найти недостатки)
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Return on failure
if (!success) return false;
}
char *allium_read_stdout_line(struct TorInstance *instance) {
char *buffer = instance->buffer.data;
// Process the input
unsigned int read_len = 0;
while (true) {
// Read data
unsigned long bytes_read;
if (ReadFile(instance->stdout_pipe, buffer, 1, &bytes_read, NULL) == false || bytes_read == 0) return NULL;
// Check if we have reached end of line
if (buffer[0] == '\n') break;
// Proceed to the next character
++buffer; ++read_len;
}
// Terminate the new line with null character and return
// Special handling for Windows, terminate at CR if present
buffer[read_len >= 2 && buffer[-1] == '\r' ? -1 : 0] = '\0';
return instance->buffer.data;
}
allium_start
создает канал для перенаправления вывода (он использует один и тот же канал как для stdout, так и для stdout).stderr, чтобы получить объединенные потоки), а затем создает дочерний процесс.Другая функция allium_read_stdout_line
отвечает за чтение вывода из канала и его возврат при обнаружении новой строки.
Проблема возникает при вызове функции ReadFile
, она никогда не возвращается, если нет ничегочитать после завершения процесса, из моего понимания все дескрипторы процесса закрываются Windows, когда он завершается, поэтому похоже, что ReadFile
не может обнаружить тот факт, что канал (дескриптор записи) на другом конце былзакрыто.
Как это исправить?Я искал решение, но пока не нашел ни одного, один из возможных вариантов - использовать многопоточность и поместить ReadFile
в отдельный поток, чтобы он не блокировал всю программу, используя этот метод, который я могупроверять, существует ли процесс периодически, пока я жду окончания чтения ... или убить / остановить поток, если процесс завершен.
Я предпочитаю исправить проблему, а не выбирать обходной путь, ноЯ открыт для любых других решений, чтобы заставить это работать.Заранее спасибо!
Редактировать : После прочтения ответа @ RemyLebeau и комментариев @ RbMm в этом ответе становится совершенно ясно, что мое понимание того, как работает наследование дескрипторов, в корне неверно.Поэтому я включил их предложения (SetHandleInformation
, чтобы отключить наследование дескриптора чтения и закрыть его после создания дочернего процесса) в мою функцию allium_start
:
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
SetHandleInformation(output_pipes[0], HANDLE_FLAG_INHERIT, 0);
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Close the write end of our stdout handle
CloseHandle(output_pipes[1]);
// Return on failure
if (!success) return false;
}
(приведенный ниже текст изначально был здесь до редактировать 2 )
Редактировать 2 (после принятия ответа) : Это работает!Смотрите мой последний комментарий о принятом ответе.