in

DotNetMarche

.NET Framework User Group delle Marche

Articoli

Creare un programma estensibile tramite plugins - Parte 2

Di: Ricci Gian Maria

La tecnologia .NET consente di iniettare codice in maniera dinamica in un processo, rendendolo di fatto estendibile mediante unità di codice chiamate plugin.

Attributi alla riscossa

Nel precedente articolo sono state esaminate le basi per gestire l’Inversion of Control tramite plugin, in questo articolo si cercherà di dare più dignità alla classe PluginManager in modo da renderla un po’ più robusta. Il problema principale del codice del precedente esempio è che i plugin vengono individuati in base all’interfaccia implementata, generando di fatto due problemi: il primo è che l’esposizione di un plugin all’esterno della dll viene fatta in maniera implicita ed il secondo è che non si hanno informazioni dettagliate da mostrare all’utente. Per risolvere entrambi i problemi è necessario agire su due punti: dare la possibilità di decidere in maniera programmatica quali classi verranno esportate e fornire nel contempo un set di informazioni standard da mostrare all’utente o da utilizzare per gestire il plugin in maniera meno implicita.

Da questa mini analisi si evince subito che un attributo custom è probabilmente la soluzione ottimale dato che consente di aggiungere informazioni ad una classe sotto forma di metadati compilati nell’assembly e può essere utilizzato anche per marcare esplicitamente tutte le classi che dovranno essere gestite come plugin.

Creare un attributo custom per i plugin

Il primo passo è creare l’attributo per identificare i plugin come mostrato dalla classe PluginAttribute presente nell’esempio accluso. La creazione di un attributo custom esula da questo articolo, ma si può comunque notare che è sufficiente ereditare dalla classe Attribute e decorare la classe con l’attributo AttributeUsage che consente di specificare come deve essere gestito l’attributo stesso.

In questo caso si è specificato che il nostro attributo può essere utilizzato solamente con le classi e che non può essere ereditato. La classe attributo deve poi contenere le proprietà che identificano il plugin ed esporre un costruttore di convenienza per rendere più semplice l’utilizzo dell’attributo stesso. Nel caso in esame sono state scelte quattro proprietà distintive per il plugin: il nome, una sigla che indica il creatore, la versione ed infine una descrizione testuale che permette di specificare dettagliatamente cosa fa il plugin in questione.

Per completare la classe viene anche fatto l’override del metodo virtuale ToString() per dare una descrizione più user friendly dell’attributo.

Un istanza di un PluginAttribute è quindi a tutti gli effetti una descrizione completa di un plugin che può essere sia mostrata all’utilizzatore a titolo informativo, sia utilizzata internamente dal gestore per la catalogazione di tutti i plugin caricati.

Le altre classi di supporto

Prima di esaminare il codice del nuovo gestore è necessario creare altre classi ausiliarie necessarie per una gestione efficente. Prima di tutto si deve creare un altro attributo custom chiamato PluginInterfaceAttribute il cui scopo è specificare in maniera esplicita le interfacce implementate da una classe plugin.

In questo caso la proprietà AllowMultiple è pari a true dato che una singola classe può implementare se vuole più interfacce di plugin e quindi presentare un’istanza di questo attributo per ognuna di esse. Con questa tecnica abbiamo del tutto eliminato il caricamento implicito dato che ogni classe che vuole essere caricata dinamicamente deve essere decorata in maniera esplicita con una serie di attributi che ne identificano l’utilizzo.

L’ultima classe da esaminare si chiama PluginInfo ed il suo scopo è contenere tutte le informazioni su di un plugin caricato in modo da permetterne la gestione. Il costruttore accetta come parametro il tipo di dato da gestire ed internamente inserisce in una collection tutte le interfacce implementate recuperando tramite reflection.

Come si può notare le interfacce implementate vengono individuate semplicemente esaminando la la lista di attributi PluginInterface definiti sul tipo. La classe PluginInfo al suo interno tiene inoltre un riferimento al tipo di oggetto e fornisce un metodo ImplementInterface() che restituisce un boolean che indica se il tipo implementa una particolare interfaccia.

Il nuovo gestore di plugin

Il nuovo gestore internamente mantiene la lista di tutti i plugin caricati in un dictionary la cui chiave è un’istanza della classe PluginAttribute e il valore è un PluginInfo; due clausole using rendono più leggibile il codice dichiarando due alias di convenienza

Come per la versione precedente la classe PluginManager accetta nel costruttore il nome della cartella che internamente contiene i plugin da caricare e chiama la funzione AnalyzeAssemblyFile() su tutti i file .dll che trova nella cartella specificata.

