Quando si deve realizzare un sistema basato su plug-in, è buona norma utilizzare un AppDomain diverso per caricare le estensioni, in modo che un plug-in mal funzionante non possa compromettere il funzionamento dell'intera applicazione. La soluzione che adotto io si basa su una classe chiamata AssemblyLoader:
1 using System;
2 using System.Reflection;
3
4 namespace System.Reflection
5 {
6 public class AssemblyLoader : MarshalByRefObject
7 {
8 public AssemblyLoader()
9 { }
10
11 public object CreateInstance(string assemblyName,
12 string interfaceName, params object[] args)
13 {
14 try
15 {
16 Assembly assembly = Assembly.LoadFrom(assemblyName);
17 foreach (Type type in assembly.GetExportedTypes())
18 {
19 Type interfaceType = type.GetInterface(interfaceName);
20 if (interfaceType != null)
21 return Activator.CreateInstance(type, args);
22 }
23 return null;
24 }
25 catch
26 {
27 return null;
28 }
29 }
30 }
31 }
Il cuore della classe è il metodo CreateInstance, che controlla i tipi esportati da un certo assembly (riga 17), quindi crea un'istanza del tipo che implementa l'interfaccia passata come argomento (righe 19-21).
Tale classe deve essere compilata in una class library. A questo punto, per caricare un plug-in in un nuovo AppDomain è sufficiente scrivere qualcosa del tipo:
AppDomain pluginAppDomain = AppDomain.CreateDomain("Plug-in AppDomain");
AssemblyLoader loader =
(AssemblyLoader)pluginAppDomain.CreateInstanceFromAndUnwrap
("AssemblyLoader.dll", typeof(AssemblyLoader).FullName);
IPlugin plugin = (IPlugin)
loader.CreateInstance(AppDomain.CurrentDomain.BaseDirectory +
@"\Plugins\TestPlugin.dll", "IPlugin");
L'oggetto loader viene caricato nell'AppDomain chiamato Plug-in AppDomain. IPlugin è l'interfaccia che deve essere implementata da ogni plug-in; ogni plug-in, inoltre, deve estendere la classe MarshalByRefObject. Il nome dell'interfaccia deve essere passato come secondo argomento alla funzione AssemblyLoader.CreateInstance. Il risultato di questo codice è l'istanziazione della classe che implementa IPlugin (e che è contenuta in TestPlugin.dll) all'interno del nuovo AppDomain. In tal modo, richiamando sull'oggetto plugin i metodi che sono definiti nell'interfaccia IPlugin, essi verranno eseguiti all'interno del Plug-in AppDomain.
Utilizzando questa strategia, se il plug-in manifesta qualche problema è sufficiente scaricare l'AppDomain che lo contiene, senza essere costretti ad interrompere l'intera applicazione.