Введение
Многие приложения должны взаимодействовать с внешними программами: запускать утилиты командной строки, конвертеры, компиляторы, скрипты. В Qt для этого есть класс QProcess, который запускает внешний процесс и предоставляет асинхронный интерфейс для отправки данных в его стандартный ввод и чтения из стандартного вывода и потока ошибок — по аналогии с QIODevice, что делает работу с дочерним процессом похожей на работу с файлом или сокетом.
Концепция
QProcess::start() запускает программу асинхронно; о завершении процесса сигнализирует finished(int exitCode, QProcess::ExitStatus exitStatus), а о готовности новых данных в stdout/stderr — сигналы readyReadStandardOutput()/readyReadStandardError(). Запись в стандартный ввод дочернего процесса осуществляется через write(), как и для любого QIODevice.
Важная деталь — каналы вывода по умолчанию разделены (SeparateChannels): нужно отдельно читать readAllStandardOutput() и readAllStandardError(). Если важен только общий вывод без разделения на потоки, можно объединить их через setProcessChannelMode(QProcess::MergedChannels).
Пример кода
#include <QCoreApplication>
#include <QProcess>
#include <QDebug>
class ProcessRunner : public QObject
{
Q_OBJECT
public:
void runCommand(const QString &program, const QStringList &arguments)
{
connect(&m_process, &QProcess::readyReadStandardOutput, this, [this]() {
qDebug().noquote() << "stdout:" << m_process.readAllStandardOutput();
});
connect(&m_process, &QProcess::readyReadStandardError, this, [this]() {
qWarning().noquote() << "stderr:" << m_process.readAllStandardError();
});
connect(&m_process, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) {
qWarning() << "Ошибка запуска процесса:" << error;
});
connect(&m_process, &QProcess::finished, this,
[this](int exitCode, QProcess::ExitStatus status) {
qDebug() << "Процесс завершён, код:" << exitCode
<< "статус:" << (status == QProcess::NormalExit ? "обычный" : "сбой");
emit finished();
});
m_process.start(program, arguments);
}
// Пример интерактивного взаимодействия через stdin
void sendInput(const QString &text)
{
m_process.write((text + "n").toUtf8());
}
signals:
void finished();
private:
QProcess m_process;
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ProcessRunner runner;
QObject::connect(&runner, &ProcessRunner::finished, &app, &QCoreApplication::quit);
// Пример: запуск python-скрипта, читающего строку из stdin
runner.runCommand("python3", {"-c", "print(input('Echo: '))"});
QTimer::singleShot(200, [&runner]() {
runner.sendInput("Привет из QProcess");
});
return app.exec();
}
#include "main.moc"
Пояснения к коду
ProcessRunner подписывается на все ключевые сигналы QProcess перед вызовом start(): вывод (readyReadStandardOutput/readyReadStandardError), ошибки запуска (errorOccurred, срабатывает, например, если исполняемый файл не найден) и завершение (finished, с отдельными кодом возврата и статусом завершения). Метод sendInput() показывает, как писать в стандартный ввод запущенного процесса — это работает, только если сам процесс ожидает ввод (как в примере с input() в Python).
Обратите внимание на использование .noquote() при логировании вывода процесса — без него qDebug() оборачивает QByteArray в кавычки и экранирует управляющие символы, что неудобно при выводе многострочного текста.
Подводные камни
- Путь к исполняемому файлу не найден в PATH на целевой машине. Жёстко прописанный относительный путь к программе, работающий на машине разработчика, может не сработать в продакшен-окружении — лучше искать исполняемый файл через
QStandardPaths::findExecutable()или явно указывать полный путь, разворачиваемый в зависимости от ОС. - Дедлок из-за переполнения буфера stdout/stderr. Если дочерний процесс производит много вывода, а приложение не читает его своевременно (например, ждёт
waitForFinished()без параллельного чтения), внутренний буфер ОС может заполниться, и дочерний процесс заблокируется на попытке писать дальше — это классический deadlock при синхронном взаимодействии с дочерними процессами. waitForFinished()без таймаута может зависнуть навечно, если дочерний процесс зависает сам или ждёт ввода, который никогда не придёт — всегда указывайте таймаут и обрабатывайте случай, когда процесс не завершился (process.kill()).- Кодировка вывода процесса. Вывод стандартных консольных утилит на Windows часто в кодировке CP866/CP1251, а не UTF-8 — прямое преобразование
QByteArrayвQStringбез учёта реальной кодировки даёт «кракозябры»; нужно явно конвертировать черезQStringDecoderили соответствующий codec с указанием правильной кодировки источника.