在C#项目中动态加载DLL文件 - 如何实现?

15

在我的项目中,我需要使用插件。但是为了在项目中使用这些插件,我需要导入插件的引用。由于我事先不知道项目将使用多少个或哪些插件,因此我希望能够动态地将它们导入我的项目中。

    String path = Application.StartupPath;
    string[] pluginFiles = Directory.GetFiles(path, "*.dll");
    ipi = new IPlugin[pluginFiles.Length];
    Assembly asm;

        for (int i = 0; i < pluginFiles.Length; i++)
        {
            string args = pluginFiles[i].Substring(
                pluginFiles[i].LastIndexOf("\\") + 1,
                pluginFiles[i].IndexOf(".dll") -
                pluginFiles[i].LastIndexOf("\\") - 1);

            asm = Assembly.LoadFile(pluginFiles[i]);
            Type[] types = asm.GetTypes();
在这个代码示例中,我搜索了所有的.dll文件,并将它们放入了一个字符串列表中。但是现在我该如何加载所有这些.dll文件呢?或者是否有一种方式可以在没有真正导入它们的情况下使用这些.dll文件?

2
请查看以下链接:https://dev59.com/k3I-5IYBdhLWcg3wiY7a - kjana83
2
你应该研究一下MEF。请参考:http://msdn.microsoft.com/zh-cn/library/dd460648.aspx - Cole Cameron
3
你正在使用WinForms还是WPF?微软的Prism框架/指南描述了模块化应用程序开发和其他良好实践。它们使用依赖注入容器,如MEF或Unity,并实现DirectoryCatalog,可以在运行时从目录中加载程序集而无需引用它。UI设计原则更为WPF导向(MVVM),但Prism的好处在于它仅是一组指南,你可以使用需要的内容并忽略其余部分。[Microsoft Prism 4: 模块化应用程序开发](http://msdn.microsoft.com/en-us/library/gg405479%28v - Alan
3个回答

23

MEF(托管可扩展性框架)方法:

您需要为使用MEF的导入/导出功能的项目添加对 System.ComponentModel.Composition 的引用。

首先是启动程序/加载器(在我的情况下,我只是将其添加到Main类中)。

Program.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using MEFContract;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var prgm = new Program();

            // Search the "Plugins" subdirectory for assemblies that match the imports.
            var catalog = new DirectoryCatalog("Plugins");
            using (var container = new CompositionContainer(catalog))
            {
                // Match Imports in "prgm" object with corresponding exports in all catalogs in the container
                container.ComposeParts(prgm);
            }

            prgm.DoStuff();

            Console.Read();
        }

        private void DoStuff()
        {
            foreach (var plugin in Plugins)
                plugin.DoPluginStuff();
        }

        [ImportMany] // This is a signal to the MEF framework to load all matching exported assemblies.
        private IEnumerable<IPlugin> Plugins { get; set; }
    }
}

IPlugin接口是导入和导出之间的契约。所有插件都将实现此接口。这个契约非常简单:

IPlugin.cs:

namespace MEFContract
{
    public interface IPlugin
    {
        void DoPluginStuff();
    }
}

最后,您可以在不同的程序集中创建任意数量的插件。这些插件必须实现契约接口,并且还必须用“Export”属性进行修饰,以指示MEF应将它们与任何相应的导入匹配起来。然后将dll放入“Plugins”文件夹中(该文件夹应位于可执行文件所在的同一位置)。以下是一个示例插件:

Plugin.cs

using System;
using System.ComponentModel.Composition;
using MEFContract;

namespace Plugin
{
    [Export(typeof(IPlugin))]
    public class Plugin : IPlugin
    {
        public void DoPluginStuff()
        {
            Console.WriteLine("Doing my thing!");
        }
    }
}

经过漫长的时间浪费和在互联网上的搜索,你的这个解决方案终于也解决了我的问题,非常感谢你的分享。 - Zain Ul Abidin

5
假设为了简单起见,所有的IPlugin实现都有默认构造函数(公共且无参数)。
话虽如此,您确实希望找到所有实现此接口的类型并创建它们的实例。您有一定的正确方向,但使用一点LINQ可以大大简化这个过程:
String path = Application.StartupPath;
string[] pluginFiles = Directory.GetFiles(path, "*.dll");


ipi = (
    // From each file in the files.
    from file in pluginFiles
    // Load the assembly.
    let asm = Assembly.LoadFile(file)
    // For every type in the assembly that is visible outside of
    // the assembly.
    from type in asm.GetExportedTypes()
    // Where the type implements the interface.
    where typeof(IPlugin).IsAssignableFrom(type)
    // Create the instance.
    select (IPlugin) Activator.CreateInstance(type)
// Materialize to an array.
).ToArray();

说实话,你最好使用依赖注入框架;它们通常允许在编译时未引用程序集的情况下进行接口实现的动态加载和绑定。
此外,虽然有点复杂(我个人认为),但你可能需要查看 System.AddIn 命名空间,因为它们是专门为此目的构建的。但是,如果你不必担心合同的版本控制等问题,依赖注入路线通常要容易得多。

不包含该类型的程序集会发生什么?它们会自动卸载吗? - Gilles jr Bisson
@GillesjrBisson 不,一旦程序集被加载到应用程序域中,就无法卸载它。如果你想要卸载一个程序集,你必须卸载它所在的应用程序域,但这可能不可行(因为它正在运行)。如果你将程序集加载到其他应用程序域中,那么你就必须开始跨应用程序域边界进行调用传输,这会变得有点混乱。 - casperOne

1
我有一个应用程序,它不仅可以在运行时加载插件,还可以在用户将它们放入文件夹、取出或删除它们时进行热加载和卸载。因此,当我需要重新编译我的插件时,我不需要重新启动我的应用程序。在我的情况下,所有插件都派生自Plugin抽象类,因此它们很容易在.DLL中找到。
这是我的加载方法:
private static void LoadPlugins(FileInfo file)
{
    try
    {
        Assembly assembly = Assembly.LoadFrom(file.FullName);

        foreach (Type type in assembly.GetTypes())
        {
            if (type.IsSubclassOf(typeof(Plugin)) && type.IsAbstract == false)
            {
                Plugin b = type.InvokeMember(null,
                                            BindingFlags.CreateInstance,
                                            null, null, null) as Plugin;
                plugins.Add(new PluginWrapper(b, file));
                b.Register();
            }
        }
    }
    catch (ReflectionTypeLoadException ex)
    {
        StringBuilder sb = new StringBuilder();
        foreach (Exception exSub in ex.LoaderExceptions)
        {
            sb.AppendLine(exSub.Message);
            if (exSub is FileNotFoundException)
            {
                FileNotFoundException exFileNotFound = exSub as FileNotFoundException;
                if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
                {
                    sb.AppendLine("Fusion Log:");
                    sb.AppendLine(exFileNotFound.FusionLog);
                }
            }
            sb.AppendLine();
        }
        string errorMessage = sb.ToString();
        Log.Error("Plugins Manager", errorMessage);
    }
}

显然,有些人会对一个看起来非常有用的非平凡解决方案进行负投票,给人留下他们比发帖者聪明得多的印象,却没有给出任何提示说明为什么这对他们来说不是有用的。我更喜欢他们的评论和见解,而不是负投票。 - Roland
示例代码不完整,因为它没有展示如所描述的卸载能力。主应用程序需要在不同的应用程序域中才能实现该功能。 - Jonas Jakobsson

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