Среда, 30.07.2025 10:39

Обработка ошибок и вывода внешних программ в Qt6. Запуск внешних программ в Qt6. Часть 2

Обработка ошибок и вывода внешних программ в 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.

>> Часть 3

Категория Qt6
Теги Qt Qt6 Cpp

Добавить комментарий

Простой текст

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
Просмотров: 301