37

I'm trying to write a plugin system to provide some extensibility to an application of mine so someone can write a plugin(s) for the application without touching the main application's code (and risk breaking something).

I've got the base "IPlugin" interface written (atm, nothing is implemented yet)

Here is how I'm loading:

public static void Load()
{
    // rawr: http://www.codeproject.com/KB/cs/c__plugin_architecture.aspx
    String[] pluginFiles = Directory.GetFiles(Plugins.PluginsDirectory, "*.dll");
    foreach (var plugin in pluginFiles)
    {
        Type objType = null;
        try
        {
            //Assembly.GetExecutingAssembly().GetName().Name
            MessageBox.Show(Directory.GetCurrentDirectory());
            Assembly asm = Assembly.Load(plugin);
            if (asm != null)
            {
                objType = asm.GetType(asm.FullName);
                if (objType != null)
                {
                    if (typeof(IPlugin).IsAssignableFrom(objType))
                    {
                        MessageBox.Show(Directory.GetCurrentDirectory());
                        IPlugin ipi = (IPlugin)Activator.CreateInstance(objType);
                        ipi.Host = Plugins.m_PluginsHost;
                        ipi.Assembly = asm;
                    }
                }
            }
        }
        catch (Exception e)
        {
            MessageBox.Show(e.ToString(), "Unhandled Exception! (Please Report!)", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
        }
    }
}

A friend tried to help but I really didn't understand what was wrong.

The folder structure for plugins is the following:

\
\Plugins\

All plugins reference a .dll called "Lab.Core.dll" in the [root] directory and it is not present in the Plugins directory because of duplicate references being loaded.

The plugin system is loaded from Lab.Core.dll which is also referenced by my executable. Type "IPlugin" is in Lab.Core.dll as well. Lab.Core.dll is, exactly as named, the core of my application.

EDIT:

Question: Why/What is that exception I'm getting and how could I go about fixing it?

FINAL EDIT:

Ok so I decided to re-write it after looking at some source code a friend wrote for a TF2 regulator.

Here's what I got and it works:

    public class TestPlugin : IPlugin {
    #region Constructor

    public TestPlugin() {
        //
    }

    #endregion

    #region IPlugin Members

    public String Name {
        get {
            return "Test Plugin";
        }
    }

    public String Version {
        get {
            return "1.0.0";
        }
    }

    public String Author {
        get {
            return "Zack";
        }
    }

    public Boolean OnLoad() {
        MessageBox.Show("Loaded!");
        return true;
    }

    public Boolean OnAllLoaded() {
        MessageBox.Show("All loaded!");
        return true;
    }

    #endregion
}

        public static void Load(String file) {
        if (!File.Exists(file) || !file.EndsWith(".dll", true, null))
            return;

        Assembly asm = null;

        try {
            asm = Assembly.LoadFile(file);
        } catch (Exception) {
            // unable to load
            return;
        }

        Type pluginInfo = null;
        try {
            Type[] types = asm.GetTypes();
            Assembly core = AppDomain.CurrentDomain.GetAssemblies().Single(x => x.GetName().Name.Equals("Lab.Core"));
            Type type = core.GetType("Lab.Core.IPlugin");
            foreach (var t in types)
                if (type.IsAssignableFrom((Type)t)) {
                    pluginInfo = t;
                    break;
                }

            if (pluginInfo != null) {
                Object o = Activator.CreateInstance(pluginInfo);
                IPlugin plugin = (IPlugin)o;
                Plugins.Register(plugin);
            }
        } catch (Exception) {
        }
    }

    public static void LoadAll() {
        String[] files = Directory.GetFiles("./Plugins/", "*.dll");
        foreach (var s in files)
            Load(Path.Combine(Environment.CurrentDirectory, s));

        for (Int32 i = 0; i < Plugins.List.Count; ++i) {
            IPlugin p = Plugins.List.ElementAt(i);
            try {
                if (!p.OnAllLoaded()) {
                    Plugins.List.RemoveAt(i);
                    --i;
                }
            } catch (Exception) {
                Plugins.List.RemoveAt(i);
                --i;
            }
        }
    }
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Zack
  • 2,477
  • 4
  • 37
  • 50
  • 3
    I note that you haven't actually asked a question, you've just described what you're trying to do. Could you phrase your question in the form of a question? – Eric Lippert Jul 01 '09 at 19:14
  • 1
    Err. Sorry, got hung up trying to explain what I was doing but forgot the question. :x I'll make that edit. – Zack Jul 01 '09 at 19:16
  • 1
    Please post the full exception you received, complete with stack trace and any inner exceptions. Post the results of ex.ToString(). – John Saunders Jul 01 '09 at 19:26
  • What's the reason for asm.GetType(asm.FullName)? And what's the value of asm.FullName when this fails? – John Saunders Jul 01 '09 at 19:28
  • Zack, check my answer below. To prove it, configure your plugin directory to the same directory as your executable and other DLL's. – Brad Barker Jul 01 '09 at 19:35
  • Ok, GetType isn't the issue. What is the exact value of "plugin" that makes this fail? I'm concerned about the number of slashes I see in the output. When reporting the answer, please make sure you're looking at real slashes and not those generated by the debugger. – John Saunders Jul 01 '09 at 19:39
  • Just because you have it working now, does not mean it will continue working see: http://stackoverflow.com/questions/974495/net-add-ins-and-versioning – Sam Saffron Jul 01 '09 at 22:44
  • MSDN has a page on creating plug-in mechanism: [Creating a simple plugin mechanism](https://code.msdn.microsoft.com/windowsdesktop/Creating-a-simple-plugin-b6174b62) – samad montazeri Aug 04 '16 at 13:06

3 Answers3

33

The Managed Extensibility Framework (MEF) is a new library in .NET that enables greater reuse of applications and components. Using MEF, .NET applications can make the shift from being statically compiled to dynamically composed. If you are building extensible applications, extensible frameworks and application extensions, then MEF is for you.

http://www.codeplex.com/MEF

Edit: CodePlex is going away - the code has been moved to Github for archival purposes only: https://github.com/MicrosoftArchive/mef

MEF is now a part of the Microsoft .NET Framework, with types primarily under the System.Composition. namespaces. There are two versions of MEF

  • System.ComponentModel.Composition, which has shipped with .NET 4.0 and higher. This provides the standard extension model that has been used in Visual Studio. The documentation for this version of MEF can be found here
  • System.Compostion is a lightweight version of MEF, which has been optimized for static composition scenarios and provides faster compositions. It is also the only version of MEF that is a portable class library and can be used on phone, store, desktop and web applications. This version of MEF is available via NuGet and is documentation is available here
VoteCoffee
  • 4,692
  • 1
  • 41
  • 44
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    This should really be possible without the use of MEF. – Brad Barker Jul 01 '09 at 19:14
  • 11
    Of course it is possible without the use of MEF. There's nothing magic in MEF. But MEF was designed and implemented by experts in plugin architecture over many years. (I worked a little bit on the initial design of MEF back in 2004 IIRC.) You really want to try to duplicate the effort of five years of work by pro architects in this space? – Eric Lippert Jul 01 '09 at 19:17
  • Well, I don't have a problem with the answer if my first comment came off as confrontational. It's actually good to have this in here to know it exists. Also, though, I hope we get to see what is wrong with this plugin example. – Brad Barker Jul 01 '09 at 19:22
  • @Brad: I'm with you. I hope my post didn't come across as 'hurried' or desperate, lol. – Zack Jul 01 '09 at 19:27
  • @Brad- anything is possible without frameworks, but MEF looks like a great candidate to evaluate for plugin scenarios. In my experience too many devs quickly dismiss frameworks without evaluating them properly. For example I've seen lots of people write rubbish logging frameworks when they could of used something like log4net. – RichardOD Jul 01 '09 at 19:42
  • @RichardOD, I don't disagree, but we are still yet to come to a definitive answer for this question. It still serves as a good programming exercise if nothing else. – Brad Barker Jul 01 '09 at 20:07
  • @Eric, MEF is still in flux, so people are a little shy to adopt it. Also MEF is HUGE and has thousands of features some very simple systems do not need. – Sam Saffron Jul 01 '09 at 22:46
  • @Eric, when writing a plugin system, the hairiest part I found was versioning, http://stackoverflow.com/questions/974495/net-add-ins-and-versioning – Sam Saffron Jul 01 '09 at 22:48
  • "Still in flux"? Can't be in _too_ much flux, considering VS2010 is using it in the new editor. – John Saunders Jul 02 '09 at 03:48
  • @John I mean MEF is in "MEF Preview 5" mode, its not a production release yet. Some big changes were made in April and I'd imagine more changes will be made to accommodate for VS2010 – Sam Saffron Jul 02 '09 at 08:18
  • necro-comment, but coming back to this problem and reading more about MEF, is would be the way to go. However, I believe @BradBarker was right at the time for my specific issue I was having. – Zack Jul 25 '12 at 22:13
11

It sounds like you have a circular reference. You said your plugins reference Lab.Core.DLL, but you also say the plugins are loaded from Lab.Core.DLL.

Am I misunderstanding what is happening here?

EDIT: OK now that you have added your question to the question...

You need to have Lab.Core.DLL accessible to the plugin being loaded since it is a dependency. Normally that would mean having it in the same directory or in the GAC.

I suspect there are deeper design issues at play here, but this is your immediate problem.

Brad Barker
  • 2,053
  • 3
  • 17
  • 28
  • Thanks for the help. I ended up re-writing it based on a friend's soruce code. Edited original post to include my solution. :) – Zack Jul 01 '09 at 20:35
9

As a side answer, i use these 2 interfaces for implementing that

    public interface IPlugin {
        string Name { get; }
        string Description { get; }
        string Author { get; }
        string Version { get; }

        IPluginHost Host { get; set; }

        void Init();
        void Unload();

        IDictionary<int, string> GetOptions();
        void ExecuteOption(int option);
}

    public interface IPluginHost {
        IDictionary<string, object> Variables { get; }
        void Register(IPlugin plugin);
     }
Cole Tobin
  • 9,206
  • 15
  • 49
  • 74
Jhonny D. Cano -Leftware-
  • 17,663
  • 14
  • 81
  • 103