Question

I've started on a simple plugin loader that monitors a directory and loads plugins if the dll(s) in it contain the IPlugin interface.

public class PluginLoader : Dictionary<string, IPlugin>
{
    private FileSystemWatcher watcher;
    private string pluginPath;

    public PluginLoader()
        : base()
    {            
        pluginPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "plugins");
        if (!Directory.Exists(pluginPath))
            Directory.CreateDirectory(pluginPath);

        watcher = new FileSystemWatcher(pluginPath, "*.dll");
        watcher.IncludeSubdirectories = true;
        watcher.Created += watcher_Created;
        watcher.EnableRaisingEvents = true;
    }        

    private void watcher_Created(object sender, FileSystemEventArgs e)
    {
        LoadPlugin(e.FullPath);
    }

    private void LoadPlugin(string path)
    {            
        IPlugin plugin = null;
        Assembly assembly = Assembly.LoadFrom(path);

        foreach (Type type in assembly.GetExportedTypes())
        {
            if (type.IsClass && type.GetInterfaces().Count(iType => iType == typeof(IPlugin)) == 1)
            {
                ConstructorInfo constructor = type.GetConstructor(new Type[] { });
                object instance = constructor.Invoke(new object[] { });
                plugin = instance as IPlugin;
                // plugin is now not null
            }
        }
        if (plugin != null && !this.ContainsKey(plugin.PluginName))
        {
            this[plugin.PluginName] = plugin;
        }
    }        
}    

This version of LoadPlugin() works, the plugin variable ends up being != null. This one, however, does not work:

    private void LoadPlugin(string path)
    {            
        IPlugin plugin = null;
        Assembly assembly = Assembly.LoadFrom(path);

        foreach (Type type in assembly.GetExportedTypes())
        {
            if (type.IsClass && type.GetInterface(typeof(IPlugin).FullName) != null)
            {
                ConstructorInfo constructor = type.GetConstructor(new Type[] { });
                object instance = constructor.Invoke(new object[] { });
                plugin = instance as IPlugin;
                // plugin is still null
            }
        }
        if (plugin != null && !this.ContainsKey(plugin.PluginName))
        {
            this[plugin.PluginName] = plugin;
        }
    }

I just don't understand why. So my question is: Why does plugin end up being null in the second example?

Solution: Problem was that I had two different IPlugin types because its assembly existed in two different locations. So deleting the Framework.Lib.dll in the plugin directory solved it.

Was it helpful?

Solution

The only reason I can think of is that the type implements an IPlugin with the same namespace but from a different assembly. The typeof(IPlugin).FullName would then match between your plugin loader and the plugin, but the implemented type still does not equal the expected type.

The first example does match the exact same type, in the second example you're matching by the FullName which only includes the namespace, not the assembly a type is loaded from.

To determine whether this is the case, try log the following value:

bool matches = typeof(IPlugin).IsAssignableFrom(type);
string expected = typeof(IPlugin).AssemblyQualifiedName;
string actual = type.GetInterface(typeof(IPlugin).FullName).AssemblyQualifiedName;

typeof(IPlugin).IsAssignableFrom(type) is probably what you were looking for in the first place.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top