Qt - Création de plugins

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Ce tutoriel a pour but de vous faire découvrir les plugins avec Qt.

Il demande quelques connaissances en POO, dont l'abstraction de classe et montre un petit bout de code utilisant les templates.
Si vous ne connaissez pas cela, ou si vous n'y êtes pas habitués, vous pouvez lire le tutoriel de Nanoc qui explique bien quelques principes avancés de la POO (en C++).

Dans un premier temps, j'introduirai le principe des plugins, puis dans un second temps, vous aurez droit à une grande partie théorique. Pour finir et pour s'entraîner un petit peu, vous disposerez un mini TP sur la création d'un programme utilisant des plugins.

IntroductionDéfinition

En informatique, un plugin est un moyen de rajouter de nouvelles fonctionnalités à un logiciel de base. C'est pour cela qu'on appelle aussi les plugins « modules » ou « greffons ».
Ces modules sont en général utilisés pour qu'un programme puisse évoluer facilement. De plus, d'autres personnes voulant aider le logiciel peuvent à leur tour en créer pour ajouter de nouvelles fonctionnalités. L'un des cas les plus connus reste le célèbre navigateur Internet Mozilla Firefox.

Où et pourquoi ?

On peut rendre un programme totalement modulaire mais il ne faut pas perdre de vue que dans certains cas, cela peut se révéler complètement inutile. En effet, nous verrons par la suite qu'un plugin doit avoir une sorte de définition. Plus on veut rendre une partie de logiciel modulaire et plus la définition doit être floue, abstraite. Mais si elle l'était trop, cette partie n'aurait plus de but et ne servirait donc plus.

Il faut alors un but minimum pour qu'un système de plugins soit rentable. On peut pourtant en mettre dans beaucoup de programmes et votre seule limite est votre imagination. En effet, l'utilisation des plugins peut aller de la simple boîte de dialogue à un système complet utilisant lui aussi des plugins.

Théorie

Pour créer un plugin, il suffit de créer une définition puis de l'implémenter. Cependant, un plugin seul n'est pas très utile. :D
Il faut donc aussi modifier la base du programme en lui donnant un moyen de charger les plugins.

Créer une définition

Comme dit plus haut, un plugin doit avoir un but minimal et une sorte de définition.
La définition est la classe qui lie le projet mère à ses plugins. En effet, pour pouvoir concevoir un plugin, il y aura besoin de cette définition. Et pour que le projet utilise les plugins, il a besoin de savoir comment ils fonctionnent.
La définition est donc dans les deux projets (projet mère et projet de chaque plugin).

Cette définition est une simple interface, c'est-à-dire une classe abstraite contenant une liste de fonctions que tous les plugins du même type auront.

Par exemple la classe Animal qui permettra de créer des plugins de type Animal aura comme méthodes :

  • void manger(const Nourriture &repas) ;

  • void bouger(const Position &destination) ;

  • void attaquer(const Cible &cible) ;

  • ...

Plus le but est abstrait et moins la définition contient de méthodes, car plus de choses devraient « coller » avec elle.

Vous avez peut-être remarqué que cela ressemble à l'utilisation de la POO classique. En effet, pour concevoir plusieurs objets qui se ressemblent, on crée des classes de base abstraites, puis on les spécialise. Pour créer une interface, il faut que toutes les méthodes soient virtuelles pures.

Pour ceux qui ne savent pas comment les définir, voici un exemple :

