如何在.NET中加载插件?

27
我想提供一种在我的软件中创建动态可加载插件的方式。通常的方法是使用 LoadLibrary WinAPI 函数来加载 DLL 文件,然后调用 GetProcAddress 函数获取该 DLL 中某个函数的指针。
我的问题是如何在 C#/.Net 应用程序中动态加载插件?
8个回答

28

从.NET 3.5开始,有一种正式且内置的方式来创建和从.NET应用程序中加载插件。它都在System.AddIn命名空间中。想了解更多信息,可以查看MSDN上的此文章:Add-ins and Extensibility


20
以下代码片段(C#)构造出从应用程序路径中的类库(*.dll)中找到的任何派生自Base的具体类的实例,并将它们存储在列表中。

下面的代码片段(C#)构造了一个由派生自 Base 的任何具体类组成的实例列表,这些类可以在应用程序路径中的类库(*.dll)中找到。

using System.IO;
using System.Reflection;

List<Base> objects = new List<Base>();
DirectoryInfo dir = new DirectoryInfo(Application.StartupPath);

foreach (FileInfo file in dir.GetFiles("*.dll"))
{
    Assembly assembly = Assembly.LoadFrom(file.FullName);
    foreach (Type type in assembly.GetTypes())
    {
        if (type.IsSubclassOf(typeof(Base)) && type.IsAbstract == false)
        {
            Base b = type.InvokeMember(null,
                                       BindingFlags.CreateInstance,
                                       null, null, null) as Base;
            objects.Add(b);
        }
    }
}

编辑: 在.NET 3.5中,Matt提到的类可能是更好的选择。


8

动态加载插件

关于如何动态加载.NET程序集的信息,请参见这个问题(以及我的答案)。以下是创建一个AppDomain并将程序集加载到其中的代码。

var domain = AppDomain.CreateDomain("NewDomainName");
var pathToDll = @"C:\myDll.dll"; 
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(pathToDll, t.FullName) 
    as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

卸载插件

插件框架的一个典型需求是卸载插件。要卸载动态加载的程序集(例如插件和附加组件),必须卸载包含它们的AppDomain。有关详细信息,请参见MSDN上有关Unloading AppDomains本文

使用WCF

有一个stackoverflow问题和答案描述了如何使用Windows Communication Framework(WCF)创建插件框架。

现有的插件框架

我知道两个插件框架:

有些人将Managed Extensibility Framework (MEF)视为插件或附加组件框架,但它并不是。有关详细信息,请参见此StackOverflow.com问题此StackOverflow.com问题


5

一个提示是将所有插件和类库加载到自己的AppDomain中,因为运行的代码可能是恶意的。自己的AppDomain也可以用于“过滤”你不想加载的程序集和类型。

AppDomain domain = AppDomain.CreateDomain("tempDomain");

要将程序集加载到应用程序域中:

AssemblyName assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
Assembly assembly = domain.Load(assemblyName);

卸载应用程序域:
AppDomain.Unload(domain);

4
是的,Matt和System.AddIn值得推荐(有关System.AddIn的两部分MSDN杂志文章可在此处查看查看)。另一个你可能想要了解的技术是Managed Extensibility Framework,目前以 CTP 形式在 Codeplex 上提供。该技术能让你对 .NET Framework 未来的发展方向有所了解。

3
基本上你可以用两种方式来实现。
第一种是导入kernel32.dll并像以前一样使用LoadLibrary和GetProcAddress:
[DllImport("kernel32.dll")]

internal static extern IntPtr LoadLibrary(String dllname);

[DllImport("kernel32.dll")]

internal static extern IntPtr GetProcAddress(IntPtr hModule, String procname);

第二种方法是按照.NET的方式:使用反射。请查看System.Reflection命名空间和以下方法: 首先,您需要通过路径加载程序集,然后通过名称获取其中的类型(类),再次通过名称获取类的方法,并最终使用相关参数调用该方法。

2

哎呀,链接不幸已经失效了。虽然你包含了标题,这并不算太糟糕,但是我在谷歌上也没能快速找到 :-( - SamB

0
这是我的实现,受到this code的启发,避免迭代所有程序集和所有类型(或至少使用linQ进行过滤)。我只加载库并尝试加载实现共享接口的类。简单而快速 :)
只需在一个独立的库中声明一个接口,并在您的系统和插件中引用它:
public interface IYourInterface
{
    Task YourMethod();
}

在您的插件库中,声明一个实现IYourInterface接口的类。
public class YourClass: IYourInterface
{
    async Task IYourInterface.YourMethod()
    {
        //.....
    }
}

在您的系统中,声明此方法。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;

public abstract class ReflectionTool<TSource> where TSource : class
{
    public static TSource LoadInstanceFromLibrary(string libraryPath)
    {
        TSource pluginclass = null;
        if (!System.IO.File.Exists(libraryPath))
            throw new Exception($"Library '{libraryPath}' not found");
        else
        {
            Assembly.LoadFrom(libraryPath);

            var fileName = System.IO.Path.GetFileName(libraryPath).Replace(".dll", "");
            var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(c => c.FullName.StartsWith(fileName));
            var type = assembly.GetTypes().FirstOrDefault(c => c.GetInterface(typeof(TSource).FullName) != null);

            try
            {
                pluginclass = Activator.CreateInstance(type) as TSource;
            }
            catch (Exception ex)
            {
                LogError("", ex);
                throw;
            }
        }

        return pluginclass;
    }
}

然后像这样调用它:

IYourInterface instance = ReflectionTool<IYourInterface>.LoadInstanceFromLibrary("c:\pathToYourLibrary.dll");

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