一个灵活的插件系统
用于加载和管理插件的通用插件系统
引言
随着应用程序的增长,它们往往越来越难以维护,尽管大多数应用程序从一开始就被设计得或多或少地良好。这通常是因为当您需要向应用程序添加新功能时,很容易增加耦合。插件是一种通过在小型可测试的 DLL 中添加新功能来解决此问题的方法。
这个例子没有包含很多描述所有内容的内容,相反,我只会向您展示如何使用插件系统。如果您有任何想法,请在文章中发表评论或访问 Codeplex 的讨论区。
插件系统
插件系统由三部分组成。
应用程序
应用程序创建 PluginManager 的一个实例,该实例用于查找和加载插件。通过指定一个包含目录信息和文件通配符的搜索字符串来查找插件。您可以指定每个程序集应加载的安全设置(证据),如果您为您的插件提供不同的访问级别,这非常完美。
应用程序接口
第二个程序集包含应用程序向插件公开的接口。此程序集存在,因为您可能不想将整个应用程序发送给第三方插件开发人员。
插件
最后,我们得到了实际的插件程序集。插件可以将自己标记为依赖于其他插件。例如,假设您有一个新闻通讯插件,该插件生成并发送新闻通讯。新闻通讯插件需要一个在另一个插件中指定的邮件发送器。因此,新闻通讯插件可以将其自身标记为依赖于邮件发送器插件,因此邮件发送器将始终在新闻通讯插件之前加载。
示例
在本文中,我将演示如何创建一个简单的插件。
首先,我们需要指定插件应该能够做什么,并定义应用程序的哪些部分可以从插件访问。所有这些都添加到一个将被发送给插件开发人员的类库中。
应用程序共享程序集
在此示例中,我们的应用程序希望让插件能够注册组件并将菜单项添加到主应用程序菜单中。通过让插件注册组件,我们得到了一个相当灵活的解决方案,因为插件可以使用其他插件注册的组件。
public interface IMyApplication : IApplication
{
void AddMenuItem(string name, MenuItemClickedHandler clickHandler);
void RegisterComponent<T>(T component) where T : class;
T GetComponent<T>() where T : class;
}
public delegate void MenuItemClickedHandler(string name);
以及插件应该实现的接口
public interface IMyPlugin : IPlugin
{
void SayHelloTo(string name);
}
我们对插件没有很高的期望,它们只需要向我们告诉它们的任何人问好即可。
插件接口
插件有一个它将注册的组件,从而向应用程序添加功能,这些功能可以由应用程序本身或其他插件使用。
public interface IWorldPeace
{
void Initiate();
}
插件
插件包含两部分:首先是实现插件接口的类,然后是实现插件引入的 IWordPeace
的类。
public class MyPlugin : IMyPlugin
{
private const string _pluginName = "MyPlugin";
private readonly IPluginInfo _pluginInfo = new MyPluginInfo();
private readonly IEnumerable<string> _dependencies = new List<string>();
/// <summary>
/// This name is used to determine dependencies, should always be in English.
/// Should not be confused with the human friendly name in
/// <see cref="IPlugin.PluginInfo"/>.
/// </summary>
public string PluginName
{
get { return _pluginName; }
}
/// <summary>
/// Information about the plugin.
/// </summary>
public IPluginInfo PluginInfo
{
get { return _pluginInfo; }
}
/// <summary>
/// Other plugins that this one depends on.
/// The list should contain <see cref="PluginName"/>s.
/// </summary>
/// <value>Is never null</value>
public IEnumerable<string> Dependencies
{
get { return _dependencies; }
}
/// <summary>
/// Start the plugin
/// </summary>
/// <param name="application">Application interface exposed
/// towards the plugins.</param>
public void Start(IApplication application)
{
IMyApplication myApplication = (IMyApplication) application;
myApplication.RegisterComponent<IWorldPeace>(new WorldPeaceThroughLove());
myApplication.AddMenuItem("Click me", OnClick);
}
/// <summary>
/// Invoked when the user clicks on our menu item
/// </summary>
/// <param name="name"></param>
private void OnClick(string name)
{
Console.WriteLine("Omg! You clicked me!");
}
/// <summary>
/// Function that must be implemented
/// </summary>
/// <param name="name"></param>
public void SayHelloTo(string name)
{
Console.WriteLine("Hello " + name);
}
}
最后是世界和平的实现
public class WorldPeaceThroughLove : IWorldPeace
{
public void Initiate()
{
Console.WriteLine("Love is all around!");
}
}
应用程序
在这个例子中,我们使用一个控制台应用程序,但它也可以是一个 Winforms 或 Web 应用程序。
public class Program : IApplication
{
private PluginManager<imyplugin> _pluginManager;
private readonly IDictionary<type,> _components = new Dictionary<type,>();
[STAThread]
public static void Main()
{
new Program().Start();
}
public void AddMenuItem(string name, MenuItemClickedHandler clickHandler)
{
// here we should add the menu item to a real application
}
public void RegisterComponent>T<(T component) where T : class
{
_components.Add(typeof(T), component);
}
public T GetComponent>T<() where T : class
{
return (T)_components[typeof(T)];
}
private void Start()
{
// Create a plugin manager and load it
_pluginManager = new PluginManager<IMyPlugin>(this);
// plugins are in the same folder as the application and
// their file names end with "Plugin.dll"
_pluginManager.PluginPath = "*Plugin.dll";
_pluginManager.Start();
// Call a method defined in the plugin
_pluginManager["MyPlugin"].SayHelloTo( "everyone!");
// Access a registered component.
IWorldPeace worldPeace = GetComponent<IWorldPeace><iworldpeace>();
worldPeace.Initiate();
}
}
更新:卸载插件
我收到了一些关于如何卸载插件的问题。我在插件系统中没有对此的支持,我将尝试解释原因。
首先,无法从 appdomain(应用程序)中卸载程序集(DLL)。永远。
解决方案:Appdomains 和 Remoting
尽管有一种解决方法:为所有插件使用一个辅助 appdomain,这意味着插件位于可以被视为外部应用程序的内容中(每个 appdomain 都有隔离的内存,并且辅助 appdomain 中的未处理异常不会影响主 appdomain),这意味着您需要使用 remoting 才能在插件和主应用程序之间进行通信。
将所有插件放在辅助 appdomain 中意味着您无法卸载单个插件,您必须卸载所有插件。但是,您可以为每个插件使用一个 appdomain,对吧?当然;如果您可以忍受性能下降。
另一个问题
假设您有一个插件,它向应用程序的其余部分提供一个包含联系人的通讯录。您真的想卸载它吗?假设应用程序(或任何其他插件)包含对您的某些联系人的引用,如果卸载该插件,它们会发生什么?
卸载东西比您想象的要复杂得多。
结束语
就这样。如果您有任何不理解的地方,请告诉我。
请随意查看我的其他项目:C# Webserver 和 TinyDAL。
历史
- 2008 年 8 月 30 日:初始帖子