views:

157

answers:

3

Intro

I am building a plugin architecture in my app. The plugins implement a given Interface IBasePlugin, or some other interface which inherited from the base interface:

interface IBasePlugin
interface IMainFormEvents : IBasePlugin

The host is loading the plugin assemblies, and then creates the appropriate object of any class implementing the IBasePlugin interface..

This is the class loading the plugins and instantiating the objects:

 public class PluginCore
 {
     #region implement singletone instance of class
     private static PluginCore instance;
     public static PluginCore PluginCoreSingleton
     {
         get
         {
             if (instance == null)
             {
                 instance = new PluginCore();
             }
             return instance;
         }
     }
     #endregion

     private List<Assembly> _PlugInAssemblies = null;
     /// <summary>
     /// Gets the plug in assemblies.
     /// </summary>
     /// <value>The plug in assemblies.</value>
     public List<Assembly> PlugInAssemblies
     {
         get
         {
             if (_PlugInAssemblies != null) return _PlugInAssemblies;

             // Load Plug-In Assemblies
             DirectoryInfo dInfo = new DirectoryInfo(
                 Path.Combine(
                     Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                     "Plugins"
                     )
                 );
             FileInfo[] files = dInfo.GetFiles("*.dll");
             _PlugInAssemblies = new List<Assembly>();
             if (null != files)
             {
                 foreach (FileInfo file in files)
                 {
                     _PlugInAssemblies.Add(Assembly.LoadFile(file.FullName));
                 }
             }

             return _PlugInAssemblies;
         }
     }

     List<IBasePlugin> _pluginsList = null;
     /// <summary>
     /// Gets the plug ins instances.
     /// all the plugins are being instanciated ONCE when this if called for the first time
     /// every other call will return the existing classes.
     /// </summary>
     /// <value>The plug ins instances.</value>
     public List<IBasePlugin> PlugInInstances
     {
         get
         {
             if (_pluginsList != null) return _pluginsList;

             List<Type> availableTypes = new List<Type>();

             foreach (Assembly currentAssembly in this.PlugInAssemblies)
                 availableTypes.AddRange(currentAssembly.GetTypes());

             // get a list of objects that implement the IBasePlugin
             List<Type> pluginsList = availableTypes.FindAll(delegate(Type t)
             {
                 List<Type> interfaceTypes = new List<Type>(t.GetInterfaces());
                 return interfaceTypes.Contains(typeof(IBasePlugin));
             });

             // convert the list of Objects to an instantiated list of IBasePlugin
             _pluginsList = pluginsList.ConvertAll<IBasePlugin>(delegate(Type t) { return Activator.CreateInstance(t) as IBasePlugin; });

             return _pluginsList;
         }
     }

The Question

Currently, any module which supports the plugins, uses the PlugInInstances property to retrieve the IBasePlugins list. Then it iterates the objects querying who is implementing a given child interface.

foreach (IBasePlugin plugin in PluginCore.PluginCoreSingleton.PlugInInstances)
{
     if (plugin is IMainFormEvents)
     {
         // Do something
     }
 }

I would like to improve this technique by having a function that receives a given child interface, and return a list of those interface. The catch is that no casting should be done in the caller.

pseudo code:

void GetListByInterface(Type InterfaceType, out List<InterfaceType> Plugins)

Do you have a recommendation on how to implement this?

+2  A: 

You could try something like this:

void GetListByInterface<TInterface>(out IList<TInterface> plugins) where TInterface : IBasePlugin
{
  plugins = (from p in _allPlugins where p is TInterface select (TInterface)p).ToList();
}
Andrew Kennan
@Andrew: thanks, just what i was looking for. Though i had to implement it without LINQ, since i have .NET 2 compliance.
Am
I should have said _allPlugins.OfType<TInterface>() but it doesn't really matter as you're not using linq.
Andrew Kennan
+2  A: 

I used a similar approach for my tournament system.

You can take a look at the source here: http://tournaments.codeplex.com/SourceControl/ListDownloadableCommits.aspx

In the trunk/TournamentApi/Plugins/PluginLoader.cs, I have defined the methods required to load an arbitrary assembly's plugins.


The idea I used was that of a Plugin-Factory-Enumerator class that can be found, instantiated and called to produce plugin factory instances.

Here is the meat of the code:

List<IPluginFactory> factories = new List<IPluginFactory>();

try
{
    foreach (Type type in assembly.GetTypes())
    {
        IPluginEnumerator instance = null;

        if (type.GetInterface("IPluginEnumerator") != null)
        {
            instance = (IPluginEnumerator)Activator.CreateInstance(type);
        }

        if (instance != null)
        {
            factories.AddRange(instance.EnumerateFactories());
        }
    }
}
catch (SecurityException ex)
{
    throw new LoadPluginsFailureException("Loading of plugins failed.  Check the inner exception for more details.", ex);
}
catch (ReflectionTypeLoadException ex)
{
    throw new LoadPluginsFailureException("Loading of plugins failed.  Check the inner exception for more details.", ex);
}

return factories.AsReadOnly();
John Gietzen
A: 

I would using an IOC container to perform the plug-in lookup. MEF might be a little much, but StructureMap is a single DLL, and has built-in support for this out of the box.

You can Scan a folder for assembly that contain objects that implement interfaces and load them into your application easily. StructureMap on SourceForge

Example of scanning within the Configure method of ObjectFactory:

        Scan(scanner =>
        {
            string assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            scanner.AssembliesFromPath(assemblyPath, assembly => { return assembly.GetName().Name.StartsWith("Plugin."); });

            scanner.With(typeScanner);
        });

The type scanner implements ITypeScanner and can examine the types are check if the type is assignable to the interface type in question. There are good examples in the documentation link attached.

Chris Patterson