允许C#插件在应用程序挂钩上注册

时间:2012-04-24 22:11:54

标签: c# .net plugins architecture

我正在构建一个基于.NET的应用程序,并希望允许更具可扩展性和可插拔性的设计。

为简单起见,应用程序公开了一组操作和事件:

  • DoSomething()
  • DoOtherThing()
  • 的OnError
  • 的onSuccess

我想提供“插件”来加载并挂钩其中一些操作(类似于:当Event1触发时,运行plugin1)。

例如 - 当 OnError 事件触发时,运行 plugin1.HandleError()

这可以通过事件订阅轻松完成:

this.OnError += new Plugin1().HandleError();

问题在于:

  1. 我的应用程序不知道类型“Plugin1”(它是一个插件,我的应用程序 不直接引用它)。
  2. 这样做会在时间之前实例化插件,我不这样做 想做。
  3. 在“传统”插件模型中,应用程序(插件的“客户端”)在某些关键点加载并执行插件代码。例如 - 执行特定操作时的图像处理应用程序。)

    客户端应用程序知道何时实例化插件代码以及何时执行它。

    在我的应用程序中,插件本身是决定它应该执行的时间(“插件应该在OnError事件上注册”)。

    将插件“执行”代码与“注册”代码一起保存会导致插件DLL在注册时被加载到内存中的问题,这是我希望阻止的。

    例如,如果我在插件DLL中添加Register()方法,则必须将插件DLL加载到内存中才能调用Register方法。

    对于这个特定问题,什么是一个好的设计解决方案?

    • 懒惰加载(或提供延迟/急切加载)插件DLL。
    • 允许插件控制他们挂钩的系统/应用程序的各个部分。

4 个答案:

答案 0 :(得分:5)

您正在尝试解决不存在的问题。代码调用Assembly.LoadFrom()时加载的所有类型的心理图像都是错误的。 .NET框架充分利用Windows作为需求分页的虚拟内存操作系统。然后是一些。

当您调用LoadFrom()时,球会滚动。这使得CLR创建了一个内存映射文件,这是一个核心操作系统抽象。它会更新一些内部状态以跟踪现在驻留在AppDomain中的程序集,它非常小。 MMF设置内存映射,创建映射文件内容的虚拟内存页。只是处理器TLB中的一个小描述符。实际上没有从汇编文件中读取任何内容。

接下来,您将使用反射来尝试发现实现接口的类型。这导致CLR从程序集中读取一些程序集元数据。此时,页面错误导致处理器将覆盖程序集元数据部分的某些页面的内容映射到RAM。少数千字节,如果组件包含很多类型,可能会更多。

接下来,即时编译器开始生成为构造函数生成代码的操作。这会导致处理器将包含构造函数IL的页面置入RAM。

等等。核心思想是,只有当需要时,才能懒惰地阅读汇编内容。对于插件,此机制不同,它们的工作方式与解决方案中的常规程序集类似。唯一的区别是订单略有不同。首先加载程序集,然后立即调用构造函数。与在代码和CLR中调用类型的构造函数相反,然后立即加载程序集。这需要很长时间。

答案 1 :(得分:4)

您需要做的是找到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
}

当然,从这里你可以自由地做任何你想做的事,使用方法,订阅事件等。

答案 2 :(得分:2)

对于加载插件程序集,我倾向于依靠我的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的this post,它涵盖了开发可插拔应用程序的许多问题。这就是我们用作自己的“插件管理器”的基础。

就个人而言,我会避免按需加载程序集。 IMO最好比运行应用程序的用户必须等待加载必要的插件时启动时间稍长(在Web应用程序上的问题更少)。

答案 3 :(得分:1)

Managed Extensibility Framework会满足您的需求吗?