关于Windows:如何在.NET中加载插件?

关于Windows:如何在.NET中加载插件?

How to load plugins in .NET?

我想提供一些在软件中创建可动态加载的插件的方法。
典型的方法是使用LoadLibrary WinAPI函数加载dll,然后调用GetProcAddress获取指向该dll中函数的指针。

我的问题是如何在C#/。Net应用程序中动态加载插件?


从.NET 3.5开始,有一种形式化的嵌入式方法可以从.NET应用程序创建和加载插件。全部在System.AddIn命名空间中。有关更多信息,您可以在MSDN上查看本文:加载项和可扩展性


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
        }
    }
}

编辑:Matt引用的类在.NET 3.5中可能是更好的选择。


动态加载插件

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

1
2
3
4
5
6
7
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上有关卸载AppDomains的文章。

使用WCF

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

现有的插件框架

我知道两个插件框架:

  • Mono.Add-ins-如对另一个问题的回答中所述。
  • 托管加载项框架(MAF)-这是Matt在其回答中提到的System.AddIn命名空间。

有人谈论托管扩展框架(MEF)作为插件或加载项框架,但事实并非如此。有关更多信息,请参见此StackOverflow.com问题和此StackOverflow.com问题。


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

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

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

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

卸载应用程序域:

1
AppDomain.Unload(domain);

是的,对Matt和System.AddIn ++(这是有关MSDN的关于System.AddIn的两部分文章,请参见此处和此处)。您可能希望了解的另一种技术,以了解.NET Framework将来的发展方向是Codeplex上当前以CTP形式提供的Managed Extensibility Framework。


基本上,您可以通过两种方式做到这一点。

第一种是导入kernel32.dll并使用之前使用的LoadLibrary和GetProcAddress:

1
2
3
4
5
6
7
[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命名空间和以下方法:

  • Assembly.LoadFile
  • Assembly.GetType
  • Assembly.GetTypes
  • Type.GetMethod
  • MethodInfo.Invoke

首先,通过程序集的路径加载程序集,然后通过其名称从其获取类型(类),然后再次通过其名称获取该类的方法,最后使用相关参数调用该方法。


本文较旧,但仍适用于在应用程序中创建可扩展性层:

允许用户使用宏和插件向您的.NET应用程序添加功能


这是我的实现,受此代码启发,避免迭代所有程序集和所有类型(或至少使用linQ进行过滤)。我只是加载该库,然后尝试加载实现一个通用共享接口的类。简单快速:)

只需在一个单独的库中声明一个接口,然后在您的系统和插件中都引用它:

1
2
3
4
public interface IYourInterface
{
    Task YourMethod();
}

在您的插件库中,声明一个实现IYourInterface的类

1
2
3
4
5
6
7
public class YourClass: IYourInterface
{
    async Task IYourInterface.YourMethod()
    {
        //.....
    }
}

在您的系统中,声明此方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
    }
}

并这样称呼它:

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

推荐阅读