我想提供一些在我的软件中创建动态可加载插件的方法。 执行此操作的典型方法是使用LoadLibrary WinAPI函数加载dll并调用GetProcAddress以获取指向该dll内函数的指针。
我的问题是如何在C#/ .Net应用程序中动态加载插件?
答案 0 :(得分:28)
从.NET 3.5开始,有一种从.NET应用程序创建和加载插件的形式化,烘焙方式。它全部在System.AddIn命名空间中。有关详细信息,请查看MSDN上的这篇文章:Add-ins and Extensibility
答案 1 :(得分:20)
以下代码片段(C#)构造从应用程序路径中的类库(* .dll)中找到的Base
派生的任何具体类的实例,并将它们存储在列表中。
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中更好的选择。
答案 2 :(得分:8)
有关如何动态加载.NET程序集的信息,请参阅this question(和my answer)。以下是一些用于加载创建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
的内容。有关详细信息,请参阅this article on MSDN on Unloading AppDomains。
有一个stack overflow question and answer描述了如何使用Windows Communication Framework(WCF)来创建插件框架。
我知道两个插件框架:
System.AddIn
命名空间mentioned by Matt in his answer。有些人将Managed Extensibility Framework (MEF)称为插件或插件框架,但并非如此。有关详细信息,请参阅this StackOverflow.com question和this StackOverflow.com question。
答案 3 :(得分:5)
一个提示是将所有插件加载到自己的AppDomain中,因为运行的代码可能具有潜在恶意。自己的AppDomain也可用于“过滤”您不想加载的程序集和类型。
AppDomain domain = AppDomain.CreateDomain("tempDomain");
将程序集加载到应用程序域:
AssemblyName assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
Assembly assembly = domain.Load(assemblyName);
要卸载应用程序域:
AppDomain.Unload(domain);
答案 4 :(得分:4)
是的,++ to Matt和System.AddIn(关于System.AddIn的两部分MSDN杂志文章可用here和here)。您可能希望了解另一种技术,以了解.NET Framework未来的发展方向,目前在Codeplex上以CTP格式提供Managed Extensibility Framework。
答案 5 :(得分: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命名空间和以下方法:
首先按照它的路径加载程序集,然后通过它的名称从中获取类型(类),然后再次通过它的名称获取类的方法,最后使用相关参数调用该方法。
答案 6 :(得分:2)
该文章有点陈旧,但仍适用于在您的应用程序中创建可扩展性层:
Let Users Add Functionality to Your .NET Applications with Macros and Plug-Ins
答案 7 :(得分: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");