Questa nuova versione è se possibile anche più semplice rispetto alla precedente, in particolare la scansione degli assembly è ora desicamente più lineare. Come nella versione precedente si esaminano tutti i tipi definiti nell’assembly e per sapere se il tipo esaminato deve essere caricato è sufficiente utilizzare il metodo IsDefined() della classe Type che permette di controllare se il tipo è stato decorato con un particolare attributo. Per identificare i plugin basta quindi cercare un attributo di tipo PluginAttribute e in caso positivo inserirlo nell’apposito dictionary creando il corrispondente PluginInfo().

Il gestore fornisce due soli metodi per interagire con l’esterno, GetPluginListForInterface() restituisce una lista di PluginAttribute contenente tutti i plugin che implementano una determinata interfaccia, mentre CreateInstance<T>() permette di creare un’istanza di plugin passando il corrispondente PluginAttribute.

Come si può vedere la creazione di nuove istanze viene fatta utilizzando l’oggetto Activator che chiama il costruttore di default dell’oggetto, come si può vedere l’implementazione è decisamente semplice e facile da gestire.

Cosa cambia lato client

Questi cambiamenti non complicano assolutamente il codice lato client. Il programma di esempio è costituito infatti dalla solita combobox con cui si seleziona il plugin da utilizzare e in aggiunta viene inserito un controllo PropertyGrid che permette di visualizzare tutti i dati dei plugin caricati.

Nell’evento FormLoad viene creata l’istanza del gestore passando la cartella contenente i plugin. Un client più efficace dovrebbe tenere una singola istanza del gestore con un pattern di tipo singleton, ma questo esercizio è lasciato al lettore. La creazione di un PluginManager è infatti piuttosto onerosa dato che internamente si utilizzano numerosi metodi di reflection.

Per visualizzare la lista dei plugin è sufficiente impostare il DataSource della ComboBox che automaticamente chiama il metodo ToString() per ogni oggetto della lista, mostrando di fatto la descrizione dell’attributo. Tramite la gestione dell’evento SelectedIndexChanged viene invece aggiornata la visualizzazione sulla PropertyGrid.

Infine la pressione del bottone crea un’istanza del plugin ed invoca il suo unico metodo Greet(). Anche in questo caso l’interfaccia generica consente di scrivere codice senza cast ed inoltre, dato che il metodo CreateInstance accetta un PluginAttribute, è sufficiente passare il parametro comboBox1.SelectedValue.

Come si può notare utilizzare il gestore è veramente immediato ed efficace e richiede poche linee di codice.

Implementare un plugin

L’ultimo cambiamento rispetto all’esempio precedente riguarda i plugin, che per poter essere caricati debbono ora essere compilati con gli appositi attributi, ecco ad esempio l’implementazione dell’EducatedGreeter.

Come si può vedere sono presenti due attributi, il primo imposta le informazioni base sul plugin ed il secondo indica l’interfaccia implementata.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Published Jan 29 2007, 09:37 AM by Alkampfer
Filed under:
Attachment: Examples.zip

Comments

No Comments

About Alkampfer

Chi sono

Mi chiamo Gian Maria Ricci, sono nato il 18/08/1974 e il fatto che mi troviate qui in dotnetmarche significa che per passione e per lavoro mi occupo di .NET.

Sono laureato in Ingegneria Elettronica con specializzazione in Microelettronica, ma la mia passione è sempre stata la programmazione, che coltivo da quando mi fu regalato il mitico Vic20 un paio di decadi fa. Passato per il Basic, alle superiori ho programmato in Pascal e Prolog con qualche deviazione in Fortran e Cobol, fino ad approdare al C/C++. Per lavoro ho poi conosciuto il VB6 ed ho iniziato ad apprezzare il .NET fin dalle beta, tanto da utilizzare .NET come ambiente di sviluppo per la mia tesi.

Oltre che di dotnetmarche faccio parte del gruppo nablasoft, formato da miei ex colleghi di università con cui ho condiviso e condivido ancora la passione per la computer graphics. Tra i lavori prodotti per nablasoft, sicuramente quello di cui vado più orgoglioso è un tutorial in lingua inglese sulla programmazione della Playstation2 tramite il linux kit che ancora oggi ci da una visibilità al primo posto in alcune ricerche di google. Nel sito trovate inoltre altri lavori miei e dei miei colleghi che spaziano dalla matematica, all’elettronica e naturalmente anche al .NET.

