编写C#插件系统

37
我正在尝试编写一个插件系统,为我的应用程序提供一些可扩展性,以便某人可以为应用程序编写插件而不必触及主应用程序的代码(并冒着破坏某些东西的风险)。
我已经编写了基本的“ IPlugin”接口(目前尚未实现任何内容)。
以下是我的加载方式:
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);
        }
    }
}

我的朋友试图帮助我,但我真的不明白出了什么问题。

插件的文件夹结构如下:

\
\Plugins\

所有插件都引用 [root] 目录下名为 "Lab.Core.dll" 的 .dll 文件。该文件没有出现在插件目录中,因为它已经被加载了重复的引用。

插件系统从 Lab.Core.dll 中加载,我的可执行文件也引用了它。Type “IPlugin” 也位于 Lab.Core.dll 中。正如其名称所示,Lab.Core.dll 是我的应用程序的核心。

编辑:

问题:我为什么会收到这个异常,我该如何修复它?

最终编辑:

好的,我决定在查看一个朋友为 TF2 调节器编写的源代码后重新编写它。

以下是我得到的内容,它有效:

    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;
            }
        }
    }

3
我注意到您并没有实际提出问题,而只是描述了您想要做的事情。您能否用问句的形式表达您的问题? - Eric Lippert
1
抱歉,我在试图解释我正在做的事情时卡住了,但忘记了问题。 :x 我会进行编辑。 - Zack
1
请发布完整的异常信息,包括堆栈跟踪和任何内部异常。发布 ex.ToString() 的结果。 - John Saunders
仅仅因为你现在让它工作了,并不意味着它会一直工作。请参考:https://dev59.com/FUfSa4cB1Zd3GeqPBfES - Sam Saffron
MSDN有一个关于创建插件机制的页面:创建简单的插件机制 - samad montazeri
显示剩余3条评论
3个回答

33
Managed Extensibility Framework (MEF) 是.NET中的一个新库,它能够更好地实现应用程序和组件的重用。使用MEF,.NET应用程序可以从静态编译变为动态组合。如果您正在构建可扩展应用程序、可扩展框架和应用程序扩展,则MEF适合您。

http://www.codeplex.com/MEF

编辑:CodePlex已经关闭,该代码已被移至Github仅用于归档目的: https://github.com/MicrosoftArchive/mef
MEF现在是Microsoft .NET Framework的一部分,主要类型位于System.Composition命名空间下。有两个版本的MEF:
- System.ComponentModel.Composition,它已经随着.NET 4.0及更高版本一起发布。这提供了在Visual Studio中使用的标准扩展模型。可以在此处找到此版本MEF的文档。 - System.Composition是MEF的轻量级版本,它已经针对静态组合场景进行优化,并提供更快的组合。它也是唯一一个可移植类库的MEF版本,可用于电话,存储,桌面和Web应用程序。此版本的MEF可通过NuGet获得,并提供文档此处

1
这应该可以不使用MEF实现。 - Brad Barker
11
当然可以不使用MEF来实现,MEF并没有什么魔法。但是,MEF是由插件架构专家们在多年的时间内设计和实现的。(我在2004年时花了一点时间参与了MEF的初始设计。)你真的想要重新尝试复制这五年由专业架构师投入的工作吗? - Eric Lippert
@RichardOD,我不反对,但我们仍然没有得出这个问题的明确答案。如果没有其他的话,它仍然是一个很好的编程练习。 - Brad Barker
@Eric,MEF仍在变化中,因此人们有些不敢采用它。此外,MEF非常庞大,并且拥有成千上万的功能,其中一些非常简单的系统并不需要。 - Sam Saffron
@Eric,在编写插件系统时,我发现最棘手的部分是版本控制。https://dev59.com/FUfSa4cB1Zd3GeqPBfES - Sam Saffron
显示剩余6条评论

11

听起来你遇到了循环引用的问题。你说你的插件引用了Lab.Core.DLL,但你也说插件是从Lab.Core.DLL中加载的。

我是否误解了这里发生的情况?

编辑:好的,现在你已经把你的问题添加到问题中了...

你需要让被加载的插件能够访问Lab.Core.DLL,因为它是一个依赖项。通常情况下,这意味着将其放置在相同的目录或GAC中。

我怀疑这里存在更深层次的设计问题,但这是你当前面临的问题。


感谢您的帮助。最终我根据朋友的源代码进行了重写。已编辑原帖以包含我的解决方案。 :) - Zack

9
作为一个附加的答案,我使用这两个界面来实现它。
    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);
     }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接