IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Qt Discussion :

[QProcess] Récupération de la sortie console


Sujet :

Qt

  1. #1
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut [QProcess] Récupération de la sortie console
    Bonjour,

    dans mon programme, je dois exécuter des applications externes; pour cela j'utilise donc QProcess.
    Ces applications donnent des informations en console sur leur déroulement, qui peut prendre plusieurs minutes. Je voudrais afficher sur mon IHM MainWindow (dans un PlainTextEdit) cette sortie au fur et à mesure du déroulement.

    Pour cela j'ai écrit le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
        QProcess* progExterne = new QProcess();
        progExterne->setWorkingDirectory(projectPath);
        progExterne->start("./progExterne.sh");
        bool processStarted = progExterne->waitForStarted();
        if(!processStarted)
        {
            emit error("Impossible d'executer proExterne.sh.");
            return;
        }
        else
        {
            // Suivi du deroulement de progExterne
            progExterne->waitForReadyRead();
            while(progExterne->state() == QProcess::Running)
            {
                sleep(0.01);
                emit textOut(progExterne->readAllStandardOutput());
            }
        }
    J'ai écrit ce code dans un objet Worker dérivant de QObject (dans une méthode "executer"). Dans ma classe MainWindow, je crée alors un QThread puis utilise la méthode moveToThread pour executer le code de mon objet Worker dans un Thread séparé du Thread principal.
    En effet le contenu de ma méthode executer ne se résume pas au code ci-dessus mais contient d'autres étapes, c'est pourquoi je n'utilise pas directement les QProcess dans la classe MainWindow.

    Mon signal textOut est connecté à un slot de MainWindow, dans lequel j'ajoute le texte reçu en argument dans un QPlainTextEdit.
    ça fonctionne, mais:
    - je ne suis pas certaine de m'y prendre de la bonne manière, cette façon de coder est-elle correcte?
    - l'affichage se passe bien au début mais il est du type:
    "Execution de ProgExterne
    Démarrage...
    Ok
    Execution... 0%"

    Ensuite seul le "0%" bouge en fonction de l'avancement de l'application, sans créer de nouvelle ligne dans la console. Donc l'affichage s'arrête là dans ma MainWindow, le pourcentage ne se met pas à jour... Comment faire?

    Merci d'avance!

  2. #2
    Membre émérite
    Avatar de ymoreau
    Homme Profil pro
    Ingénieur étude et développement
    Inscrit en
    Septembre 2005
    Messages
    1 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur étude et développement
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 154
    Points : 2 834
    Points
    2 834
    Par défaut
    waitForReadyRead() blocks until new data is available for reading on the current read channel.
    Ce code devrait fonctionner, mais tu peux vérifier que le read channel du processus soit bien celui que tu veux lire QProcess::readChannel(). Selon si ton programme externe écrit sur la sortie standard ou d'erreur, j'imagine que c'est standard et j'imagine aussi que c'est la valeur par défaut de QProcess mais bon.

    Dans tous les cas il me semblerait plus simple et naturel d'utiliser le signal de la sortie standard QProcess::readyReadStandardOutput(), comme pas de question à se poser et pas besoin de faire toi même une boucle d'évènement (tu utilises celles de Qt). En créant simplement un slot qui lit la sortie standard et met à jour ton affichage. Par contre le code devient asynchrone, et tous les traitements que tu fais après l'exécution du processus devraient être déplacés dans un autre slot qui attend la fin du processus (avec le signal QProcess::finished).

    Si ça ne fonctionne toujours pas, essaye de cibler ce qu'il se passe en debug ou avec des traces. Est-ce que le programme externe s'exécute bien et écrit bien sa sortie, est-ce que tu reçois les signaux sur ton QProcess qu'il a bien écrit sa sortie (il pourrait y avoir des buffers à différents endroits qui ne sont pas publiés), est-ce que tu lis bien les bonnes données avant d'essayer de les afficher (ça pourrait être seulement la mise à jour de l'interface qui a un bug et pas la lecture du processus).

  3. #3
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut
    Merci pour la réponse ymoreau, mais je n'y arrive pas...
    J'ai fait ce que tu proposais, un connect() entre le signal readyReadStandardOutput et un slot qui lit la sortie puis l'affiche a l'IHM.
    J'ai aussi connecté le signal finished pour sortir de ma boucle while (dans cette boucle while d'attente du signal finished, il y a uniquement une verification periodique si l'utilisateur a demandé une interruption en cliquant sur un bouton).

    - Quand je lance le QProcess avec start(), aucune sortie ne s'affiche ni dans ma console QtCreator ni dans mon IHM. La boucle while se lance, si j'appuie sur mon bouton "interrompre" cela fonctionne (on sort de la boucle), mais jamais les signaux readyReadStandardOutput et finished ne sont émis (je ne sais même pas si mon process s'est bien lancé puisque aucun affichage).

    - Quand j'utilise startDetached, il se passe exactement la même chose sauf que cette fois je vois le déroulement du programme externe dans la console QtCreator. Mais les deux signaux que j'ai connecté ne sont jamais émis non plus.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    // Reimplementation methode run() de QThread
    MonThread::run()
    {
        demandeInterrupt =false;
        QProcess* progExterne = new QProcess();
        connect(progExterne, SIGNAL(readyReadStandardOutput()), this, SLOT(lireSortie());
        connect(progExterne, SIGNAL(finished(int)), this, SLOT(finProcess(int));
        progExterne->start("./progExterne.sh");
        while(!fini)
        {
            sleep(1);
            if(demandeInterrupt)
                fini = true;
        }
    }
     
    // Appele par le slot d'un bouton "Interrompre" de mon IHM principale
    MonThread::interrompre()
    {
        demandeInterrupt = true;
    }
     
    MonThread::lireSortie()
    {
        qDebug() << "lecture sortie";
        emit textOut(progExterne->readAllStandardOutput());
    }
     
    MonThread::finProcess(int exitCode)
    {
        qDebug() << "fin process";
        fini =true;
    }
    Au final, ce que je veux faire est simple: exécuter deux programmes externes successivement (le premier doit impérativement être terminé avant de lancer le second), tout en affichant la sortie console sur mon IHM. Comment procéderiez-vous? Y a t'il forcément besoin de créer un QThread?

  4. #4
    Membre émérite
    Avatar de ymoreau
    Homme Profil pro
    Ingénieur étude et développement
    Inscrit en
    Septembre 2005
    Messages
    1 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur étude et développement
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 154
    Points : 2 834
    Points
    2 834
    Par défaut
    Tout d'abord : tu as deux programmes à lancer à la suite, donc pas en parallèle = pas besoin de les mettre chacun dans un thread différent l'un de l'autre. Tu pourrais mettre dans un thread ta procédure qui appelle ces deux programmes si elle est longue et bloquante pour ton interface graphique (sinon l'interface est figée le temps que ta procédure s'exécute). Mais si jamais le plus long est l'appel des programmes externes et pas du traitement que tu fais toi, cette exécution là est déjà faite dans un autre processus donc pas bloquante pour toi sauf si tu bloques toi même ton code avec un while ou un wait.
    En résumé à moins que tu aies des gros traitements en dehors de tes programmes externes tu n'as pas besoin de thread, par contre tu as besoin de synchroniser les processus, ne lancer le second que quand le premier est terminé, pas avec un wait mais en asynchrone. C'est un autre choix que le wait, mais tu es libre de faire ce que tu préfères.

    En résumé l'organisation asynchrone donnerait :
    Etape 1 (une fonction)
    créer process1 et attacher ses signaux sortieStandard pour l'affichage1 et finished pour passer à l'étape 2
    lancer process1
    fin de la fonction, retour à la boucle d'évènements Qt

    Etape 2 (une autre fonction)
    créer process2 et attacher ses signaux sortieStandard pour l'affichage2 et finished pour passer à la fin
    lancer process2
    fin de la fonction

    Etape 3 (encore une autre fonction)
    Faire les traitements que tu veux après que tes deux programmes externes aient terminé


    Ensuite pour trouver pourquoi ça ne marche pas, tu peux peut être mettre un slot sur le signal void QProcess::error(QProcess::ProcessError error) qui affiche une éventuelle erreur du processus, juste pour avoir un log s'il y a un problème. Tu peux aussi faire la même chose sur le signal void QProcess::started() pour vérifier que le process a bien démarré.
    Sinon là comme ça je ne vois pas pourquoi tu ne reçois pas les signaux du QProcess, tu devrais voir passer des erreurs en console si la connexion des slots ne marchait pas.

  5. #5
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut
    En fait je me suis mal exprimée, j'ai bien créé un seul QThread pour mes deux QProcess. Mais je me posais la question si je ne pouvais pas directement créer les QProcess dans mon Thread principal (l'IHM), en me demandant comment faire pour éviter de bloquer l'IHM pendant leur exécution (qui est effectivement longue).
    D'après ta réponse ça à l'air possible, du coup merci je vais tester ça et je reviens faire un retour!

  6. #6
    Membre émérite
    Avatar de ymoreau
    Homme Profil pro
    Ingénieur étude et développement
    Inscrit en
    Septembre 2005
    Messages
    1 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur étude et développement
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 154
    Points : 2 834
    Points
    2 834
    Par défaut
    J'avais bien compris un seul thread pour les 2 QProcess mais c'était pour détailler les choix possibles, je parlais bien au final d'un seul thread pour ton appli donc comme tu dis le thread principal Qt.
    Et tu avais raison pour ne pas bloquer l'IHM il faut détacher les procédures longues dans un autre thread que celui de l'IHM, c'est simplement que dans ton cas la procédure est déjà détachée dans un autre processus donc ce n'est plus la peine (encore une fois, sauf si tu as aussi des traitements longs dans ton code en plus des QProcess).

  7. #7
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut
    Ah ok! ^^
    Non je n'ai pas d'autres traitements à faire, juste afficher la sortie console sur mon IHM (et quand les 2 process sont finis je n'ai rien d'autre à faire non plus).
    J'ai donc codé la méthode que tu proposes:

    Code mainwindow.h : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <QMainWindow>
    #include <QFileDialog>
    #include <QtCore>
    #include <QtGui>
     
    namespace Ui {
    class MainWindow;
    }
     
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
     
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
     
    private slots:
        // ... les slots de mon IHM
        void on_pb_lancerProgrammes_clicked();   // SLOT d'un bouton "Lancer les programmes" de mon IHM
        // Slots des QProcess
        void executerProcess2(int statutProcess1);
        void process2Fini(int exitCode);
        void lireSortieProcess1();
        void lireSortieProcess2();
     
    private:
        // Attributs
        //...
        Ui::MainWindow *ui;            // Fenetre principale
        QProcess* process1;            // Process de lancement de mon programme externe 1
        QProcess* process2;            // Process de lancement de mon programme externe 2
        QString projectPath;            // Nom du repertoire ou se trouvent mes programmes externes a executer
     
        // Methodes
        //...
        void executerProcess1();
    };
     
    #endif // MAINWINDOW_H

    Code mainwindow.cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
     
    //...
     
    /**    Lancement des programmes externes
       *    SLOT appele lors d'un clic sur le bouton ui->pb_lancerProgrammes */
    void MainWindow::on_pb_lancerProgrammes_clicked()
    {
        // Cas ou l'execution est en cours: interruption
        if(ui->pb_lancerProgrammes->text().contains("Arreter"))
        {
            if(process1.state() == QProcess::Running)
                process1.kill();
            if(process2.state() == QProcess::Running)
                process2.kill();
            QMessageBox::information(this, "Termine", "Execution interrompue");
            return;
        }
         // Initialisation et execution des programmes
        ui->pb_lancerProgrammes->setText("Arreter l'execution");    // Le bouton "Lancer" devient "Arreter"
        ui->myPlainTextEdit->clear();                                              // Efface l'eventuel contenu de la "console" IHM
        projectPath = "/home/me/project1";                                     // Indication du repertoire projet a executer
        executerProcess1();                                                             // Lancement du process 1
    }
     
    /* Execution du process 1
    /* Appele apres un clic sur le bouton ui->pb_lancerProgrammes */
    void MainWindow::executerProcess1()
    {
        process1= new QProcess();
        // A chaque fois que des donnees sortent en console, lireSortieProcess1() sera appele
        connect(process1, SIGNAL(readyReadStandardOutput()), this, SLOT(lireSortieProcess1()));
        // Quand le processus sera fini, executerProcess2() sera appele
        connect(process1, SIGNAL(finished(int)), this, SLOT(executerProcess2(int)));
        // Demarrage de process1
        process1->start(projectPath + "/process1.sh");
    }
     
    /* Execution du process 2
    /* Appele apres l'emission du signal finished() de process1 */
    void MainWindow::executerProcess2(int statutProcess1)
    {
        if(statutProcess1 != 0)
            QMessageBox::error(this, "Erreur", "Process1 s'est termine avec erreur - code " + statutProcess1);
        else
        {
            process2 = new QProcess();
            process2 ->setWorkingDirectory(projectPath);
            connect(process2 , SIGNAL(readyReadStandardOutput()), this, SLOT(lireSortieProcess2()));
            connect(process2 , SIGNAL(finished(int)), this, SLOT(process2Fini(int)));
            process2->start("./process2.sh");
        }
    }
     
    /* Signale la fin de l'execution
    /* Appele apres l'emission du signal finished() de process2 */
    void MainWindow::process2Fini(int exitCode)
    {
        QMessageBox::information(this, "Termine", "Process2 s'est termine avec le code " + exitCode);
        ui->pb_lancerProgrammes->setText("Lancer l'execution");    // Le bouton "Arreter" devient "Lancer"
    }
     
    /* Lecture de la sortie console du process 1
    /* Appele a chaque emission du signal readyReadStandardOutput() de process1 */
    void MainWindow::lireSortieProcess1()
    {
        QByteArray output = process1->readAllStandardOutput();
        ui->console->appendPlainText(output);
        ui->console->moveCursor(QTextCursor::End);
    }
     
     
    /* Lecture de la sortie console du process 2
    /* Appele a chaque emission du signal readyReadStandardOutput() de process2 */
    void MainWindow::lireSortieProcess2()
    {
        QByteArray output = process2->readAllStandardOutput();
        ui->console->appendPlainText(output);
        ui->console->moveCursor(QTextCursor::End);
    }

    ça marche impeccable! Merci beaucoup

  8. #8
    Membre émérite
    Avatar de ymoreau
    Homme Profil pro
    Ingénieur étude et développement
    Inscrit en
    Septembre 2005
    Messages
    1 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur étude et développement
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 154
    Points : 2 834
    Points
    2 834
    Par défaut
    Ah juste pour information je viens de réaliser pourquoi les signaux ne fonctionnaient pas dans ton code précédent, tout simplement parce que tu attendais avec un while jusqu'à interruption mais tu ne rendais jamais la main à la boucle d'évènement Qt (qui ne s'exécute que quand tu sors du code de ton slot).

  9. #9
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut
    Oui je m'en suis rendue compte aussi en codant la nouvelle solution, ça ne risquait pas de marcher...

  10. #10
    Membre averti
    Femme Profil pro
    Ingénieur informatique scientifique
    Inscrit en
    Mai 2010
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur informatique scientifique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2010
    Messages : 313
    Points : 301
    Points
    301
    Par défaut
    Au fait! Il y a une erreur dans mon code, dans le cas où l'utilisateur demande une interruption pendant que le process1 est en cours: en appelant le signal kill(), le process1 s'arrête bien mais envoie le signal finished() ce qui démarre le process2.
    J'ai donc ajouté un disconnect() pour supprimer la connection entre finished() et lancerProcess2, avant d'appeler process1.kill().

  11. #11
    Membre émérite
    Avatar de ymoreau
    Homme Profil pro
    Ingénieur étude et développement
    Inscrit en
    Septembre 2005
    Messages
    1 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur étude et développement
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 1 154
    Points : 2 834
    Points
    2 834
    Par défaut
    Merci du retour, une autre façon de faire aurait été de tester le statut de process1 pour savoir s'il s'est terminé normalement ou pas (crash, sortie en erreur, kill etc).

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. recuperer sortie console d'un QProcess ?
    Par divide dans le forum Qt
    Réponses: 2
    Dernier message: 11/08/2010, 13h52
  2. Date et sortie console énigmatique.
    Par boutade80 dans le forum Langage
    Réponses: 4
    Dernier message: 26/07/2006, 11h34
  3. Réponses: 9
    Dernier message: 21/06/2006, 16h41
  4. Réponses: 7
    Dernier message: 02/09/2005, 15h15
  5. Réponses: 5
    Dernier message: 18/08/2005, 22h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo