[TUTO] Application à base de plugin

Informations pour bien débuter en PureBasic
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

[TUTO] Application à base de plugin

Message par G-Rom »

Les plugins permettent à une application d'avoir une ou des extensions de ses fonctionnalités sans à avoir à recoder l'ensemble de l'application.
généralement , les plugins sont des librairie dynamique (.dll ou .so )

Avertissement : un certain niveau est requis pour maîtriser ce type d'application : pointeur / gestion manuel de la mémoire. le code est disponible en bas du topic.


Créer une application à base de plugin n'est pas chose simple au premier abord. il faut dans un premier temps avoir une application avec des fondations solide permettant l'ajout de fonctionnalités par la suite.
Le point clé est simple : rendre son application accessible depuis l’extérieur

Votre application dois être capable de générer des gadgets à la volée ( via des fonctions ) , de pouvoir interagir avec ces gadgets fabriqué à la volée , votre application dois être ouverte sur l'extérieur , de manière à pouvoir la manipuler via une dll.

Dans notre exemple , nous allons prendre une application de type "photoshop" , en plus simple , juste l'essentiel : les filtres sous forme de plugin.
les filtres permet de faire des effets sur l'image à éditer , comme du flou , inverser la couleur de l'image , etc...

notre application ressemble à ceci :
Image

Un canevas pour l'édition
Sur la droite des icones pour les plugins
un menu pour des fonction bateau (ouvrir/enregistré) & des infos sur les plugin
pas de pinceau & cie, juste l'essentiel...

notre application se décompose en trois partie distincte :
Image

- Le canvas principal
- La zone ou l'on affiche nos plugins via des boutons
- un menu qui contient que des infos sur les plugin

Votre application doit pouvoir communiquer avec les plugins , et vice versa

Image

Pour cela , il n'y a pas de standard , chaque application et ces plugins possèdent leurs propre protocole.
Pour nous, c'est simple:
  • - L'application se lance
    - Il charge toute les dll présente dans le dossier "plugins"
    - Il appelle une fonction d'initialisation

L'appel se fait via la commande CallCFunction() dont le prototype est le suivant :

Code : Tout sélectionner

