玄铁剑

成功的途径:抄,创造,研究,发明...
posts - 128, comments - 42, trackbacks - 0, articles - 174

.net dll plug

Posted on 2007-07-23 09:04 玄铁剑 阅读(340) 评论(0)  编辑 收藏 引用 所属分类: C# Winform
标题:Create a Plug-In FrameWork
作者:Roy Osherove
译者:easyjoy
出处:MSDN Online,链接为
http://msdn.microsoft.com/asp.net/archive/default.aspx?pull=/library/en-us/dnaspp/html/pluginframework.asp
摘要:介绍如何为.Net应用程序添加插件支持,并提供一个框架例子。

代码下载:链接为
http://download.microsoft.com/download/7/2/e/72e64769-58d2-4a91-9686-a1a160bb1bc8/CreatePluginFramework.msi

目录
应用程序为什么需要插件框架
第一步:创建简单文本编辑器
第二步:创建Plug-In SDK
第三步:创建自己定制的插件
第四步:让应用程序找到新插件
第五步:用IConfigurationSectionHandler解析配置文件
实例化并调用插件
调用插件
总结

应用程序为什么需要插件框架
出于以下原因,人们通常为应用程序添加插件支持:
(1)在不需要重新编译和发布程序的情况下扩展功能;
(2)在不访问源代码的情况下添加新功能;
(3)应用程序业务规则变化频繁,或者经常有新的规则要加进来;

本文将构造一个简单的文本编辑器,该编辑器只有一个窗口。该程序唯一的功能就是在窗体中间的编辑框中显示一行文本。这个程序编好后,我们开始为它创建一个简单插件;这个插件读出编辑框中的文本,解析出其中的有效的EMail地址,然后返回该EMail。

如上所述,,这个例子中我们面临如下未知因素:
(1)应用程序本身如何找到插件;
(2)插件如何知道文本编辑框中的内容;
(3)如何激活插件;

第一步:创建简单文本编辑器
详细操作此处省略。下载的源代码中已经包含它。从此处开始,我假设你已经创建了这个简单程序。

第二步:创建Plug-In SDK
现在已经构造了程序,如何让应用程序与外边插件交流呢?怎样让它们交流呢?

解决方法是应用程序发布一个接口,所有的插件都要实现接口中的公共成员和方法。此处假设这个接口是IPlugin,所有想开发插件的开发人员都必须实现这个接口。这个接口位于共享库(shared library)中,应用程序和插件都用到它。下面是接口的定义:

public interface IPlugin
{
   string Name{get;}
   void PerformAction(IPluginContext context);
}

这段代码很容易懂,但是为什么要给PerformAction一个IPluginContext接口呢?原因在于这相对于直接把字符串当作参数进行传递,传递接口可以有更多的灵活性。目前,IPluginContext接口非常简单:

public interface IPluginContext
{
   string CurrentDocumentText{get;set;}
}

现在所要做的就是在若干对象中实现接口,并把对象传递给插件,并获取结果。这使得将来可以不仅修改文本框,还可以修改任何对象。

第三步:创建自己定制的插件
现在要做的是
(1)创建一个单独的类库对象;
(2)创建一个类实现接口IPlugin;
(3)编译并放到主程序同一目录下;

public class EmailPlugin:IPlugin
{
   public EmailPlugin()
   {
   }
   // The single point of entry to our plugin
   // Acepts an IPluginContext object
// which holds the current
   // context of the running editor.
   // It then parses the text found inside the editor
   // and changes it to reflect any
// email addresses that are found.
   public void PerformAction(IPluginContext context)
   {
      context.CurrentDocumentText=
             ParseEmails(context.CurrentDocumentText);
   }

   // The name of the plugin as it will appear
   // under the editor's "Plugins" menu
   public string Name
   {
      get
      {
         return "Email Parsing Plugin";
      }
   }

   // Parse the given string for any emails using the Regex Class
   // and return a string containing only email addresses
   private string ParseEmails(string text)
   {
      const string emailPattern= @"\w+@\w+\.\w+((\.\w+)*)?";
      MatchCollection emails =
               Regex.Matches(text,emailPattern,
               RegexOptions.IgnoreCase);
      StringBuilder emailString = new StringBuilder();
      foreach(Match email in emails)
      {
         emailString.Append(email.Value + Environment.NewLine);
      }

      return emailString.ToString();
   }
}

第四步:让应用程序找到新插件
编译好插件后,如何让应用程序知道呢?解决办法很简单:
(1)创建应用程序配置文件;
(2)在配置文件中创建一项来列出所有插件;
(3)创建解析器来解析该项;

要完成(1),只需向主程序添加一个XML文件。

【Tip】给该文件取名为App.Config,这样,每次编译程序的时候,Visual Studio.Net会自动把该文件复制到目标文件夹,并改名为<YourApp>.Config。

现在插件编写者可以很容易的往配置文件中添加一下。下面是这个配置文件的样子:

<configuration>
<configSections>
      <section name="plugins"
            type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
            />
</configSections>
   <plugins>
      <plugin type="Royo.Plugins.Custom.EmailPlugin, CustomPlugin" />
   </plugins>
</configuration>