class MaClass { public: virtual ~MaClass() {} virtual void maFonction() = 0; // On met = 0 pour dire qu'elle est virtuelle pure. virtual void maFonctConst() const = 0; // On met le =0 après le const };

On utilise le mot-clé virtual pour dire que la fonction est virtuelle. Ensuite, pour dire qu'on ne peut pas la définir, c'est-à-dire qu'elle est pure, on met = 0 à la fin de la déclaration. Cela rend notre classe abstraite car une méthode (ou plus) n'est pas définie.

Vous pouvez apercevoir que l'on ne rend pas le destructeur virtuel pur. En effet, il n'est pas possible pour la classe fille de choisir ce que fera le destructeur de la classe mère.

Cependant, comme vous l'avez remarqué, au tout début, j'ai utilisé le mot interface et non classe directement. En effet, Qt veut que nous déclarions explicitement une interface. Pour cela, on utilise la macro-définition Q_DECLARE_INTERFACE.

Elle prend en paramètre deux choses.
La première est le nom de la classe interface et la seconde est l'identifiant de l'interface sous forme de chaîne de caractères. Attention, celle-ci doit finir par le nom de la classe et être unique.
En général, cette macro-définition est située juste après la déclaration de la classe.

Si vous voulez créer une interface dans un namespace, il faut que la macro-définition soit appelée hors du namespace. Pour le nom de la classe, on utilise l'opérateur de portée « :: » (par exemple : « MonNamespace::MaClasse »).

Voici un exemple de deux interfaces :

#ifndef __INTER_H__ #define __INTER_H__ #include <QtPlugin> #include "EtreVivant.h" // C'est une classe comme les autres (qui sont abstraites). class Animal : public EtreVivant { public: virtual ~Inter1() { } virtual void mange(const EtreVivant &proie) = 0; virtual void attaquer(const EtreVivant &victime) = 0; // On peut mettre des variables. protected: int m_Faim; int m_Vie; }; Q_DECLARE_INTERFACE(Animal, "Mon programme.Animal") // Voici un exemple avec un namespace. namespace Out { // Un namespace qui contient des classes de sorties pour le programme. Ce n'est qu'à titre d'exemple, bien sûr. class Console { // Ici on a l'interface dans le namespace. public: virtual Console() {} virtual print(const std::string &str) = 0; }; } Q_DECLARE_INTERFACE(Out::Console, "Mon programme.Console") // On doit utiliser l'opérateur de portée :: pour accéder à notre interface. #endif

Il ne faut pas mettre de point-virgule après la macro-définition Q_DECLARE_INTERFACE. Il en est de même pour toutes les macro-définitions que vous rencontrerez dans ce cours.

Vous connaissez maintenant presque tout sur la création d'interfaces avec Qt. Il ne reste plus que la création du plugin en lui-même, qui est très simple, et le chargeur.

Création du plugin

Un plugin est avant tout un objet qui respecte une définition. Donc pour cela, il suffit de le faire hériter de l'interface.
Puisqu'un plugin est aussi un objet Qt, alors il doit hériter de QObject (directement ou indirectement).

Voici donc pour l'instant votre définition du plugin :

class MyPlugin : public QObject, public MyInter { };

Le fait de ne pas dériver votre plugin de QObject avant toute autre classe entraînera forcément une erreur. Veillez donc à bien faire hériter votre interface dans un premier temps de QObject, puis de toute autre classe.

Il faut ensuite dire à Qt que cette classe n'est pas comme les autres. Tout d'abord, c'est un QObjet, donc on peut mettre la macro-définition Q_OBJECT. Puis il faut signaler à Qt que l'on utilise une ou des interfaces. Pour cela, on utilise la macro-définition Q_INTERFACES. Elle attend la liste des interfaces séparées par des espaces.

Notre prototype de la classe est donc pour l'instant :

class Chien : public QObject, public Animal { // Un chien est un animal, il dérive donc de l'interface Animal. Q_OBJECT Q_INTERFACES(Animal) // Si notre plugin dérivait de plusieurs interfaces, il aurait fallu donner la liste à la macro-définition avec des espaces entre chaque nom. // Comme cela : // Q_INTERFACES(MyInter1 MyInter2 ...) };

Pour pouvoir utiliser un plugin, il faut qu'il soit concret. En effet, un plugin est avant tout une classe. Or si une classe est abstraite, elle ne peut pas être instanciée.
Puisque notre plugin hérite d'une interface abstraite, il faudra donc définir toutes les méthodes virtuelles pures.

Si notre plugin a besoin de plus de fonctions, on peut en rajouter. Cependant, le programme de base ne pourra toucher qu'aux méthodes définies dans l'interface. En effet, il ne connaît que l'interface, il ne sait donc pas si un plugin a la fonction machin().

Dans l'implémentation, tout se passe comme d'habitude, mais il faut rajouter la macro-définition Q_EXPORT_PLUGIN2(nomPlugin, nomClass).
Le nom du plugin est celui que l'on définira dans le .pro du plugin. Le nom de la classe est celui de la classe d'entrée du plugin, donc celle qui hérite de l'interface. On peut donc mettre plusieurs classes dans un plugin.

Voici la macro-définition pour l'exemple :

Q_EXPORT_PLUGIN2(nom_plugin, MyPlugin) // Pas de point-virgule après la macro-définition.

Il faut maintenant changer le .pro du projet. En effet, notre « projet » ne contient pas de main, donc le compilateur ne trouvera pas de point d'entrée s'il en cherche. Il faut donc le signaler en lui disant que l'on veut faire une bibliothèque. Pour cela, on utilise la variable TEMPLATE du .pro comme ceci :

TEMPLATE = lib

Il existe différents types de bibliothèques : les dynamiques, les statiques et aussi les plugins. On utilise alors CONFIG pour montrer à Qt que c'est un plugin :

CONFIG += plugin

Si un programme est en débogage, alors les plugins doivent aussi être en mode débogage, de même pour le release. On peut donc dire le type de compilation en rajoutant « release » ou « debug » à CONFIG.

Pour finir, on définit le nom de la cible (c'est-à-dire le plugin) avec TARGET. C'est aussi le nom du plugin qu'on avait mis dans la macro-définition Q_EXPORT_PLUGIN2 :

TARGET = nom_plugin

On a donc maintenant un .pro qui permet de compiler notre plugin. Il ne reste plus que la partie sur la création d'un chargeur de plugins et la partie théorique est finie.

Pour qu'il n'y ait pas d'ambiguïtés, sachez qu'un plugin ne se compile pas avec le projet mère. Il utilise donc un fichier .pro différent. Mais il peut utiliser des fichiers du projet mère (l'interface, notamment). De même, un plugin a son propre fichier projet et ne le partage pas avec un autre plugin.

Faire un chargeur de plugins

Il est relativement simple de charger un plugin car Qt a une classe prête pour cela : QPluginLoader.
Pour en créer un, il suffit juste de donner le chemin vers le plugin.
Ensuite, on peut récupérer un pointeur vers le plugin avec la méthode QPluginLoader::instance(). Elle retourne un QObject*, donc il faudra la réinterpréter en MyPlugin*, par exemple.
Pour la réinterpréter, soit on utilise le cast C++ reinterprete_cast<T>(obj) , soit on utilise un cast défini par Qt : qobject_cast<T>(obj) .
Puisque l'on utilise Qt et que qobject_cast utilise reinterprete_cast avec un plus, le mieux est d'utiliser qobject_cast. Ce cast demande simplement le type de transformation comme argument template et prend aussi l'objet à réinterpréter.

Cela se résume par ce bout de code :

QPluginLoader loader("./cheminVersMonPlugin"); // On charge le plugin en lui donnant juste le chemin. if(QObject *plugin = loader.instance()) { // On prend l'instance de notre plugin sous forme de QObject. On vérifie en même temps s'il n'y a pas d'erreur. MyPlugin* myPlugin = qobject_cast<MyPlugin *>(plugin); // On réinterprète alors notre QObject en MyPlugin }

Ce code fonctionne et permet de charger un plugin, mais il est assez basique. Un système de plugin doit permettre à une application d'évoluer facilement, elle doit donc savoir si un plugin a été ajouté. Si on met tous les plugins du même type dans le même dossier, alors on peut utiliser QDir pour lire ce dossier et donc connaître tous les plugins.

Déjà, il faut créer un QDir en lui donnant le dossier du programme pour être sûr de connaître le bon chemin vers le plugin.
Justement, la fonction qApp->applicationDirPath() retourne le chemin vers l'application. :)

Ensuite, il faut déplacer le QDir dans le dossier du plugin avec la méthode « cd ». Si vous utilisez la console, vous verrez que l'utilisation de cette méthode est identique à la commande « cd ».

Voici pour l'instant le code :

QDir plugDir = QDir(qApp->applicationDirPath()); // On place le QDir dans le dossier de notre exécutable. plugDir.cd("./cheminSecretVersLeTresorDuTuto"); // Puis on le déplace dans le dossier des plugins. Je ne pense pas que le chemin de votre plugin soit celui-ci.

Il faut maintenant boucler avec la liste des fichiers se trouvant dans le répertoire. QDir::entryList() retourne la liste des fichiers avec comme arguments la define QDir::Files qui est un filtre : il ne prendra que les fichiers du dossier, les répertoires ne seront pas listés. Elle retourne un QStringList.
Puisqu'un QStringList est une List, il est alors possible d'utiliser un foreach. Un foreach est une structure de Qt qui permet de faire des actions pour chaque élément d'une liste. D'où son nom.
Cela revient au même qu'une boucle itérative, mais je trouve qu'elle prend moins de place.

Voici comment on utilise une classe foreach :

// T est un type défini. T unElement; QList<T> listElement; foreach(unElement, listElement) { // On prend chaque élément de listElement que l'on met l'un après l'autre dans la variable unElement. // Les actions. }

On se servira aussi de QDir::absoluteFilePath(QString) qui nous permet d'avoir le chemin absolu vers le fichier.

Voici donc notre chargeur de plugins :

QList<MyPlugin *> m_LsPlugin; // On crée une liste de MyPlugin* qui contiendra nos plugins. QDir plugDir = QDir(qApp->applicationDirPath()); // Comme avant, on crée un QDir. plugDir.cd("./Chemin"); // On se déplace encore. foreach(QString file, plugDir.entryList(QDir::Files)) { // Puis on utilise le foreach. QPluginLoader loader(plugDir.absoluteFilePath(file)); // On fait ensuite la même chose que pour un seul plugin. if(QObject *plugin = loader.instance()) MyPlugin* myPlugin = qobject_cast<MyPlugin *>(plugin); m_LsPlugin.push_back(myPlugin); // Vous pouvez maintenant les stocker ou directement les utiliser. } }

Vous avez enfin un chargeur de plugins. Vous pouvez soit utiliser une fonction soit une classe singleton (pas la peine d'avoir 50 instances) pour le chargeur.

Pratique : mini TP

Maintenant que vous savez comment faire pour réaliser des plugins avec Qt, je pense qu'il ne reste plus qu'à s'entraîner.
Cette dernière partie sera donc sous la forme d'un mini TP où le but sera de créer une application utilisant les plugins.

Pour ne pas rendre l'exemple trop compliqué, il sera inutile simple.
En effet, le sujet de ce cours n'est pas la création d'une grosse application. C'est pourquoi, vous aurez au maximum à créer une fenêtre.

J'aurais même pu vous faire faire un TP en console car Qt n'est pas...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Qt - Création de plugins

Prix sur demande