Result = CallCFunction(#Bibliotheque, FunctionName$ [,Parameter1 [, Parameter2...]])
FunctionName$ est important pour tout les plugins , dans notre cas , ce sera "initializePlugin" , cette fonction sera présente dans tout les plugins, cela permettra à tout nos plugins de s'initialiser.

le prototype de la fonction initializePlugin() est la suivante :

Code : Tout sélectionner

initializePlugin(*aP.application)
*aP.application est un pointeur vers notre application , sa structure est la suivante :

Code : Tout sélectionner

Structure application
  
  winMain.i       ; identifiant de la fenêtre principale
  canvasID.i      ; id du canvas principal
  menuID.i        ; id du menu

  List toolBarButton.ToolBarGadget()
  *funcCreateToolBarButton.i  

  version_major.c ; version de l'application
  version_minor.c
 
  event.i         ; evenement de l'application
  ...
endstructure
On comprend donc l’intérêt immédiat de passer en paramètre un pointeur vers l'application à notre plugin.
le membre *funcCreateToolBarButton de la structure application est très important, il contient l'adresse de la fonction qui permet de faire des boutons à la volée.

Notre DLL ressemble donc à cela :

Code : Tout sélectionner

ProcedureDLL.i initializePlugin(*aP.application)
 ;*aP\funcCreateToolBarButton <- Adresse de la fonction de création de bouton à la volée
 ; ...
 ; ...
 ; ...
Endprocedure 
note: la dll & le programme principal ont donc la même structure en commun : "application" !

Notre dll peut donc appeler cette fonction qui est au coeur de l'application et créer un bouton à la volée pour le plugin.
Le problème est le suivant , créer un bouton à la volée c'est simple , mais comment le faire réagir avec le reste de l'application ?
En faisant un link ( un lien ) entre le bouton , et l'adresse d'une fonction ( fonction du plugin pour modifier l'image )

On va donc avoir besoin d'une liste chaînée structurée sous cette forme :

Code : Tout sélectionner

Structure ToolBarGadget ; Action sur les icones du haut
  gadgetID.i
  iconeID.i
  *linkFunction.i
EndStructure 
A chaque plugin chargé on vas incrémenté cette liste d'un nouvel élément. cette liste est présente dans la structure de base "application" vu plus haut
Le plugin va donc appeler la fonction de création de bouton à la volée par son adresse :

Code : Tout sélectionner

 CallFunctionFast(*aP\funcCreateToolBarButton, *aP, ?plugin_icon ,@pluginExecute() )
le programme va donc exécuter cette fonction dont le prototype est le suivant :

Code : Tout sélectionner

createToolBarButton(*aP.application, *iconePtr.i ,*linkFunction.i)
*aP.application , contient comme on la vu plus haut , toute les infos sur la fenêtre principale ( utile pour créer les gadget )
*iconePtr.i , est un pointeur sur l'icone , depuis la dll , on lui passe la datasection en paramètre via "?label..." , le plugin peut donc posséder ses propres image.
*linkFunction.i , adresse de la fonction même du plugin qui modifie l'image ( on verra cela plus tard )

Donc "createToolBarButton()" est une fonction interne au programme principal, mais le programme principal ne l’appellera jamais.
Le plugin que l'on charge connais l'adresse de cette fonction grâce à l'initialisation du plugin , c'est donc lui, le plugin qui appellera cette fonction afin de créer le bouton et de faire un lien bouton/fonction.

Le plugin ne peut pas modifier / créer de gadget sur le programme principal , la dll est le programme principal n'ont pas le même contexte , l'idéal aurait été de créer / modifier l'interface depuis la dll , mais c'est pas possible , sauf en appelant des fonctions interne au programme depuis la dll, c'est valable aussi pour les images...

Comme je le dis juste au dessus , la dll ne peut pas vraiment avoir d'interaction avec le programme principal , sauf si les fonctions dans le programme principal permettent d'en avoir.
le soucis , c'est que ce genre de fonction dans la dll est exclue :

Code : Tout sélectionner

ProcedureDLL applyFilter(imageID.i)
startDrawing(imageOutput(imageID))
...
stopdrawing()
EndProcedure
La seule solution est donc simple , convertir notre image purebasic à modifier en tableau de pixel ( sans dim() hein, mais à coup d'allocateMemory()... )
et aussi de pouvoir faire l'inverse , pour cela notre programme principal aura ces 2 fonctions :

Code : Tout sélectionner

Procedure.i imageToArray(pbImage.i, width.i, height.i)

  *nArray.c = AllocateMemory( (width * height) * 4 ) ; RGBA
  
  StartDrawing(ImageOutput(pbImage))
  
    DrawingMode(#PB_2DDrawing_AlphaBlend)

    For y = 0 To height -1
      For x = 0 To width -1
        color.l = Point(x,y)
        
        PokeC( (*nArray + (x*4) + ImageWidth(pbImage) * (y*4))+0 , Red(color) ) 
        PokeC( (*nArray + (x*4) + ImageWidth(pbImage) * (y*4))+1 , Green(color) ) 
        PokeC( (*nArray + (x*4) + ImageWidth(pbImage) * (y*4))+2 , Blue(color) ) 
        PokeC( (*nArray + (x*4) + ImageWidth(pbImage) * (y*4))+3 , Alpha(color) ) 
        
      Next 
    Next 
  StopDrawing()
  
  
  ProcedureReturn *nArray
EndProcedure

Code : Tout sélectionner

Procedure ArrayToImage(pbImage, *pixel_data.i, width.i, height.i)

  StartDrawing(ImageOutput(pbImage))
  
    DrawingMode(#PB_2DDrawing_AlphaChannel)
    Box(0,0,width, height,RGBA(0,0,0,0))
    
    DrawingMode(#PB_2DDrawing_AlphaBlend)
  
    For y = 0 To height -1
      For x = 0 To width -1
        
        red.c   = PeekC( (*pixel_data + (x*4) + width * (y*4))+0 )
        green.c = PeekC( (*pixel_data + (x*4) + width * (y*4))+1 )
        blue.c  = PeekC( (*pixel_data + (x*4) + width * (y*4))+2 )
        alpha.c = PeekC( (*pixel_data + (x*4) + width * (y*4))+3 )
        
        Plot(x,y,RGBA(red,green,blue,alpha))
  
      Next 
    Next 
  StopDrawing()
  
EndProcedure
Rien de bien compliqué , les fonctions parlent d'elles même.

Tip :
Pour naviguer en 2 dimension (x,y) dans une zone mémoire, la formule est la suivante :
Memoire + ( x * taille de la donnée) + taille dimension x * ( y * taille de la donnée)
ici la taille de la donnée est de 4 octets , 1 octet pour chaque couleur RGBA.
taille dimension x est la largeur de la dimension en x , si le tableau représente une image de 512x256, taille dimension x sera égal à 512
pour le rouge donc :
Memoire + ( x * taille de la donnée) + taille dimension x * ( y * taille de la donnée) + 0
pour l'alpha :
Memoire + ( x * taille de la donnée) + taille dimension x * ( y * taille de la donnée) + 3

+0 & +3 représente l'offset ( le décalage )

Memoire est retourné par allocateMemory()
x & y les coordonnée , attention a ne pas sortir , sinon plantage !
On passera donc un pointeur vers notre tableau de pixel à notre dll, et notre dll renvera la même chose , un tableau de pixel que le programme principal reconvertira en image purebasic.
Le prototype de la fonction de modification des pixels dans la dll est la suivante :

Code : Tout sélectionner

pluginExecute(*aP.application, *pixel_data.i, width.i, height.i)
en modifiant *pixel_data dans la dll , on modifiera *pixel_data dans l'application !
le code complet est à disposition ICI
je vous ai expliquer les fondements , à vous de décortiquer mon code , et l'adapter à vos besoins.


EDIT:

Le traitement d'image en temps réel mange pas mal de ressource CPU , l'idéal afin de ne pas figé le programme principal serait d’exécuter le plugin dans un thread séparé et d'ajouté au programme principal une barre de progression du plugin qui affiche l'avancement du plugin en %
si on modifie une zone de 1680x1050 , on à 1764000 pixel à modifié par passe.

Code : Tout sélectionner

Avancement = 0
iteration = 0
Max = width * Heigth 

For y = 0 to Heigth - 1
  For x = 0 to width - 1
   
   Avancement = iteration * 100 / Max  
   iteration + 1 

   ; modification pixel...
   ; envois de l'avancement à l'application principale
  next 
next 
EDIT 2:

Pour des applications plus conséquente , il faut une organisation plus propre que celle présenté ci-dessus.
Je vous ai parlé que l'application & le plugin doivent pouvoir communiquer.
Le plugin dois pouvoir changer le comportement de l'application principale via une API commune définie par vos soins.
L'application principale est structuré sous cette forme :

Code : Tout sélectionner

Structure Application
  ...
  ...
EndStructure
de cette manière, une simple variable contient toutes les informations sur l'application :

Code : Tout sélectionner

*monApplication.Application
L'API que l'on dois mettre en place est commune , le plugin fera donc appel à cet API pour pouvoir interagir avec le programme principal.
L'API est codé en dur au sein de l'application , le plugin fera donc appel aux fonctions qui se situent au sein du programme.

L'application de base aura dans sa structure ceci :

Code : Tout sélectionner

Structure Application
  Map api_export.i()
EndStructure
Cette map contiendra tout les noms des fonctions de l'api , les adresses qui vont avec.
de cette manière , lors de l'appel de initializePlugin(*aP.application) au sein du plugin , ce dernier , via le paramètre connaîtra l'adresse des fonctions.
du code est plus parlant :

L'API commun

Code : Tout sélectionner

Structure api_window
  windowID.i
  x.l
  y.l
  width.l
  height.l
  title.s
  flag.l
  parentID.l
EndStructure


; Ne sera compilé que sur la partie plugin
CompilerIf Defined(PLUGIN_EXPORT,#PB_Constant)
  
  Prototype API_OpenWindow(windowID.l, x.l, y.l, width.l, height.l, title.s, flag.l = #PB_Window_SystemMenu , parentID.l = #Null )
    Global API_OpenWindow.API_OpenWindow = #Null  ; Initialisé à zéro
  
  Prototype.i API_EventWindow()
    Global API_EventWindow.API_EventWindow = #Null 

CompilerEndIf
  
; Ne sera compilé que sur la partie application
CompilerIf Defined(APPLICATION_MAIN,#PB_Constant)

  
  Procedure API_OpenWindow(windowID.l, x.l, y.l, width.l, height.l, title.s, flag.l = #PB_Window_SystemMenu , parentID.l = #Null )
    ;...
  EndProcedure
  
  
  Procedure API_EventWindow()
    ;...
  EndProcedure
  
CompilerEndIf
notez l'utilisation des directives du compilateur , tout les plugins devrons déclarer #PLUGIN_EXPORT , l'application déclarera #APPLICATION_MAIN

Le plugin n'a que des prototypes vides , il connait les fonctions, mais ne sais pas ou elles se trouvent encore.

Coté application (code très réduis):

Code : Tout sélectionner

Structure Application
  Map api_export.i()
EndStructure

*APP.Application = AllocateMemory(SizeOf(Application))
 InitializeStructure(*APP,Application)
    
*APP\api_export("API_OpenWindow")  = @API_OpenWindow()
*APP\api_export("API_EventWindow") = @API_EventWindow()
Lorsque l'application chargera les dll , il fera appel à initializePlugin(*aP.application) qui se trouve dans la dll.

coté dll (code réduis aussi):

Code : Tout sélectionner

ProcedureDLL initializePlugin(*aP.application)
  API_OpenWindow  = *aP\api_export("API_OpenWindow")
  API_EventWindow = *aP\api_export("API_EventWindow")
EndProcedure
oublié pas d'inclure l'api commune vue plus haut

A partir de là , la dll est chargé ,connais les fonctions , les adresses sont bonnes , le plugin peut donc appeler ces fonctions qui sont au sein de l'application principale.
Dernière modification par G-Rom le ven. 20/juil./2012 13:35, modifié 2 fois.
Avatar de l’utilisateur
falsam
Messages : 7317
Inscription : dim. 22/août/2010 15:24
Localisation : IDF (Yvelines)
Contact :

Re: [TUTO] Application à base de plugin

Message par falsam »

Magnifique ce tuto, merci pour ce partage G-Rom.
Configuration : Windows 11 Famille 64-bit - PB 6.20 x64 - AMD Ryzen 7 - 16 GO RAM
Vidéo NVIDIA GeForce GTX 1650 Ti - Résolution 1920x1080 - Mise à l'échelle 125%
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Re: [TUTO] Application à base de plugin

Message par blendman »

Excellent tutoriel !
Bravo et encore un grand merci à toi :D
Avatar de l’utilisateur
Kwai chang caine
Messages : 6989
Inscription : sam. 23/sept./2006 18:32
Localisation : Isere

Re: [TUTO] Application à base de plugin

Message par Kwai chang caine »

Merci GRom pour ce TUTO. 8)
les plugins sont des librairie dynamique (.dll ou .so )
Dll ça me dit quelque chose... :D mais ".so" a part dans PHP, c'est quoi exactement ?? Une DLL comme les autres avec une autre extension? Ou bien tout autre chose ?? Quelle est la difference ??

Encore merci de ce partage :wink:
ImageLe bonheur est une route...
Pas une destination

PureBasic Forum Officiel - Site PureBasic
Avatar de l’utilisateur
wood51
Messages : 122
Inscription : ven. 05/juin/2009 13:04
Localisation : orléans

[TUTO] Application à base de plugin

Message par wood51 »

Salut , super ton tuto G-Rom , ça va décortiquer sévère !
KCC : ".so" c'est les dlls version Linux
Compétences : Bricoleur PureBasic du dimanche
Crâmage de cerveau en cours 100% :D
Projet en cours : http://purepicbasic.frenchboard.com/
Torp
Messages : 360
Inscription : lun. 22/nov./2004 13:05

Re: [TUTO] Application à base de plugin

Message par Torp »

Super clair,
Merci !
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: [TUTO] Application à base de plugin

Message par G-Rom »

Merci les gars.

@kcc

DLL : Dynamic Link Library, ou en Français Bibliothèque de liens dynamiques.
SO : Shared object, ou en Français Objet partagé.

en gros la même chose, l'un est utilisé pour windows , l'autre pour linux ;)
Avatar de l’utilisateur
Ar-S
Messages : 9539
Inscription : dim. 09/oct./2005 16:51
Contact :

Re: [TUTO] Application à base de plugin

Message par Ar-S »

Joli tuto, merci G-ROM
~~~~Règles du forum ~~~~
⋅.˳˳.⋅ॱ˙˙ॱ⋅.˳Ar-S ˳.⋅ॱ˙˙ॱ⋅.˳˳.⋅
W11x64 PB 6.x
Section HORS SUJET : ICI
LDV MULTIMEDIA : Dépannage informatique & mes Logiciels PB
UPLOAD D'IMAGES : Uploader des images de vos logiciels
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: [TUTO] Application à base de plugin

Message par lepiaf31 »

J'ai lu en diagonale mais il ne me semble pas que tu ais parlé de sécurité. Je m'explique, j'ai l'impression que les plugs-in n'ont aucune restriction (ils peuvent accéder à tout le disque, peuvent fouiller dans le registre, peuvent supprimer ce que bon leur semblent). Bref, du coup il serait judicieux (et intéressant) de trouver un moyen pour que l'application de base puisse 'manager' un peu tout cela et restreindre certaines actions. Je ne pense pas que ceci soit possible avec de simples dll mais bon je peux me tromper.
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: [TUTO] Application à base de plugin

Message par G-Rom »

Difficile en effet d'allier sécurité & fonctionnalité dans se domaine.
je ne vois pas comment une application pourrais restreindre une dll, techniquement c'est un défi.
"l'idéal" serait d'écrire une "surcouche" au compilateur pour la compilation de plugin et de restreindre certaine fonction ( reseau / registre / suppression de fichier ) , mais là on s'embarque dans autre chose.

on pourrais aussi utilisé un interpréteur qui interprète les plugins , qui eux seront sous forme de bytecode , les perfs seront semblable à du java. en cran en dessous donc , pour le traitement d'image , pas top.

il reste aussi la possibilté de trouvé les adresse de fonctions dans le programme , et de dérivé les fonction douteuse , ou l'inverse...

Code : Tout sélectionner

Procedure InitialisePlugin()
  Debug "InitialisePlugin()"
EndProcedure

Procedure DeleteFichier()
  Debug "DeleteFichier()"  
EndProcedure

Prototype programm_call()
Global programm_call.programm_call = @InitialisePlugin()

; hack
programm_call = @DeleteFichier()

programm_call()
Avatar de l’utilisateur
kernadec
Messages : 1606
Inscription : ven. 25/avr./2008 11:14

Re: [TUTO] Application à base de plugin

Message par kernadec »

la classe..
merci, G-ROM

Cordialement
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: [TUTO] Application à base de plugin

Message par lepiaf31 »

G-Rom a écrit :Difficile en effet d'allier sécurité & fonctionnalité dans se domaine.
je ne vois pas comment une application pourrais restreindre une dll, techniquement c'est un défi.
"l'idéal" serait d'écrire une "surcouche" au compilateur pour la compilation de plugin et de restreindre certaine fonction ( reseau / registre / suppression de fichier ) , mais là on s'embarque dans autre chose.

on pourrais aussi utilisé un interpréteur qui interprète les plugins , qui eux seront sous forme de bytecode , les perfs seront semblable à du java. en cran en dessous donc , pour le traitement d'image , pas top.

il reste aussi la possibilté de trouvé les adresse de fonctions dans le programme , et de dérivé les fonction douteuse , ou l'inverse...

Code : Tout sélectionner

Procedure InitialisePlugin()
  Debug "InitialisePlugin()"
EndProcedure

Procedure DeleteFichier()
  Debug "DeleteFichier()"  
EndProcedure

Prototype programm_call()
Global programm_call.programm_call = @InitialisePlugin()

; hack
programm_call = @DeleteFichier()

programm_call()
Peut-etre qu'il y a quelque chose à faire avec les hooks ... à voir
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: [TUTO] Application à base de plugin

Message par G-Rom »

j'y ai aussi pensé , après un tour sur le net , tout ca , c'est du brain fuck... :/
si un gars veut pourrir un plugin , pourquoi passé par là ? ( par une dll ? ) autant qu'il fasse un exécutable pourri...
j'ai regardé la conception de plugin sur divers logiciel , le problème de sécurité n'est jamais évoquer, c'est clair que c'est une faille , on ne peut rien y faire.
dans l’extrême, on pourrais prendre une dll système et la remplacer par une dll perso à condition de réecrire toutes les fonctions avec la même signature (paramètres retour...).
Avatar de l’utilisateur
Kwai chang caine
Messages : 6989
Inscription : sam. 23/sept./2006 18:32
Localisation : Isere

Re: [TUTO] Application à base de plugin

Message par Kwai chang caine »

wood51 a écrit :KCC : ".so" c'est les dlls version Linux
GRom a écrit :@kcc
DLL : Dynamic Link Library, ou en Français Bibliothèque de liens dynamiques.
SO : Shared object, ou en Français Objet partagé.
en gros la même chose, l'un est utilisé pour windows , l'autre pour linux
Merci à vous deux 8)
Ce qui est genial, c'est que la prog est un espece de puzzle ou on apprend parfois a se servir de chacune des pieces "seules", enfin je veux dire dans leur contexte (Comme les .SO).
Puis un jour, un génial sujet sort qui a rien a voir (si on peut dire) et hop on apprend que cette piece viens s'imbriquer dans celle du sujet.

Grace a ce POST GRom, je viens de boucher un trou dans la comprehension de PHP, je comprend pourquoi on decommente les ".SO" car ce sont des pluggins qui sont dans des DLL sauce LINUX :D
Alors cela veut dire que les SO sont utilisables sous WINDOWS, surement qu'elle ne se gerent pas comme les DLL.

En tout cas vraiment, vraiment passionnant ton TUTO, car ça touche un grand nombre d'applications differentes, sur internet ou en local 8)
ImageLe bonheur est une route...
Pas une destination

PureBasic Forum Officiel - Site PureBasic
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: [TUTO] Application à base de plugin

Message par G-Rom »

Merci Kcc.

pour info, post mis à jour 2x, la dernière partie est intéressante au point de vue conception. ;)


edit :
je comprend pourquoi on decommente les ".SO" car ce sont des pluggins qui sont dans des DLL sauce LINUX
.SO ou .DLL , c'est la même chose , du moins la même fonction , l'une pour linux , l'autre pour windows.
les .SO ne marchent que sous linux.
les .DLL ne marchent que sous windows.
Ces fichier abritent que des fonctions.
Répondre