我想提供一些在软件中创建可动态加载的插件的方法。
典型的方法是使用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"); |