Tra le mie altre partecipazioni a gruppi web posso citare GPI (game programming italia) per il quale ricopro il ruolo di responsabile della sezione playstation2, anche se la mia attività è sicuramente stata molto breve ed è molto che non contribuisco più in maniera attiva.

Le mie pubblicazioni

Nel corso degli anni oltre a pubblicare materiale nel sito nablasoft, ho collaborato con altre realtà come www.programmazione.it, il gruppo playstation2linux ed il gruppo infomedia di cui sono articolista principalmente per la rivista dev.

  • "Funzionali ed adattatori nella STL"- Computer Programming (aprile 2004).
  • "Esportare il contenuto di una DataGrid in Microsoft Excel" –Computer Programming (maggio 2004)
  • "Garbage Collection in ambiente .NET" – Computer Programming (luglio 2004)
  • "Serializzazione e persistenza in database" –DEV (Luglio 2004)
  • "Programmare la PS2 tramite il Linux kit. Introduzione" - DEV (settembre 2004)
  • "Utilizzare la libreria zlib in C# o VB.NET" - DEV (agosto 2004)
  • "Programmare la PS2 tramite il Linux kit. Renderizzare un cubo" – DEV (ottobre 2004)
  • "Utilizzo di puntatori per elaborare immagini in C#" - DEV (novembre 2004)
  • "Programmare la PS2 tramite il Linux kit. Utilizzare il joypad" - DEV (novembre 2004)
  • "Personalizzare l'aspetto dei menu con GDI+" - DEV (dicembre 2004)
  • "Utilizzare MSDE in applicazioni desktop. Installazione e gestione" – DEV (dicembre 2004)
  • "Utilizzare MSDE in applicazioni desktop. Deployment" – DEV (gennaio 2005)
  • "PC o console, tecniche di sviluppo a confronto" – DEV (gennaio 2004)
  • "CLR Profiler per .NET - Stringhe e finalizzazione" – Computer Programming (gennaio 2005)
  • "Utilizzare MSDE in applicazioni desktop. Distribuire la struttura di database" – DEV (febbraio 2005)
  • "Comunicare in rete tramite C#" – DEV (febbraio 2004)
  • "CLR Profiler per .NET – Pool di oggetti" – Computer Programming (febbraio 2005)
  • "Amministrare le politiche in Active Directory" – Login (marzo/aprile 2005)
  • "Spedire mail in C#" – DEV (aprile 2005)
  • "Utilizzare API di shell in C#" – DEV (maggio 2005)
  • ".NET e remoting" – DEV (Maggio 2005)
  • "Utilizzare Dll C++ in Visual Basic 6" – DEV (Maggio 2005)
  • "Sql Server 2005 - Scrivere un aggregatore custom in Visual Basic" – DEV (Giugno 2005)
  • "Utilizzare le API di shell in C# - 2a parte" – DEV (Giugno 2005)
  • "Utilizzare le API di shell in C# - 3a parte" – DEV (Settembre 2005)
  • "Trasferire una sessione da ASP ad ASP.NET " – DEV (Settembre 2005)
  • "Unit testing in .NET 2.0" – DEV (Ottobre 2005)
  • "Visual Studio 2005 Class diagram" – DEV (Ottobre 2005)
  • "Nullable Types in .NET 2.0", DEV (Novembre 2005)
  • "Creare Una trial in C#", DEV (Dicembre 2005)
  • "Linguaggi ed evoluzione di sintassi", DEV (Gennaio 2006)
  • "C# 2.0 Evoluzioni sintattiche”, DEV (Gennaio 2006)
  • "Office Web Components - Realizzare un report in Excel", DEV (Gennaio 2006)
  • "VB.NET e Sql Server 2005 - Scrivere stored procedures in VB.NET", DEV (Febbraio 2006)
  • "Persistence Frameworks in C#”, DEV (Maggio 2006)
  • "Persistenza con serializzazione XML in .NET 2.0", DEV (Maggio 2006)
  • ".NET 2.0 – Enterprise Library 2.0, logging", DEV (Maggio 2006)
  • "Database - XML e SQL Server 2005", DEV(Giugno 2006)
  • "Enterprise Library 2.0 Logging - Seconda parte", DEV (Giugno 2006)
  • "ASP.NET 2.0 e AJAX", DE (Luglio 2006)

  • PS2 3D programming Tutorial - Tutorial sulla programmazione 3D per la console Playstation 2 Pubblicato nel sito ufficiale Sony per il kit PS2 linux.

Powered by Community Server (Commercial Edition), by Telligent Systems