允许 C# 插件在应用程序挂钩上注册
本文关键字:注册 应用程序 插件 允许 | 更新日期: 2023-09-27 18:32:05
我正在构建一个基于 .NET 的应用程序,并希望允许一个更具可扩展性和可插拔性的设计。
为简单起见,应用程序公开一组操作和事件:
- 做某事()
- 做其他事情()
- 错误
- 成功
我想提供"插件"来加载并钩接到其中一些操作中(例如:当 Event1 触发时,运行 plugin1)。
例如 -- 运行插件 1。HandleError() 当 OnError 事件触发时。
这可以通过事件订阅轻松完成:
this.OnError += new Plugin1().HandleError();
问题是:
- 我的应用程序不知道"Plugin1"类型(它是一个插件,我的应用程序不直接引用它)。
- 这样做会在时间之前实例化插件,但我不会想做。
在"传统"插件模型中,应用程序(插件的"客户端")在某些关键点加载并执行插件代码。例如 -- 执行特定操作时的图像处理应用程序)。
客户端应用程序知道何时实例化插件代码以及何时执行插件代码的控制。
在我的应用程序中,插件本身是决定何时执行("插件应该在 OnError 事件上注册")。
将插件"执行"代码与"注册"代码一起保留会带来一个问题,即插件 DLL 将在注册时加载到内存中,这是我希望防止的。
例如,如果我在插件 DLL 中添加一个 Register() 方法,插件 DLL 必须加载到内存中才能调用 Register 方法。
对于这个特定问题,什么可能是一个好的设计解决方案?
- 延迟加载(或提供延迟/预先加载)插件 DLL。
- 允许插件控制它们连接到系统/应用程序的哪些不同部分。
您正在尝试解决一个不存在的问题。 当你的代码调用Assembly.LoadFrom()时,加载所有类型的心理图像是错误的。 .NET 框架充分利用了 Windows 作为按需分页虚拟内存操作系统的优势。 然后是一些。
当你调用 LoadFrom() 时,球会滚动。 使 CLR 创建内存映射文件,即核心操作系统抽象。 它更新了一些内部状态,以跟踪现在驻留在 AppDomain 中的程序集,这是非常小的。 MMF 设置内存映射,创建映射文件内容的虚拟内存页。 只是处理器 TLB 中的一个小描述符。 实际上不会从程序集文件中读取任何内容。
接下来,您将使用反射来尝试发现实现接口的类型。 这会导致 CLR 从程序集读取某些程序集元数据。 此时,页面错误会导致处理器将涵盖程序集元数据部分的某些页面的内容映射到 RAM。 几千字节,如果程序集包含很多类型,则可能更多。
接下来,实时编译器立即采取行动,为构造函数生成代码。 这会导致处理器将包含构造函数 IL 的页面错误到 RAM 中。
诸如此类。 核心思想是,程序集内容始终在需要时被懒惰地读取。 此机制对于插件没有什么不同,它们的工作方式就像解决方案中的常规程序集一样。 唯一的区别是顺序略有不同。 首先加载程序集,然后立即调用构造函数。 而不是在代码中调用类型的构造函数,然后立即加载程序集 CLR。 这需要同样长的时间。
您需要做的是找到 dll 的路径,然后从中创建程序集对象。从那里,你需要获取你想要检索的类(例如,任何实现你的接口的类):
var assembly = Assembly.Load(AssemblyName.GetAssemblyName(fileFullName));
foreach (Type t in assembly.GetTypes())
{
if (!typeof(IMyPluginInterface).IsAssignableFrom(t)) continue;
var instance = Activator.CreateInstance(t) as IMyPluginInterface;
//Voila, you have lazy loaded the plugin dll and hooked the plugin class to your code
}
当然,从这里您可以自由地做任何您想做的事情,使用方法,订阅事件等。
对于加载插件程序集,我倾向于依靠我的 IoC 容器从目录中加载程序集(我使用 StructureMap),尽管您可以按照@Oskar的答案手动加载它们。
如果你想支持在应用程序运行时加载插件,StructureMap 可以"重新配置",从而选择任何新插件。
对于应用程序挂钩,可以将事件调度到事件总线。下面的示例使用 StructureMap 查找所有已注册的事件处理程序,但您可以使用普通的旧反射或其他 IoC 容器:
public interface IEvent { }
public interface IHandle<TEvent> where TEvent : IEvent {
void Handle(TEvent e);
}
public static class EventBus {
public static void RaiseEvent(TEvent e) where TEvent : IEvent {
foreach (var handler in ObjectFactory.GetAllInstances<IHandle<TEvent>>())
handler.Handle(e);
}
}
然后,您可以引发如下事件:
public class Foo {
public Foo() {
EventBus.RaiseEvent(new FooCreatedEvent { Created = DateTime.UtcNow });
}
}
public class FooCreatedEvent : IEvent {
public DateTime Created {get;set;}
}
并像这样处理它(例如在您的插件中):
public class FooCreatedEventHandler : IHandle<FooCreatedEvent> {
public void Handle(FooCreatedEvent e) {
Logger.Log("Foo created on " + e.Created.ToString());
}
}
我肯定会推荐Shannon Deminick的这篇文章,它涵盖了开发可插拔应用程序的许多问题。这就是我们用作我们自己的"插件管理器"的基础。
就个人而言,我会避免按需加载程序集。IMO 最好有一个稍长的启动时间(在 Web 应用程序上甚至更少),而不是正在运行的应用程序的用户必须等待加载必要的插件。
托管扩展性框架是否满足您的需求?