Reflection, interfaces, plugins, frustration


  • Notification Spam Recipient

    I have this pet project of mine. A small gui program which loads plugins, displays them as a dropdown and lets me choose which one to execute. Nothing groundbreaking really.

    It was written in Framework 4.6 and worked fine. Then I got a bright idea of porting it to .Net 5, probably because it worked too well and things that are not broken are not interesting.

    How it's supposed to work:

    public interface IPluginHost
    {
        void DisplayMessage(string message);
    }
    
    public interface IPlugin
    {
        IPluginHost Host { get; set; }
    
        string GetPluginName();
    
        void Execute();
    }
    

    Host program implements IPluginHost, plugins implement IPlugin. Host loads plugins at startup, injects itself, gets plugin names. Obviously, I guess.

    Plugins loading:

    var currentAssembly = pluginLoadContext.LoadFromAssemblyPath(file.FullName);
    
    foreach(var type in currentAssembly.GetTypes())
    {
        if(type.IsPublic && !type.IsAbstract)
        {
            if(type.GetInterface(nameof(IPlugin)) != null)
            {
                var instance = Activator.CreateInstance(type);
                var plugin = (IPlugin)instance;
    
                Plugins.Add(plugin);
            }
        }
    }
    

    Problem? This line
    var plugin = (IPlugin)instance;
    throws
    InvalidCastException: 'Unable to cast object of type SomePlugin to type IPlugin'

    but at the same time, while debugging, I can do
    ((IPlugin)instance).GetPluginName()
    in immediate/watch window.

    Worked without a hitch before. Thoughts?


  • Notification Spam Recipient

    Aaaand, solved.

    Plugins reside in their own directory. As they all reference IPlugin, this library is copied to this directory. Main program also references IPlugin, so it's also copied to its directory.

    When doing the cast, runtime has two IPlugin.dll assemblies to choose from. Those assemblies are identical, but runtime gets confused and throws a fit.


  • Fake News

    @MrL said in Reflection, interfaces, plugins, frustration:

    Thoughts?

    I have been assisting on a project which also did its own plugin loading.

    We noticed that .NET Core would allow you to load the assembly containing the IPlugin interface definition twice (once from the host program's folders, once from a plugin's folder) and thus get incompatible instances.

    The workaround we used was to remove the DLL with the interface definition from the plugin folders before we made the plugin available for download, that way it can't show up twice.


  • Notification Spam Recipient

    @JBert said in Reflection, interfaces, plugins, frustration:

    @MrL said in Reflection, interfaces, plugins, frustration:

    Thoughts?

    I have been assisting on a project which also did its own plugin loading.

    We noticed that .NET Core would allow you to load the assembly containing the IPlugin interface definition twice (once from the host program's folders, once from a plugin's folder) and thus get incompatible instances.

    The workaround we used was to remove the DLL with the interface definition from the plugin folders before we made the plugin available for download, that way it can't show up twice.

    Yes, this turned out to be the case.

    The worst thing about this is that exception tells you nothing, inner is exception null, etc.


  • Discourse touched me in a no-no place

    @MrL said in Reflection, interfaces, plugins, frustration:

    The worst thing about this is that exception tells you nothing, inner is exception null, etc.

    Reminds me of the same sort of issue in Java, where I once got a class cast exception saying that class couldn't be cast to itself (because of library instance confusion). I don't have that sort of problem any more (mostly because it was such a common problem that there's standard ways to work around it now, especially including the provided library scope of Maven and the use of careful examination of the version graph) but it was horrible the first time.

    I have no idea whether any of that rambling can really carry over into the C# world. 😆


Log in to reply