8

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

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

  • 做一点事()
  • 做其他事情()
  • 错误
  • 成功

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

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

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

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

问题是:

  1. 我的应用不知道“Plugin1”类型(它是一个插件,我的应用不直接引用它)。
  2. 这样做会在时间之前实例化插件,这是我不想做的。

在“传统”插件模型中,应用程序(插件的“客户端”)在某些关键点加载并执行插件代码。例如——图像处理应用程序,当执行某个操作时)。

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

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

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

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

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

  • 延迟加载(或提供延迟/急切加载)插件 DLL。
  • 允许插件控制它们连接到系统/应用程序的各个部分。
4

4 回答 4

5

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

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

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

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

等等。核心思想是汇编内容总是被懒惰地读取,只有在需要的时候。这种机制对于插件来说并没有什么不同,它们就像您解决方案中的常规程序集一样工作。唯一的区别是顺序略有不同。您首先加载程序集,然后立即调用构造函数。与在代码中调用类型的构造函数相反,CLR 然后立即加载程序集。它需要同样长的时间。

于 2012-04-25T00:05:58.903 回答
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
}

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

于 2012-04-24T22:15:51.770 回答
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 的这篇文章,它涵盖了开发可插拔应用程序的许多问题。这是我们用作我们自己的“插件管理器”的基础。

我个人会避免按需加载程序集。IMO 与运行应用程序的用户必须等待加载必要的插件相比,启动时间稍长一些(甚至在 Web 应用程序上的问题更小)更好。

于 2012-04-24T22:32:56.963 回答
1

托管可扩展性框架是否适合您的需求?

于 2012-04-24T22:52:58.343 回答