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 Quick Discussion :

Charger une interface Qt Quick depuis une application Windows non Qt


Sujet :

Qt Quick

  1. #1
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut Charger une interface Qt Quick depuis une application Windows non Qt
    Bonjour,

    Je suis complètement débutant sur QT, aussi je requiers vos avis concernant mon soucis et les interrogations qui en découlent.

    Je suis chargé de trouver de solutions pour mettre au goût du jour les éléments graphiques d'un environnement logiciel Windows de la boîte dans laquelle je bosse qui utilise toujours des éléments graphiques génériques WIN32 ou MFC. Ces éléments graphiques sont contenus dans des dll C WIN32 ou C++ MFC chargées dynamiquement par l'environnement comme plugins.

    J'ai donc pensé à intégrer QT pour faire cette mise à jour et bénéficier ainsi d'éléments graphiques avec un look plus moderne. De plus, la séparation C++/QML va complètement dans le sens de ce que l'on aimerait mettre en place.

    La contrainte, je ne peux pas toucher à l'application mère qui charge les plugins avec des LoadLibrary et appellent des point d'entrées définis (DllEntryStart, DllEntryEnd, DllFunc, etc ...).

    Peut on encapsuler complètement un environnement QT Quick dans une dll qui serait chargée dynamiquement de cette manière par un environnement C ou C++ non QT ?

    Je n'ai pas trouvé grand chose sur le sujet et ça ne m'a pas l'air si simple que ça. Vous avez des exemples, des pistes qui pourraient m'aider ?

    Merci d'avance pour vos réponses, je suis un peu bloqué pour le moment.

  2. #2
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    Si votre application mère ne fait que charger un point d'entrée, alors si vous développez directement votre application en considérant le main() comme étant votre point d'entrée de la dll, alors vous pouvez parfaitement intégrer votre environnement Qt et QtQuick dedans. Par exemple, le contenu de la dll pourrait être celui-ci (avec l'interface graphique instanciée dans main.qml et ses dépendances) :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    extern "C"
    {
        void myEntryPoint(int argc, char **argv)
        {
            QGuiApplication app(argc, argv); // Nécessaire à toute application Qt
     
            QQmlApplicationEngine engine;
            engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); // Chargement de l'interface graphique Qt Quick
     
            app.exec(); // Boucle d'événements de Qt ; vous en sortirez uniquement quand la fenêtre sera fermée
        }
    }

    Est-ce que ceci vous convient ou bien souhaitez-vous plus de précisions ?

    Bonne soirée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  3. #3
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Merci beaucoup pour votre réponse .

    Le problème, c'est que les arguments argc et argv correspondant à ceux du main ne sont pas passés aux points d'entrées des dlls "plugins". Il y a moyen de s'en passer ou des les récupérer autrement ?

    Je pourrais peut être les créer moi même en initialisant le premier argument avec le chemin de l'éxecutable. Pourquoi QT a t-il besoin des arguments de la ligne de commande ?

  4. #4
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    Oui, vous pouvez vous en passer :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int unused = 0;
    QGuiApplication app(unused, NULL);

    De ce que j'en sais, Qt (sans majuscule sur le t, par contre) s'en sert exclusivement pour pouvoir faire qApp->arguments() et récupérer une QStringList contenant les arguments sans avoir à passer argc et argv à droite à gauche. Par conséquent, si vous faites qApp->arguments(), vous obtiendrez avec le code ci-dessus une liste vide.

    Bonne journée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  5. #5
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Cf la doc :
    Warning: The data referred to by argc and argv must stay valid for the entire lifetime of the QCoreApplication object. In addition, argc must be greater than zero and argv must contain at least one valid character string.
    Donc :
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int argc = 1;
    char* argv[] = { "nom of application" };
    QGuiApplication app(argc, argv);

  6. #6
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    J'avais fait un test préalable qui fonctionnait, mais vu que la documentation précise cela, votre solution est plus appropriée.
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  7. #7
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Merci !

  8. #8
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Bonjour,

    Je reviens vers vous et vos précieux conseils sur le même sujet mais avec une autre problématique associée.
    A l'heure actuelle j'ai pu créer une dll simple avec le point d'entrée suivant:

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    extern "C"
    {
       void qtdll_New(char * qmlPath)
      { 
          QApplication app(argc, argv);
          QQmlApplicationEngine engine;
          engine.load(QUrl::fromLocalFile(qmlPath));
          pApp->exec();     
      }
    }

    Ce point d'entrée prend en argument un chemin vers un répertoire externe où sont stockés le fichier main.qml + les autres fichiers de composants QML constituant ma GUI courante. Et ça marche !

    Mon objectif ensuite serait de pouvoir:

    - soit multi-instancier cette même dll (dans des threads indépendants) en appelant à chaque fois le même point d'entrée qtdll_New avec un directory de QML différent afin de pouvoir lancer des interfaces QML differentes et indépendantes les unes des autres,
    - soit éventuellement créer et instancier des dll "Qt" distinctes pour gérer des GUI distinctes les unes des autres .

    Je rappelle que l'exe qui instancie ces dll comme des "plugins" indépendants est un exe non Qt développé en C et sur lequel je n'ai pas la main.

    Hors, mon problème avec l’implémentation actuelle c'est que je ne sais pas comment gérer plusieurs instances distinctes de dll Qt/QML.

    Déjà je ne peux pas avoir plusieurs
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
     QApplication app(argc, argv);
    . Si j'appelle plusieurs fois mon point d'entrée
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    void qtdll_New(char * qmlPath)
    dans des threads séparés, j'obtiens une exception ASSERT Failure in QCoreApplication: "There should be only one application object".

    Si j'essaye d’appeler une seule fois QApplication app(argc, argv); et qu'ensuite j'essaye de loader des interfaces QML dans des threads différents, j'obtiens:

    ASSERT Failure in QCoreApplication::sendEvent "Cannot send events to objects owned by a different thread ...".

    Comment faire ?

    Il y a un moyen de pouvoir instancier en parallèle deux (ou plus) dlls, chacune chargeant un environnement QML distinct ? Comment gère t-on dans ce cas des boucles d’événements indépendantes pour chaque environnement ?

    Merci d'avance pour votre aide, je suis un peu perdu dans ma découverte de Qt/QML.

  9. #9
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    Ce que vous voulez faire peut s'avérer erroné sur le papier, dans le sens où, pour les applications graphiques, le thread principal est en général requis pour la boucle événementielle (comprenant l'affichage de l'interface graphique). Ainsi, le multi-thread n'est pas viable (d'où l'erreur avec QCoreApplication::sendEvent). Pour ce qui est d'avoir plusieurs QApplication, Qt ne permet également pas ce genre de chose. Mais si je comprends bien, vous voulez juste charger plusieurs fenêtre QML, ce qui ne mérite aucunement de forker ou de threader tout ça. Je vous propose ceci (pas sûr que cela fonctionne, par contre) :

    Code C++ : 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
    extern "C"
    {
       void qtdll_New(char * qmlPath)
      {
          bool hasApp = qApp != NULL;
     
          if (!qApp)
              QApplication *app = new QApplication(argc, argv);
     
          QQmlApplicationEngine *engine = new QQmlApplicationEngine;
          engine->load(QUrl::fromLocalFile(qmlPath));
     
          if (!hasApp)
              qApp->exec();     
      }
    }

    Par contre, cela induit une à deux fuites de mémoires (le QApplication* et le QQmlApplicationEngine*). Il vous faudra les stocker quelque part ou les retourner de sorte de les supprimer au moment de la libération de la dll. Autre problème : la partie qApp->exec() devrait sans doute être déportée dans une autre dll, sinon la deuxième fenêtre ne s'ouvrira pas avant que la première ne soit fermée. On pourrait même voir ceci :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // DLL de lancement du contexte Qt, à lancer avant les autres DLL
     
    extern "C"
    {
       void qtdllcontextloader_New()
      {
          if (!qApp)
              QApplication *app = new QApplication(argc, argv);
      }
    }

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // DLL de chargement d'une interface graphique, à lancer x fois
     
    extern "C"
    {
       void qtdll_New(char * qmlPath)
      {
          QQmlApplicationEngine *engine = new QQmlApplicationEngine;
          engine->load(QUrl::fromLocalFile(qmlPath));
      }
    }

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // DLL de lancement de la boucle d'événements, à lancer après que les x DLL précédentes ont été lancées
     
    extern "C"
    {
       void qtdllcontextrunner_New()
      {
          if (qApp)
            qApp->exec();
      }
    }

    Il n'y a qu'une seule boucle d'événements dans le cas actuel qui gère les x fenêtres QML chargées. Attention à ce que toutes ces DLL soient fermés qu'une fois que la session Qt est terminée (fin de la troisième DLL, quand le exec() aura terminé) et à traiter les memory leaks (une instance de QApplication*, récupérable par qApp, et x fois un QQmlApplicationEngine*).

    Bonne journée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  10. #10
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Bonjour,

    Merci pour votre réponse. En fait, ce que je veux faire est tout simple (même si mes explications sont certainement un peu confuses ).

    Je travaille sur une plateforme logicielle développée en C sur windows et sur laquelle je n'ai pas la main. L'exécutable principal de cette plateforme peut charger des "plugins" qui sont développés sous la forme de dll windows avec des points d'entrées génériques en C. Ces plugins ont tous les même points d'entrée et constituent des packages indépendants les uns des autres, tous chargés dans des threads distincts.

    Chaque plugin peut disposer de sa propre interface graphique. Mon objectif serait de pouvoir implémenter l'interface graphique de chaque plugin en Qt/QML.

    Par exemple, je voudrait pouvoir charger en parallèle un plugin qui m'affiche une fenetre "Hello World" en QML et un autre plugin avec un formulaire indépendant en QML également. Soit deux dlls distinctes, chacune avec son interface graphique Qt. C'est possible ?

    Le fait de charger qu'un seul QApplication peut être traité facilement. Par contre pour la boucle d’événement je manque d'expérience en Qt pour savoir comment faire . Ne peut-on pas dépiler les événements de manière indépendantes pour chacune des fenêtres ? Exactement comme on peut le faire en WIN32 ou en MFC en implémentant une pompe à message dédiée au thread auquel appartient la fenêtre ?

    J'ai lu dans une réponse sur un forum que l'on pouvait demander explicitement de dépiler des messages à intervalle régulier avec processEvent. Il y a t-il un moyen de s'en sortir avec ça dans le cas qui m’intéresse ?

    Merci d'avance pour votre aide.

  11. #11
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    À la base, il faut considérer qApp->exec() comme étant l'équivalent de ceci :

    Tant qu'il y a des fenêtres affichées
        S'il y a des événements en attente
            Gestion des événements (appel à processEvents())
        Temporisation
    Le problème qui se pose est que Qt nécessite de n'avoir qu'une seule instance de QApplication et que son affichage nécessite de se faire sur le thread principal. Le framework étant événementiel, vos fenêtres ne seront affichées qu'une fois que qApp->exec() sera lancé. Étant donné que cet appel est bloquant (cela tournera tant que votre/vos fenêtres ne seront pas fermées), cela va probablement prendre la main sur votre application C et l'empêcher de lancer d'autres plugins.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    [Main thread]----->[Démarrage appli C]----->[Démarrage interface Qt]     [perte de la main]    |--------->[Démarrage autre interface]------>...
                                                                       |------>[Fin de son exéution]
    Je ne sais pas trop si le fait de faire un thread qui balance du QCoreApplication::processEvents() dans un while (1) pourrait solutionner cela. Dans ce cas, cela reviendrait plus ou moins à un qApp->exec() dans un autre thread, ce qui risque de ne pas fonctionner. L'alternative, ce serait de forker, après, mais je ne sais pas si vous pouvez faire cela ?

    Bonne journée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  12. #12
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Un grand merci Louis pour toutes vos explications.

    Il y a encore pas mal de chose que je n'arrive pas à cerner.

    Comment ca se passe pour une application Qt classique si l'on souhaite charger en dynamique à partir du C++ plusieurs fenêtre non modales créer via des fichier QML distinct? C'est possible ? Elles peuvent être indépendantes ?
    Dans un programme Qt multithread, un thread ne peut pas créer dynamiquement une MessageBox, une FileDialog, un formulaire par lui même ?

    Si le app.exec() est bloquant. Comment dois je faire si je dois charger après coup dynamiquement d'autres fenêtres à partir de fichier QML ?

    Imaginons que dans exe Qt classique, j'ai besoin de lancer plusieurs threads en parallèle pour effectuer des tâches de fond. Puis-je créer plusieurs fenêtres indépendantes qui affiche chacune les données en provenance des threads qui leur ont été préalablement assigné ? Comment puis-je lié les objet instancier dans ces threads avec leur fenêtre respective ?

    Désolé de vous ennuyer avec toutes ces questions autant je maîtrise ça sur le bout des doigts dans d'autres environnements, autant là avec Qt, je nage en plein brouillard sur les questions multithread et multi-fenêtrage ...

  13. #13
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonsoir,

    Il faut voir Qt comme étant strictement événementiel. C'est-à-dire que, une fois que le app.exec() est lancé (bloquant), la seule manière de reprendre la main dessus, via son thread, est de passer par des événements. Par exemple, si on connecte un bouton d'une fenêtre à un slot (une méthode) onButtonClicked(), seule une interaction de l'utilisateur (un clic) permettra au slot onButtonClicked() d'être appelé. C'est-à-dire que le app.exec() va parcourir les événements, va constater un événement de clic de l'utilisateur. Il va alors le dispatcher dans l'arborescence des widgets jusqu'à ce qu'un widget dise "OK, c'est pour moi" (dans le cas actuel, le bouton dira cela). Ce bouton étant connecté à la méthode, la méthode sera appelée. Exemple :

    Code C++ : 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
    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
     
        Fenetre1 fen1;
        fen1.show();
     
        return app.exec();
    }
     
    Fenetre1::Fenetre1()
    {
        QPushButton *btn = new QPushButton("Click me!", this);
        connect(btn, SIGNAL(clicked()), this, SLOT(onButtonClicked()));
    }
     
    void Fenetre1::onButtonClicked()
    {
        Fenetre2 *fen2 = new Fenetre2;
        fen2->show();
    }

    Le schéma grossier de l'exécution est le suivant :



    Avec "Clic utilisateur", l'événement qui est traité par app.exec() lors d'un clic utilisateur, "CloseEvent", l'événement lié à un clic de l'utilisateur sur la croix de la fenêtre 1 et "CloseEvent 2", l'événement lié à un clic de l'utilisateur sur la croix de la fenêtre 2. Comme vous pouvez le constater le main est bloqué par l'exécution du QApplication, qui gère et redirige l'intégralité des événements vers les fenêtres (c'est la raison pour laquelle on ne peut pas créer un widget depuis un autre thread que le main thread : le QApplication sur le main thread se charge de la boucle d'événements commune de toutes les fenêtres).

    Prenons maintenant votre exemple :

    Imaginons que dans exe Qt classique, j'ai besoin de lancer plusieurs threads en parallèle pour effectuer des tâches de fond. Puis-je créer plusieurs fenêtres indépendantes qui affiche chacune les données en provenance des threads qui leur ont été préalablement assigné ? Comment puis-je lié les objet instancier dans ces threads avec leur fenêtre respective ?
    On va dire que dans cet exécutable, il y aura une fenêtre travaillant avec son thread pour par exemple recevoir un flux vidéo : à chaque fois que le thread aura fini de récupérer une image, il voudra notifier la fenêtre pour que celle-ci puisse l'afficher. Le cas où il y aurait deux fenêtres avec deux threads est strictement identique, la procédure est toujours la même, à dupliquer. On a donc ceci, en terme de code :

    Code C++ : 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
    Thread1::run()
    {
        while (1)
        {
             // Récupération d'une partie de l'image
             // ...
             if (imageOk) // Booléen quelconque indiquant qu'une image est prête
             {
                 mutex.lock(); // Mutex pour écrire l'image dans une variable interne _image
                 _image = imageManaged; // Écriture de l'image dans la variable interne
                 mutex.unlock(); // Libération du mutex
                 emit imageAvailable(); // Émission d'un signal indiquant qu'il a une image prête (voir le connect() ci-dessous)
             }
        }
    }
     
    Fenetre1::Fenetre1()
    {
        // Le Qt::QueuedEvent est important, il signifie que le signal imageAvailable() sera géré en tant qu'événement et mis en attente dans la queue des events du QApplication :
        // cela signifie que le slot showImage() ne sera appelé que lors du tour suivant de traitement des événements de app.exec() : il sera donc appelé dans le main thread et non dans le thread 1
        connect(thread1, SIGNAL(imageAvailable()), this, SLOT(showImage()), Qt::QueuedEvent);
    }
     
    void Fenetre1::showImage()
    {
        thread1->getMutex()->lock(); // Lock du mutex au cas où le thread serait en train de fournir une nouvelle image
        QPixmap image = thread1->getImage(); // Récupération de l'image
        thread1->getMutex()->unlock(); // Unlock du mutex
        widgetAfficheur->setPixmap(image); // Affichage de l'image
    }

    Voici le schéma d'un cas de fin de traitement de la part du thread :



    Avec en rouge, ce qui est traité par le thread 1 et en vert, ce qui est traité par le main thread. Pour rajouter une fenêtre et un autre thread à cela, on duplique juste les opérations de thread 1 avec comme couleur le rouge remplacé par du orange, par exemple (c'est un autre thread), et on duplique Fenêtre 1 en Fenêtre 2, avec toujours du vert : les fenêtres sont toutes gérées par le main thread.
    Dans la partie "Tour de boucle de app.exec()", j'entends ce que j'avais donné dans mon message précédent :

    Tant qu'il y a des fenêtres affichées
        S'il y a des événements en attente // Dans le cas présent, oui, il y a le signal qui a été appelé en mode queued
            Gestion des événements (appel à processEvents()) // Mène à appeler le slot de 
            Fenetre1 et au tour de boucle suivant un PaintEvent qui gère l'affichage de Fenetre1, 
            vu que son affichage a changé
        Temporisation
    Maintenant, on peut répondre à la question de la modalité : comment une fenêtre modale peut-être en bloquer une autre ? QApplication va tout simplement cesser de délivrer les événements à la fenêtre bloquée tant qu'une autre fenêtre la bloquera (ce n'est cependant qu'une hypothèse, mais ça me paraît tout à fait plausible).

    Après, de ce que je comprends de votre problème, il faudrait que le main() puisse reprendre la main malgré le fait que le app.exec() tourne en boucle infinie (premier schéma), ce qui me parait difficile, à moins que votre plateforme C ne tourne sur un thread autre que le main thread, mais je n'y crois pas trop. Une solution pourrait être de lancer une application Qt séparée via votre dll, tout simplement (QProcess::startDetached()), plutôt que de garder le même processus ?

    Désolé pour les diagrammes de séquence un peu étranges (notamment sur les durées des traitements), je n'ai jamais pris suffisamment de temps pour apprendre à en faire de vraiment corrects.

    Bonne soirée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  14. #14
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Un énorme merci pour cette réponse hyper détaillée et pour le temps que vous avez dû y passer !

    Le fonctionnement du moteur Qt commence a devenir nettement plus clair moi grâce à vous. J'avoue que j'ai un peu de mal à m'y retrouver dans la doc du site officiel. En plus avec les versions qui se mélangent et les API qui évoluent, ça ne facilite pas la compréhension quand on est complètement novice et que l'on cherche des exemples.

    Avec vos éclaircissements je suis sûr que je vais finir par trouver une solution .

    Citation Envoyé par Amnell Voir le message
    Après, de ce que je comprends de votre problème, il faudrait que le main() puisse reprendre la main malgré le fait que le app.exec() tourne en boucle infinie (premier schéma), ce qui me parait difficile, à moins que votre plateforme C ne tourne sur un thread autre que le main thread, mais je n'y crois pas trop. Une solution pourrait être de lancer une application Qt séparée via votre dll, tout simplement (QProcess::startDetached()), plutôt que de garder le même processus ?
    Pour tout dire, cette plateforme logicielle en C est une sorte d'IDE qui permet de développer et d’exécuter des scripts (langage propriétaire). Plusieurs machines en réseau avec cette même plateforme peuvent se connecter entre elles et les scripts s'exécutent indifféremment en local ou en remote à travers le réseau sur d'autres machines pilotées à distance. Bref, il peut y avoir des centaines de scripts indépendants qui tournent en parallèles sur la même plateforme et tous ces scripts sont exécutés par la plateforme dans un ou plusieurs threads dédiés (mais pas par le thread main de l'exe principal). Cela sert dans un milieu industriel pour piloter des équipements, lancer des bancs de tests, effectuer des contrôles, remonter des statuts, etc ...

    A l'heure actuelle, les objet graphiques disponibles dans ce langage script sont très restreints. Heureusement, il est très simple de développer et d'ajouter de nouvelles classes pour ce langage script. Il suffit de développer une sorte de plugin dll en C avec des points d'entrées formalisés et de charger ce plugin dans l'environnement. Ensuite n'importe quel script est ensuite capable d'utiliser cette (ces) nouvelle(s) classe(s) et d'instancier les objets correspondants "monNouvelObjet = MaNouvelleClasse(); ...". Aujourd'hui, nous avons des classes WIN32 génériques qui proposent des fenêtres par défaut utilisables en script ou des classes avec des ressources fenêtre customisées en MFC pour des besoins graphiques plus particuliers.

    J'aimerais pouvoir réaliser avec Qt une classe d'affichage "générique" (avec certaines limitations). Dans l'idée, les scripts pourraient instancier un nouvel objet "QtGui" en passant dans son constructeur un chemin vers un fichier qml. Cet objet QtGui offrirait des méthodes génériques permettant de lire/mettre à jour des champs graphiques de l'interface QML en les nommant (sans connaître à l'avance la structure du QML) et permettrait également de réagir aux actions utilisateur simples sur la GUI (une fonction clic avec un nom suffit). Ceci afin d'offrir des interfaces graphique avec un look beaucoup plus moderne, et surtout qui pourraient être customisées ensuite par des designers indépendamment de tout développement C++.

    Ma problématique est là. N'importe quel script devrait pouvoir instancier son/ses propres objets QtGui en lui/leurs fournissant un chemin vers un ou plusieurs fichiers QML pour ses propres besoins, indépendamment des besoins d'affichage des autres scripts. Il pourrait donc y avoir plusieurs objet QtGui qui tournent en parallèle dans la même plateforme, chacun interfaçant ses propres QML.

    Voilà pour l'explication en détail.

    Votre solution de lancer des application Qt dans des process séparé (QProcess::startDetached()) me semble particulièrement intéressante. Je n'ai pas encore regarder en détail mais peut-on facilement faire discuter de process entre eux par ce biais ?
    Je vais essayer de me plonger dans la doc sur ce sujet et essayer de trouver des exemples.

    Merci beaucoup en tous cas !

    Très bonne journée à vous.


    [EDIT]
    Grâce à vos explications, je viens de trouver un début de semblant de solution à mon problème !Je créé une classe globale à ma dll, instanciée par le thread de QApplication, et qui va me servir de "serveur" de fenêtres.

    MaClasseServeur.h:

    Code C++ : 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
    class MaClasseServeur : public QObject
    {
        Q_OBJECT
    public:
        explicit MaClasseServeur(QObject *parent = 0);
        void setQmlPath(QString qmlPath);
     
    signals:
        void qmlPathSet();
     
    public slots:
        void loadQML();
     
    private:
        QString __qmlPath;
    };

    MaClasseServeur.cpp:

    Code C++ : 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
    MaClasseServeur::MaClasseServeur(QObject *parent) :
        QObject(parent)
    {    
        connect(this, SIGNAL(qmlPathSet()), this, SLOT(loadQML()), Qt::QueuedConnection);
    }
     
    void MaClasseServeur::loadQML()
    {
        QQmlApplicationEngine *engine = new QQmlApplicationEngine;
        engine->load(QUrl::fromLocalFile(__qmlPath));
    }
     
    void MaClasseServeur::setQmlPath(QString qmlPath)
    {
        __qmlPath = qmlPath;
        emit qmlPathSet();
    }

    Le cpp principal de ma dll avec deux points d'entrée et un pointeur sur MaClasseServeur en global:

    Code C++ : 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
    //Global
    MaClasseServeur * maClasseServeur;
     
    void qtdll_Start()
    {
       int argc;
       char ** argv;
       maClasseServeur = new MaClasseServeur;
       cmdline_to_argv(GetCommandLineA(), &argv, &argc);
       QApplication app(argc, argv);
       app.exec();
    }
     
    int qtdll_New(char * qmlPath)
    {       
        maClasseServeur->setQmlPath(qmlPath);
        return 0;
    }

    Le point d'entrée qtdll_Start est appelé une seule fois au premier chargement de la dll dans un thread dédié.
    Dans des threads différents, je peux ensuite appeler indépendamment des qtdll_New avec en argument des chemins vers des fichiers QML distinct. Le maClasseServeur->setQmlPath émet à chaque appel un signal en mode queuedConnection à ma classe qui charge ensuite les QML dans la boucle d'évènement de QApplication. Et ça marche !!. Je viens de créer trois threads distincts qui appellent (séquentiellement) le point d'entrée qtdll_New de ma dll avec leur propre fichier QML. Résultat, j'ai bien trois fenêtres d'affichée à l'écran, toutes réactives et fonctionnelles !
    Il me reste à protéger les accès par mutex, à gérer des contextes pour retrouver mes billes, à essayer d'interfacer le QML correctement ... Bref, beaucoup de boulot mais c'est déjà un début .
    Si la solution que je viens d'expérimenter ne vous semble pas adéquate, n'hésitez pas à me le faire savoir .

  15. #15
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    EDIT en cours d'écriture : vous m'avez pris de vitesse avec une méthode alternative tout à fait adaptée (attention cependant à l'aspect thread-safe : qtdll_New pourrait être appelé avant que l'objet serveur soit entièrement initialisé), mais je vous propose quand même une solution qui fonctionne avec une unique dll (même principe, mais avec une technique légèrement différente : un évent personnalisé envoyé au lieu d'un slot passé en event via Qt::QueuedEvent, ce qui revient à peu près au même).

    Le fait que les dll soient lancés depuis d'autres threads va énormément faciliter la tâche. J'imagine qu'il y a derrière ce mécanismes d'exécution dans un/plusieurs thread des points d'entrée une sorte de thread pool, qui mène à ne pas être gênant le fait de bloquer un thread appelant pour n fenêtres QML lancées ?

    Voici donc ce que je vous propose. Il va exister deux cas de figures : le cas où un QApplication est déjà présent et un cas où il n'y en a pas encore.

    Si aucun QApplication n'est présent
        Création du QApplication
        Création de la fenêtre avec le QML
        exec() du QApplication
    Sinon
        postEvent() vers le QApplication avec le chemin du QML
    
    OK, que veut dire et que sous-entend ce charabia ? Le cas où le QApplication n'existe pas a déjà été traité plus tôt dans les anciens posts :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    extern "C"
    {
        void qtdll_New(char * qmlPath)
        {
            if (!qApp)
            {
                QApplication app(argc, argv);
     
                QQmlApplicationEngine engine;
                engine.load(QUrl::fromLocalFile(qmlPath));
     
                app.exec();
            }
        }
    }

    Pas de fuite de mémoire cette fois-ci, pas de nouveauté, cela va bloquer le thread appelant mais si c'est dans un thread externe au main thread de l'application C, ce n'est a priori pas gênant. Ce code va cependant recevoir une petite modification qui va changer avec la deuxième étape. Dans celle-ci, je parle de "postEvent()". Vu que app.exec() est bloquant, il va falloir se débrouiller pour que tout de même interagir avec. Vu qu'il passe sont temps à traiter des events, pourquoi ne pas tout simplement lui envoyer un event pour lui demander de lancer notre nouvelle fenêtre QML ? Là intervient la notion d'event personnalisé : on va devoir créer une classe par exemple nommée LaunchEvent, par exemple, héritant de QEvent :

    Code C++ : 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
    #include <QEvent>
    #include <QString>
     
    class LaunchEvent : public QEvent
    {
    public:
        // Permettra d'identifier l'event par la suite (doit être compris entre QEvent::User et QEvent::MaxUser, c'est-à-dire entre 1000 et 65535)
        static const QEvent::Type eventType = static_cast<QEvent::Type>(3333);
     
        LaunchEvent(const QString &path) // Constructeur de l'évent
            : QEvent(eventType), // On passe l'eventType au constructeur de QEvent
              _path(path) // On y intègre également le chemin du QML
        {
        }
     
        const QString &getPath() const // Accesseur du chemin QML
        {
            return _path;
        }
     
    private:
        QString _path;
    };

    Il faut maintenant pouvoir recevoir cet event. Pour cela, on va carrément créer une classe LaunchApplication (par exemple, je ne suis pas over-inspiré pour les noms, là) et réimplémenter sa méthode event() pour traiter les LaunchEvents reçus en plus des autres events classiques :

    Code C++ : 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
    class LaunchApplication : public QApplication
    {
    public:
        LaunchApplication(int argc, char **argv)
            : QApplication(argc, argv)
        {
        }
     
    protected:
        bool event(QEvent *e)
        {
            if (e && e->type() == LaunchEvent::eventType) // C'est notre event
            {
                LaunchEvent *launchEvent = dynamic_cast<LaunchEvent *>(e); // On récupère notre event
     
                // Je préfère toujours faire ce genre de vérification,
                // au cas où un autre type d'évent custom aurait pris le type 3333,
                // même si cela paraît hautement improbable
                if (launchEvent)
                {
                    QQmlApplicationEngine *engine = new QQmlApplicationEngine; // Pour la fuite de mémoire, on pourrait stocker l'instance dans la classe pour libérer tout ça dans le destructeur
                    engine->load(QUrl::fromLocalFile(launchEvent->getPath()));
                    return true; // True car l'évent a été traité
                }
            }
     
            return QApplication::event(e);
        }
    };

    On peut alors changer le premier code en y intégrant la fin :

    Code C++ : 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
    extern "C"
    {
        void qtdll_New(char * qmlPath)
        {
            if (!qApp)
            {
                LaunchApplication app(argc, argv);
     
                QQmlApplicationEngine engine;
                engine.load(QUrl::fromLocalFile(qmlPath));
     
                app.exec();
            }
            else
                QCoreApplication::postEvent(qApp, new LaunchEvent(qmlPath)); // Aucune fuite de mémoire, Qt gère la mémoire de l'event
        }
    }

    En théorie, c'est thread-safe, vu que qApp sera directement remplacé de NULL à &app à la création du LaunchApplication, mais je ne suis pas 100% sûr pour ce point. Il me semble qu'il va falloir mutexer autour du if (!qApp) jusqu'à LaunchApplication app(argc, argv); compris pour couvrir le cas où deux threads passeraient en même temps le premier if. J'ai fait un test avec ceci, et c'est fonctionnel, donc j'imagine que ça marchera aussi avec des threads :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(int argc, char *argv[])
    {
        LaunchApplication a(argc, argv);
        QCoreApplication::postEvent(&a, new LaunchEvent("test/main.qml"));
        return a.exec();
    }

    Bonne journée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  16. #16
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Bonjour !

    Citation Envoyé par Amnell Voir le message
    J'imagine qu'il y a derrière ce mécanismes d'exécution dans un/plusieurs thread des points d'entrée une sorte de thread pool, qui mène à ne pas être gênant le fait de bloquer un thread appelant pour n fenêtres QML lancées ?
    En fait si, c'est un peu gênant de bloquer le thread de qtdll_New étant donné que l'appelant attend un retour à ce niveau. Par contre je suis complètement d'accord qu'il faut implémenter un mécanisme pour tester systématiquement si QApplication est présent et le récréer si ce n'est pas le cas.
    Car effectivement, dans l'exemple que j'ai présenté dans ma réponse précédente, autant je peux créer autant de fenêtres que je veux à la suite, autant si je créé une fenêtre et que je la ferme (ce qui provoque la retour du app.exec() et la terminaison de QApplication), je ne peux plus en créer d'autres supplémentaires ...

    La solution serait certainement de créer systématiquement un thread dédié à QApplication s'il n'existe pas. Comme je suppose (mais je n'ai pas essayé) qu'on ne peut pas créer de QThread tant que QApplication n'a pas été instancié, il faudra que je créé un thread classique via l'API windows standard.

    Merci pour l'astuce du QCoreApplication::postEvent ! Je ne savais pas que l'on pouvait envoyer directement des événements de cette manière dans la boucle d’événement principale. Effectivement, cela facilite bien les choses et cela m'ouvre un tas de perspectives. Je vais tester tout ça.

    Merci beaucoup et bonne journée .


    [EDIT]
    J'ai essayé d'implémenter votre exemple avec la classe LaunchApplication mais j'ai un phénomène bizarre.

    si j'exécute dans un thread:

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void qtdll_New(char * qmlPath)
        {
            if (!qApp)
            {
                LaunchApplication app(argc, argv);
     
                QQmlApplicationEngine engine;
                engine.load(QUrl::fromLocalFile(qmlPath));
     
                app.exec();
            }
            else
                QCoreApplication::postEvent(qApp, new LaunchEvent(qmlPath)); // Aucune fuite de mémoire, Qt gère la mémoire de l'event
        }

    Ca crash sur le engine.load(QUrl::fromLocalFile(qmlPath)); :

    Exception at 0x7740c42d, code: 0xe06d7363: C++ exception, flags=0x1 (execution cannot be continued) (first chance) at c:\work\build\qt5_workdir\w\s\qtbase\src\corelib\global\qglobal.cpp:2108

    Exception at 0x7740c42d, code: 0xe06d7363: C++ exception, flags=0x1 (execution cannot be continued) at c:\work\build\qt5_workdir\w\s\qtbase\src\corelib\global\qglobal.cpp:2108
    Si je remplace LaunchApplication app(argc, argv); par QApplication app(argc, argv);, aucun problème ...

    Des idées d'où ça pourrait venir ?

  17. #17
    Rédacteur
    Avatar de Amnell
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2009
    Messages
    1 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 840
    Points : 5 545
    Points
    5 545
    Par défaut
    Bonjour,

    Une erreur de ma part, j'ai oublié que le argc en argument du constructeur doit être un int& et non un int, ce qui fait que l'int reçu en paramètre est détruit et que le QApplication garde une référence invalide derrière :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    public:
        LaunchApplication(int argc, char **argv)

    À changer en :

    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    public:
        LaunchApplication(int &argc, char **argv)

    J'ai fait des tests de mon côté également avec un thread contenant le QApplication et cela fonctionne bien. Par contre, quand la fenêtre se ferme et que le QApplication est détruit, un nouveau lancement d'une instance (avec la recréation du QApplication) va mener Qt à lancer son warning comme quoi on n'a pas créé le QApplication depuis le main thread(). Il faudrait ainsi se débrouiller pour garder le thread du QApplication sous la main avec une waitcondition ou ce genre de chose pour le réutiliser par la suite au lieu de le détruire. C'est sans doute un peu plus complexe, mais c'est une manière bien plus propre et transparente que de faire tourner le QApplication jusqu'à la fin des temps (ou jusqu'à un Qt.quit() QML/QCoreApplication::exit(0) C++) via setQuitOnLastWindowClosed(false) sur le qApp.

    Bonne journée,
    Louis
    N'oubliez pas de consulter la FAQ Qt ainsi que les cours et tutoriels C++/Qt !

    Dernier article : Débuter avec les Enlightenment Foundation Libraries (EFL)
    Dernières traductions : Introduction à Qt Quick - Applications modernes avec Qt et QML
    Vous cherchez un livre sur Qt 5, Qt Quick et QML ? Créer des applications avec Qt 5 - Les essentiels

  18. #18
    Futur Membre du Club
    Inscrit en
    Août 2008
    Messages
    11
    Détails du profil
    Informations forums :
    Inscription : Août 2008
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Ca marche ! Merci beaucoup

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

Discussions similaires

  1. [XL-2010] sélection des données depuis une base de données depuis une autre feuille
    Par Learning everyday dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 05/11/2014, 10h14
  2. Appeler une interface 2 à partir d'une interface 1
    Par cedric_kayo dans le forum Interfaces Graphiques
    Réponses: 3
    Dernier message: 13/10/2014, 09h35
  3. Réponses: 6
    Dernier message: 31/10/2012, 16h01
  4. Réponses: 14
    Dernier message: 23/04/2012, 22h32
  5. Réponses: 36
    Dernier message: 28/09/2005, 12h30

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