in

DotNetMarche

.NET Framework User Group delle Marche

Articoli

Creare un programma estendibile mediante plugin - Parte 1

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.

Minimizzare le dipendenze

Negli ultimi anni si è molto studiato su come minimizzare le dipendenze tra gli oggetti, in modo da rendere le applicazioni molto più flessibili ed estendibili. Questo studio ha portato alla creazione di pattern particolari come quelli di Dependency Injection o Inversion of control, di cui il lettore può trovare abbondanza di informazioni semplicemente tramite google. In questo articolo si vuole invece esaminare una tecnica particolare che permette non solo di minimizzare la dipendenza tra gli oggetti, ma anche di estendere un’applicazione senza ricompilare.

Si consideri un semplicissimo esempio di un applicativo minimale che deve solamente salutare l’utente, sulla base del classico hello world. Il programma è costituito da una semplice classe chiamata standardGreeter ed una form che esegue un semplice saluto alla pressione di un bottone

Il codice è veramente banale, ma è già sufficiente per individuare una forte dipendenza con la classe StandardGreeter. Se ad esempio il nostro programma deve evolvere permettendo di eseguire un saluto più formale rispetto ad un semplice hello!! si potrebbe semplicemente realizzare un’ulteriore versione della classe chiamata FormalGreeter modificando contemporaneamente il codice dell’handler del bottone.

Questo approccio ha un enorme punto debole, il codice eseguito alla pressione del bottone deve conosce il tipo concreto di classe da istanziare per chiamare il suo metodo Greet() e quindi dipende dal tipo di oggetto utilizzato. Per risolvere elegantemente questo tipo di problematiche si può utilizzare un pattern di Abstract Factory, oppure utilizzare librerie come la Spring.NET per l’Inversion of control, o contenitori come il picocontainer, etc etc. In questo articolo la soluzione proposta è invece basata su plugin, una tecnica particolare per caricare dinamicamente il codice implementando così un pattern di Inversion of Control.

Caricamento dinamico di codice

Un plugin non è altro che un blocco di codice che viene caricato a runtime dal programma per estendere il funzionamento base. Il primo requisito per poter creare un plugin è definire la sua interfaccia, il programma host infatti non può caricare arbitrarie parti di codice se poi non sa come utilizzarle e dunque il primo passo per poter gestire un plugin è definirne l’interfaccia, proprio come se si stesse per realizzare una classica implementazione di Abstract Factory. Nell’esempio accluso è stata definita a questo proposito l’interfaccia IGreeter che comprende un’unica funzione e individua il contratto che deve essere implementato da un oggetto in grado di restituire un saluto.

La particolarità è che l’interfaccia viene definita in un progetto esterno a quello principale. La ragione è semplice dato che chi implementa un plugin deve avere un riferimento al progetto che contiene il contratto e quindi è più logico che tutte le interfacce che possono essere implementate da plugin esterni si trovino in un assembly dedicato che può essere così referenziato da chiunque voglia scrivere un plugin.

Nella solution di esempio si trovano infatti due implementazioni dell’interfaccia IGreeter, ognuna implementata in un differente assembly. Il progetto più interessante è comunque il PluginManager che contiene il codice per gestire il caricamento dinamico del codice. Dal punto di vista del programma principale un plugin è una classe che implementa un interfaccia riconosciuta e contenuto in un assembly esterno sconosciuto in fase di compilazione. Durante l’esecuzione è compito del PluginHandler scandire una particolare cartella in cui sono contenuti tutti i plugin al fine di identificare al suo interno tutti i tipi che implementano l’interfaccia richiesta ed occuparsi poi della creazione delle specifiche istanze. Il codice che effettua la scansione è veramente semplice

Come si può vedere al costruttore dell’oggetto PluginManager viene semplicemente passata la cartella da esaminare e il nome dell’interfaccia cercata. La funzione che effettua tutto il lavoro si chiama ScanDirectory() e itera semplicemente tra tutti i file con estensione dll presenti nella cartella invocando per ognuno di essi la funzione AnalyzeAssemblyFile(), la quale non fa altro che tentare di caricare l’assembly in memoria, iterare in tutti i tipi contenuti e cercare con la funzione GetInterface() i tipi che implementano l’interfaccia richiesta. Per ogni plugin trovato viene creato un nuovo oggetto PluginInfo che viene poi inserito in un dizionario indicizzato con il nome del tipo stesso. L’oggetto PluginInfo mantiene infatti al suo interno tutte le informazioni sul plugin caricato; internamente la classe PluginInfo ha due sole proprietà: il nome del plugin ed un riferimento al tipo di oggetto che lo implementa. Durante la creazione di un oggetto PluginInfo viene inoltre recuperato un riferimento al costruttore di default, utilizzato successivamente per creare nuove istanze.

Come si può notare il codice per gestire il plugin è veramente minimale. Il programma chiamante è anch’esso diverso dalla versione precedente, la form contiene infatti una combo in cui vengono listati tutti i plugin disponibili ed alla pressione del bottone viene utilizzato il greeter scelto dall’utente. Nel progetto incluso sono state inoltre aggiunte delle post build action ai due progetti in modo che, ad ogni compilazione, le dll con i plugin vengano inserite automaticamente nella sottocartella Plugins del programma principale.

Per capire il vantaggio di questa tecnica si cancelli il file EducatedGreeter.dll dalla cartella plugins e si esegua il programma; nella combo ora appare solamente lo StandardGreeter. A questo punto basta copiare nuovamente il file EducatedGreeter.dll nella cartella dei plugin, riavviare il programma e verificare che è nuovamente possibile utilizzare entrambi i greeter, senza dover ricompilare nulla. Se in futuro sorgerà la necessità di un ulteriore Greeter non si deve fare altro che implementare un nuovo plugin e copiarlo nella cartella apposita.

Conclusioni

L’utilizzo del caricamento dinamico di assembly e della reflection permette di implementare con veramente poche righe di codice una libreria che permette di gestire l’Inversion of Control con un ottica basata su caricamento dinamico di codice. Il programma finale è infatti in grado di utilizzare i nuovi plugin senza necessità di dover ricompilare l’applicativo o modificare file di configurazione, è sufficiente infatti copiare le nuove dll nella apposita cartella e “l’iniezione” del nuovo codice è completamente automatica.

Published Oct 30 2006, 01:29 PM by Alkampfer
Filed under:
Attachment: code.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