Comment implémenter une interface pour les plugins
Ah que j'ai cherché et étudié d'autres applications et exemples avant de pouvoir le faire. Mais une fois compris, c'est très simple en fait en C/C++. Le secret est dans l'interfaçage.
Qu'est-ce qu'une interface? En C++, nous pourrions dire qu'il s'agit d'une classe objet qui permet de définir un ensemble de fonctions virtuelles, ou abstraites. Ce que cela permet de faire? D'un côté, l'application qui veut communiquer avec un plugin n'a pas à connaître le code qui s'exécute de l'autre côté de l'interface. De l'autre, le plugin n'a pas à ce soucier de l'application dans son implémentation, en autant qu'il offre une implémentation des appels aux fonctions de l'application. Ensuite, par un mécanisme donné, il suffit de dire à l'application de chercher à un endroit donné pour trouver le plugin et de le mettre en mémoire lorsqu'elle en a besoin.
Allons un peu plus loin à l'aide d'un exemple :
l'application développée désire faire jouer un son. Elle est capable de lire un format audio-vidéo donnée, de décoder l'audio grâce à une librairie (l'implémentation pourrait être à l'aide d'un plugin, mais pas pour cet exemple) et il ne lui reste plus qu'à faire jouer les données audio décodées. De l'autre côté, il y a un backend pour Pulse, ALSA et OpenAL sur le système. Normalement, sans plugin, il faudrait que le support de chaque backend soit fait dans l'application. Si seul Pulse est implémenté, non seulement est-il est impossible d'ajouter un autre backend, mais en plus l'application oblige d'avoir le backend qu'elle a implémenté, à moins de pouvoir simplement laisser tomber l'audio lorsque le backend n'est pas disponible. Qu'arrive-t-il si un nouveau backend arrive et est plus performant, ou que le seul implémenté n'est plus supporté et ne se développe plus? L'application perd de son utilité.En somme, c'est comme si elle ne parlait qu'un langage et ne pouvait comprendre que ce langage en retour, et qu'il était impossible de trouver un traducteur lorsque le langage qu'elle parle ne correspond pas à celui à l'autre bout.
Par contre, si l'application communique avec les backends à l'aide de plugin à travers une interface, tout change. En effet, avec une interface (ou couche d'abstraction), l'application communique à tous les plugins de la même manière; de l'autre côté, le plugin joue le rôle d'interprête ou de traducteur. Ainsi, c'est comme si l'application était dans une chambre fermée avec une seule petite ouverture pour parler et qu'elle devait faire exécuter quelque chose à quelqu'un dans une autre pièce similaire et que dans le corridor entre les deux pièces se trouvait quelqu'un qui va d'une ouverture à l'autre pour passer les indications. L'application peut avoir un langage simple comme celui-ci :
Y a-t-il un interprête audio?
Interprête, dis à la carte que je désire lui parler.
Interprête, la carte est-elle prête à ce que je lui parle?
Interprête, traduis à la carte ce que je vais te dire.
Interprête, la carte a-t-elle compris mon message?
Interprête, dis à la carte que je la remercie et que je n'ai plus rien à lui dire.
Interprête, retire-toi.
Avec ces 7 commandes et mon interprête, même si la personne dans l'autre pièce ne m'a jamais vu, ni parlé et qu'elle parle une langue que je ne connais pas, je peux tout lui faire exécuter ce que je désire. Je peux donc parler français, mais dicter un message à quelqu'un qui parle espagnol, anglais, mandarin, arabe, etc. Et je peux avoir plusieurs traducteurs qui ne connaîtront que mon langage et celui à qui je m'adresse. Autrement, j'aurais à connaître chacune des langues moi-même.
Pour aller plus loin dans l'exemple, il serait aussi possible que la personne de l'autre côté ait à me poser des questions pour exécuter son travail. Dans ce cas mon interprête devrait aussi venir me poser des questions et retourner lui répondre. Vous voyez, il suffit ensuite d'attendre des réponses ou des questions tant que je n'ai pas terminé ce que j'ai à faire exécuter dans l'autre pièce. Je ne sais pas qui fait le travail, je ne sais pas comment, je ne sais pas si c'est la meilleure façon de le faire.
Bon, voilà pour ce qu'est l'interface. Mais comment savoir s'il y a un interprête de disponible? Il faut d'abord décider où on désire s'adresser (dans quel dossier). Ensuite, on peut questionner les fichiers pour trouver ceux qui conviennent. Nous pourrions modifier l'exemple comme ceci :
Je parle le français, un travailleur parle allemand, nous sommes les deux dans le même milieu de travail et on bosse sur un projet ensemble. Seulement, je n'arrive pas à me faire comprendre et lui non plus. Alors, sur le même lieu de travail, je fais le tour des bureaux et je demande si quelqu'un est interprête. Ah, j'en ai trouvé un. Parle-t-il allemand? Non, zut. Je cherche encore dans la boîte. Je finis par trouver quelqu'un et il vient travailler avec nous. Lorsque nous avons terminé, je remercie tout le monde et chacun retourne à ses petites affaires. Pour trouver mon interprête, je dois lui demander deux choses à la base :
Employé, es-tu un interpête?
Interprête, retire-toi.
Si on pousse l'exemple encore, lorsque je cherche à faire exécuter un travail, je dois savoir si quelqu'un peut diriger une équipe pour l'accomplir, je n'ai pas à connaître l'équipe elle-même. Alors si un employé me répond qu'il est interprête, je sais qu'il parle la même langue que moi. Il ne me reste plus qu'à lui dicter mon travail et attendre sa rétro-action. Je n'ai plus à me soucier s'il interprête une langue particulière, je sais qu'il me comprend et qu'il se fera comprendre à son tour.
Mais voilà, c'est chiant d'avoir à faire le tour de l'entreprise à chaque fois pour trouver la personne ressource. Je pourrais avoir un calpin pour noter les interprêtes et les appeler pour aider le moment venu. Ah, c'est bien comme idée... Attendez, quelqu'un dans l'entreprise pourrait connaître les fonctions des employés, s'il y en a de nouveaux, si certains ont quitté et je pourrais lui demander s'il possède une liste des interprêtes. AH! C'est encore mieux. Voilà, je viens d'introduire le gestionnaire de plugins. Son rôle est simplement de tracer les employés qui entrent et qui sortent et leur fonction dans l'entreprise. Lorsque j'ai besoin de faire exécuter un travail, je lui demande s'il possède les chefs d'équipe (ou les interprêtes). Si oui, je m'adresse au chef d'équipe et ce ne sera qu'avec lui que je communiquerai alors. L'important est que tous les chefs d'équipe d'un secteur parle mon langage et qu'il me comprenne.
Techniquement parlant, il suffit de créer une classe virtuelle (ou interface). Lorsque je compile l'application ou que je fais des appels à des fonctions de n'importe quel plugin, je sais que le plugin qui me répondra me comprendra.
Exemple
Application ---> IPlugin ---> Plugin
Application : je trouve la librairie du plugin, je l'instancie et je communique avec les fonctions de l'interface
IPlugin : spécifie ce que tous les plugins doivent implémenter, sans dire comment
virtual initialize() = 0;
virtual isReady() = 0;
virtual sendData() = 0;
virtual isWorking() = 0;
virtual free() = 0;
virtual ~IPlugin() = 0; (attention, c'est très important de mettre le destructeur virtuel à cause de l'héritage qui pourrait avoir lieu, comme dans l'exemple suivant.
Application ---> IPlugin ---> IVideoPlugin ---> VideoPlugin
|---> IAudioPlugin ---> AudioPlugin
Avec cette exemple, je m'assure que tous les plugins avec lesquels je vais communiquer me répondront au début lorsque je les questionnerai sur leur rôle de la même façon, mais qu'en fonction de leur utilité (Video ou Audio), je devrais ensuite utiliser un ensemble de fonctions particulières.
exemple :
IPlugin : appelle_plugin(), renvoie_plugin(), get_TypePlugin() {AUDIO, VIDEO}
IVideoPlugin : initializeVideo(), receiveVideoData(Video Data), showVideo()
IAudioPlugin : initializeAudio(), receiveAudioData(Audio Stream), getPlayedTime()
De l'autre côté, le plugin AudioPlugin doit simplement faire une liste de tâches qui lui est propre quand j'appelle une fonction.
Le pond entre tout ça? Les appels de fonction propres à chaque système de load librairies, recevoir les adresses en mémoire des fonctions et relâcher les librairies.
Écrit le: 11/08/2010 @ 13:49
C'est extrêmement bien expliqué.
Je ne suis pas certain d'avoir tout compris par exemple...