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 :

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 :

- 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

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...]])
le prototype de la fonction initializePlugin() est la suivante :
Code : Tout sélectionner
initializePlugin(*aP.application)
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
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
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
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() )
Code : Tout sélectionner
createToolBarButton(*aP.application, *iconePtr.i ,*linkFunction.i)
*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
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
Tip :
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.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 !
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)
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
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
Code : Tout sélectionner
*monApplication.Application
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
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
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()
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
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.