注意到configSections标签,该标签说明应用程序配置中有一个plugins项,而且有一个解析器专门解析plugins项。该解析器在类Royo.PluggableApp.PluginSectionHandler中,装配件的名字是PluggableApp。下一步中将展示这个类的代码。

接着,配置文件中有plugins标签,这列出所有的插件,包括类名和装配件名。

配置文件写好之后,就已经完成一半任务。剩下的是应用程序要读配置信息,并实例化插件。

第五步:用IConfigurationSectionHandler解析配置文件
为了解析出配置文件中的插件信息,框架提供了一个很简单的机制,使得你可以注册一个类作为配置文件中某个特定部分的处理器。对于配置文件中的某个部分,如果框架不能解析,那么就需要一个专门的处理器;否则应用程序会抛出ConfigurationException。

为了提供解析plug-ins 项的类,需要实现下面这个接口,这个接口很简单,代码如下:

//System.Configuration.IConfigurationSectionHandler,
public interface IConfigurationSectionHandler
{
   public object Create(object parent, object configContext, System.Xml.XmlNode section);
}

我们所要做的就是覆写Create方法,在这个方法中解析XML节点。在这里,要解析的节点是“Plugins”节点。这个类还需要有一个缺省的构造函数,这样框架可以动态实例化它。类的代码如下:

public class PluginSectionHandler:IConfigurationSectionHandler
{
   public PluginSectionHandler()
   {
   }
   // Iterate through all the child nodes
   //   of the XMLNode that was passed in and create instances
   //   of the specified Types by reading the attribite values of the nodes
   //   we use a try/Catch here because some of the nodes
   //   might contain an invalid reference to a plugin type
   public object Create(object parent,
         object configContext,
         System.Xml.XmlNode section)
   {
      PluginCollection plugins = new PluginCollection();
      foreach(XmlNode node in section.ChildNodes)
      {
         //Code goes here to instantiate
         //and invoke the plugins
         .
         .
         .
      }
      return plugins;
   }
}

如前面提到的,你提供框架需要的信息来处理configSection标签中的plug-ins项,该项在时间的plug-ins标签之前。
<configuration>
<configSections>
   <section name="plugins"
      type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
   />
</configSections>
...

看看上面是如何指定类的。对于的字符串有两部分组成,一个是类的全名(包括名字空间),逗号,所在的装配件。这就是框架用来实例化一个类所需要的信息,也是实例化一个插件所需要的信息。

实例化并调用插件
OK,根据下面这段代码,我们该如何实例化插件呢?
String ClassName = "Royo.Plugins.MyCustomPlugin, MyCustomPlugin"
IPlugin plugin =  (IPlugin )Activator.CreateInstance(Type.GetType(ClassName));

这里发生了哪些事情呢:由于应用程序没有直接引用插件所在的装配件,所以必须要用System.Activator类。Activator是一个特殊的类,用来根据任意数量的参数来产生某个对象的实例。如果用过ASP或者Visual Basic,也许你记得用过CreateObject()函数,根据某个类的CLSID来实例化并返回某个对象。Activator也是同样的思路,只不过是用不同的参数,返回的是一个System.Object对象,而不是一个variant。

调用Activator的时候,传入的参数是一个类型(Type)。用Type.GetType()方法返回符合插件类型的类型的实例。注意Type.GetType()以plug-ins中的字符串作为参数,这个参数描述了所需要的类和装配件。

实例化插件后,要强制转换成IPlugin。这里需要一个Try-Catch块,这是为了确保类的确实现了IPlugin接口。获取接口实例后就保存下来,然后继续下一个XML节点。代码如下:

public object Create(object parent,
    object configContext,
    System.Xml.XmlNode section)
{
   //Derived from CollectionBase
   PluginCollection plugins = new PluginCollection();
   foreach(XmlNode node in section.ChildNodes)
   {
      try
      {
         //Use the Activator class's 'CreateInstance' method
         //to try and create an instance of the plugin by
         //passing in the type name specified in the attribute value
         object plugObject =
                   Activator.CreateInstance(Type.GetType(node.Attributes["type"].Value));

         //Cast this to an IPlugin interface and add to the collection
         IPlugin plugin = (IPlugin)plugObject;
         plugins.Add(plugin);
      }
      catch(Exception e)
      {
         //Catch any exceptions
         //but continue iterating for more plugins
      }
   }
   return plugins;
}

调用插件
现在可以调用插件了。别忘了要传入一个IPluginContext参数。该参数包含插件正常工作所需要的信息。因此需要一个类实现该接口。代码如下:
public interface IPluginContext
{
   string CurrentDocumentText{get;set;}
}

public class EditorContext:IPluginContext
{
   private string m_CurrentText= string.Empty;
   public EditorContext(string CurrentEditorText)
   {
      m_CurrentText = CurrentEditorText;
   }

   public string CurrentDocumentText
   {
get{return m_CurrentText;}
      set{m_CurrentText = value;}
   }
}

下面就可以调用插件了:
private void ExecutePlugin(IPlugin plugin)
{
   //create a context object to pass to the plugin
   EditorContext context = new EditorContext(txtText.Text);
  
   //The plugin Changes the Text property of the context
   plugin.PerformAction(context);
   txtText.Text= context.CurrentDocumentText;
   }
}



只有注册用户登录后才能发表评论。