Обработка ошибок и вывода внешних программ в Qt6. Запуск внешних программ в Qt6. Часть 2
Статья обновлена 01.08.2025
При запуске программ могут возникнуть ошибки, которые нужно корректно обработать. Результат работы программ, как правило, тоже нуждается в обработке.
В прошлой статье мы не останавливались на обработке ошибок и вывода программы. Нас устраивал сам факт, того, что программа отработала.
Сегодня мы продолжим улучшать проект и рассмотрим методы обработки ошибок, возникающих при запуске внешних программ, и обработку вывода программ.
Обработка ошибок
У класса QProcess есть два сигнала
readyReadStandardError
readyReadStandardOutputСигнал readyReadStandardError отправляется, когда в работе внешней программы произошла ошибка и в стандартный канал для вывода ошибок (stderr) была добавлена новая информация.
Сигнал readyReadStandardOutput когда в в стандартный канал для вывода (stdout) была добавлена новая информация. Фактически сигнал отправляется для каждой строки выводимой программой.
Обновим класс Runner, добавим два новых сигнала:
signals:
void error(QString data);
void ready(QString data);Добавим приватный слот:
private slots:
void onReady();Реализация:
void Runner::onReady()
{ QString res = this->process->readAllStandardOutput();
qDebug() << "Результат:" << res;
emit ready(res);
}В конструктор класса добавим:
connect(this->process, SIGNAL(readyReadStandardOutput()),
this, SLOT(onReady()));Здесь мы соединили сигнал readyReadStandardOutput со слотом onReady.
Приведем метод run() к виду:
void Runner::run()
{
QStringList params;
params.append("/C command.cmd");
qDebug() << "Запуск";
this->process->start("cmd", params);
qDebug() << "Запущено";
this->process->waitForFinished();
emit finished();
}Обратите внимание! На данном этапе мы используем метод waitForFinished() для того, чтобы точно получить весь вывод программы. В дальнейшем, когда мы перейдем на использование слотов и сигналов, мы его удалим.
У нас получится:
Заголовок:
#ifndef RUNNER_H
#define RUNNER_H
#include <QObject>
#include <QProcess>
class Runner : public QObject
{
Q_OBJECT
public:
explicit Runner(QObject *parent = 0);
void run();
private:
QProcess *process;
signals:
void finished();
void error(QString data);
void ready(QString data);
private slots:
void onReady();
};
#endif // RUNNER_HРеализация:
#include "runner.h"
#include <QDebug>
Runner::Runner(QObject *parent) :
QObject(parent)
{
this->process = new QProcess(this);
connect(this->process, SIGNAL(readyReadStandardOutput()),
this, SLOT(onReady()));
qDebug() << "Инит";
}
void Runner::run()
{
QStringList params;
params.append("/C command.cmd");
qDebug() << "Запуск";
this->process->start("cmd", params);
qDebug() << "Запущено";
this->process->waitForFinished();
emit finished();
}
void Runner::onReady()
{
QString res = this->process->readAllStandardOutput();
qDebug() << "Результат:" << res;
emit ready(res);
}Запустим:
Результат: "\r\nD:\\projects\\QtRunnerDemo2\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>sleep 5 \r\n"
Результат: "\r\nD:\\projects\\QtRunnerDemo2\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>dir c:\\windows \r\n"
Результат: " ??? ? ?????? C ?? ????? ???.\r\n ??\u0A29??? ????? ??: 5A32-0ABC\r\n"
Результат: "\r\n ???????? ????? c:\\windows\r\n\r\n"
Результат: "21.07.2025 12:42 <DIR> .\r\n"
Результат: "21.07.2025 12:42 <DIR> ..\r\n08.12.2019 01:37 <DIR> addins\r\n"
Результат: "15.11.2023 13:57 <DIR> ADFS\r\n"
Результат: "21.10.2024 17:10 <DIR> appcompat\r\n"
Результат: "22.01.2025 14:30 <DIR> apppatch\r\n"
Результат: "27.07.2025 13:15 <DIR> AppReadiness\r\n"
Результат: "12.07.2025 14:06 <DIR> bcastdvr\r\n"
…
Результат: "19.02.2025 17:40 93?696 bfsvc.exe\r\n"
Результат: "07.12.2019 08:29 11?264 write.exe\r\n"Я обрезал вывод.
Как видите у нас есть проблемы с кодировкой. Это происходит из-за того, что команды cmd в русской версии Windows выводят результат в кодировке cp866, а консоль Qt6 выводит текст в кодировке Unicode.
Преобразование результата выполнения консольной программы в Unicode
Добавим приватное поле класса Runner:
QTextCodec *codec;Добавим:
#include <QTextCodec>Добавим в конструктор класса:
this->codec = QTextCodec::codecForName("IBM 866");Изменим код метода onReady():
void Runner::onReady()
{
QString res = this->codec->toUnicode(this->process->readAllStandardOutput());
qDebug() << "Результат:" << res;
emit ready(res);
}Здесь мы просто производим перекодировку результата работы метода readAllStandardOutput() в нужную нам кодировку.
У нас получится:
Заголовок:
#ifndef RUNNER_H
#define RUNNER_H
#include <QObject>
#include <QProcess>
#include <QTextCodec>
class Runner : public QObject
{
Q_OBJECT
public:
explicit Runner(QObject *parent = 0);
void run();
private:
QProcess *process;
QTextCodec *codec;
signals:
void finished();
void error(QString data);
void ready(QString data);
private slots:
void onReady();
};
#endif // RUNNER_HРеализация:
#include "runner.h"
#include <QDebug>
Runner::Runner(QObject *parent) :
QObject(parent)
{
this->process = new QProcess(this);
this->codec = QTextCodec::codecForName("IBM 866");
connect(this->process, SIGNAL(readyReadStandardOutput()),
this, SLOT(onReady()));
qDebug() << "Инит";
}
void Runner::run()
{
QStringList params;
params.append("/C command.cmd");
qDebug() << "Запуск";
this->process->start("cmd", params);
qDebug() << "Запущено";
this->process->waitForFinished();
emit finished();
}
void Runner::onReady()
{
QString res = this->codec->toUnicode(this->process->readAllStandardOutput());
qDebug() << "Результат:" << res;
emit ready(res);
}Обновим файл CmakeLists.txt
Добавим после:
find_package(Qt6 REQUIRED COMPONENTS Quick)строку:
find_package(Qt6 REQUIRED COMPONENTS Core5Compat)Перед:
include(GNUInstallDirs)добавим:
target_link_libraries(appQtRunnerDemo3 PRIVATE Qt6::Core5Compat)Замените appQtRunnerDemo3 на имя вашего проекта вы можете найти в строке:
target_link_libraries(appQtRunnerDemo3Запустим:
Результат: " Том в устройстве C не имеет метки.\r\n Серийный номер тома: 0000-0000\r\n"
Результат: "\r\n Содержимое папки c:\\windows\r\n\r\n"
Результат: "21.07.2025 12:42 <DIR> .\r\n"
Результат: "21.07.2025 12:42 <DIR> ..\r\n"
Результат: "08.12.2019 01:37 <DIR> addins\r\n15.11.2023 13:57 <DIR> ADFS\r\n"
Результат: "21.10.2024 17:10 <DIR> appcompat\r\n"
Результат: "22.01.2025 14:30 <DIR> apppatch\r\n"
Результат: "27.07.2025 13:15 <DIR> AppReadiness\r\n"
Результат: "12.07.2025 14:06 <DIR> bcastdvr\r\n"
…
Результат: "07.12.2019 20:10 11 776 winhlp32.exe\r\n"
Результат: "13.07.2025 12:11 <DIR> WinSxS\r\n"
Результат: "08.12.2019 01:39 316 640 WMSysPr9.prx\r\n"
Результат: "07.12.2019 08:29 11 264 write.exe\r\n"
Результат: " 31 файлов 12 550 625 байт\r\n"
Результат: " 79 папок 15 583 043 584 байт свободно\r\n"На этот раз у нас всё отображается корректно.
Обработка ошибок при запуске внешних программ в Qt6
При выполнении внешней программы возможно возникновение ошибок. Зачастую сам текст ошибки выводиться в консоль.
Добавим обработчик ошибок. В конструктор класса добавим:
connect(this->process, SIGNAL(readyReadStandardError()),
this, SLOT(onError()));Добавим слот:
private slots:
void onError();Реализация:
void Runner::onError()
{
QString res = this->codec->toUnicode(this->process->readAllStandardError());
qDebug() << "Результат:" << res;
emit error(res);
}Запустим. Всё работает.
Создадим ошибку. Изменим метод run() В строке
params.append("/C command.cmd");Добавим цифру:
params.append("/C command.cmd1");Запустим:
Инит
Запуск
Запущено
Ошибка: "\"command.cmd1\"\" не является внутренней или внешней\r\nкомандой, исполняемой программой или пакетным файлом.\r\n"Теперь при возникновении ошибки мы можем пол мы можем получить её текст.
Обработка результата выполнения программы и кода ошибки
Помимо самого текста ошибки вам возможно понадобится сам код ошибки или код завершения программы. Для их обработки мы можем воспользоваться сигналом:
finished(int exitCode, QProcess::ExitStatus exitStatus = NormalExit)Добавим слот:
void onFinished(int exitCode, QProcess::ExitStatus exitStatus);Реализация:
void Runner::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qDebug() << "Код завершения" << exitCode;
qDebug() << "Статус выхода" << exitStatus;
}В конструктор класса добавим:
connect(this->process, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(onFinished(int,QProcess::ExitStatus)));Запустим:
Ошибка: "\"command.cmd1\"\" не является внутренней или внешней\r\nкомандой, исполняемой программой или пакетным файлом.\r\n"
Код завершения 1
Статус выхода QProcess::NormalExitВ случае нормального запуска мы получим:
Код завершения 0
Статус выхода QProcess::NormalExitПеременная exitStatus может принимать два значения:
QProcess::NormalExit
QProcess::CrashExitПоэтому для обработки ошибок лучше полагаться на код завершения.
Так же для более сложных случаев есть сигнал
errorOccurredЕго, возможно, я рассмотрю в одной из будущих статей.
Так же на этом этапе можно удалить:
this->process->waitForFinished();Метод waitForFinished блокирует выполнение функции или метода, из которого он вызывается. Теперь, когда мы полностью перешли на использование слотов и сигналов, нам это больше не нужно.
В результате наш класс изменился:
Заголовок:
#ifndef RUNNER_H
#define RUNNER_H
#include <QObject>
#include <QProcess>
#include <QTextCodec>
class Runner : public QObject
{
Q_OBJECT
public:
explicit Runner(QObject *parent = 0);
void run();
private:
QProcess *process;
QTextCodec *codec;
signals:
void finished();
void error(QString data);
void ready(QString data);
private slots:
void onReady();
void onError();
void onFinished(int exitCode, QProcess::ExitStatus exitStatus);
};
#endif // RUNNER_HРеализация:
#include "runner.h"
#include <QDebug>
Runner::Runner(QObject *parent) :
QObject(parent)
{
this->process = new QProcess(this);
this->codec = QTextCodec::codecForName("IBM 866");
connect(this->process, SIGNAL(readyReadStandardOutput()),
this, SLOT(onReady()));
connect(this->process, SIGNAL(readyReadStandardError()),
this, SLOT(onError()));
connect(this->process, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(onFinished(int,QProcess::ExitStatus)));
qDebug() << "Инит";
}
void Runner::run()
{
QStringList params;
params.append("/C command.cmd");
qDebug() << "Запуск";
this->process->start("cmd", params);
qDebug() << "Запущено";
}
void Runner::onReady()
{
QString res = this->codec->toUnicode(this->process->readAllStandardOutput());
qDebug() << "Результат:" << res;
emit ready(res);
}
void Runner::onError()
{
QString res = this->codec->toUnicode(this->process->readAllStandardError());
qDebug() << "Ошибка:" << res;
emit error(res);
}
void Runner::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qDebug() << "Код завершения" << exitCode;
qDebug() << "Статус выхода" << exitStatus;
emit finished();
}Заключение
Сегодня мы рассмотрели обработку ошибок и результата работы запущенной внешней программы в Qt6:
Рассмотрели сигналы readyReadStandardError и readyReadStandardOutput;
Добавили слоты в наш класс и связали их с этими сигналами;
Рассмотрели обработку вывода работы программы в кодировке cp866;
Рассмотрели обработку ошибок, возникающих в ходе работы программы;
Рассмотрели обработку результатов выполнения внешней программы в Qt6.
Добавить комментарий