学习插件技术原理后的一点认识

2005-04-28 16:43  5803人阅读  评论(4)  收藏  举报

    插件的本质在于不修改程序主体(平台)的情况下对软件功能进行扩展与加强,当插件的接口公开后,任何公司或个人都可以制作自己的插件来解决一些操作上的不便或增加新的功能,也就是实现真正意义上的即插即用软件开发。平台+插件软件结构是将一个待开发的目标软件分为两部分,一部分为程序的主体或主框架,可定义为平台,另一部分为功能扩展或补充模块,可定义为插件。

    在进行软件开发之前,是否采用平台+插件结构进行软件开发,还要依据具体的软件需求情况进行确定,但一般来讲,使用平台+插件结构进行软件设计会给所开发软件增加新的生命力。当确定平台+插件的软件结构之后,就要分析哪些部分功能由主体完成,即平台的基本功能,哪些部分功能由插件完成,即需要扩展的插件功能。平台所完成的功能应为一个软件系统的核心和基础,这些基本功能即可为用户使用,也可为插件使用,就是又可以把平台基本功能分为两个部分,内核功能和插件处理功能。平台的内核功能是整个软件的重要功能,一个软件的大部分功能因由内核功能完成。平台的插件处理功能用于扩展平台和管理插件,为插件操纵平台和与插件通信提供标准平台扩展接口。插件所完成的功能是对平台功能的扩展与补充,一般插件完成系列化功能,例如:PHOTOSHOP的滤镜插件完成对图形的特殊效果处理,这些功能都有一些共性,可以进行集中管理,并且是可以定义出标准的插件接口。

 

    为了实现平台+插件结构的软件设计需要定义两个标准接口,一个为由平台所实现的平台扩展接口,一个为插件所实现的插件接口。这里需要说明的是:平台扩展接口完全由平台实现,插件只是调用和使用,插件接口完全由插件实现,平台也只是调用和使用。平台扩展接口实现插件向平台方向的单向通信,插件通过平台扩展接口可获取主框架的各种资源和数据,可包括各种系统句柄,程序内部数据以及内存分配等。插件接口为平台向插件方向的单向通信,平台通过插件接口调用插件所实现的功能,读取插件处理数据等。

 

    平台插件处理功能包括插件注册、管理和调用,以及平台扩展接口的功能实现。插件注册为按照某种机制首先在系统中搜索已安装插件,之后将搜索到的插件注册到平台上,并在平台上生成相应的调用机制,这包括菜单选项、工具栏、内部调用等。插件管理完成插件与平台的协调,为各插件在平台上生成管理信息以及进行插件的状态跟踪。插件调用为调用各插件所实现的功能。平台插件处理功能实现的另一部分功能为平台扩展接口的具体实现。

 

插件软件设计步骤包括:

1、 确定平台基本功能和插件要完成系列化功能或扩展功能;

2、 定义平台扩展接口和插件接口;

3、 完成平台设计,主要是平台插件处理功能;

4、 向插件开发者提供主平台程序(执行代码),公布平台扩展接口和插件要实现的接口,可能包括开发用的SDK;

5、 插件开发者按要求开发插件,实现插件接口,开发者可使用提供的主平台程序测试插件;

6、 主平台设计者继续完成主平台的内核功能,并可随时公布新增加主平台扩展接口和插件接口;

7、 实现4-6步骤的良性循环,整个软件系统不断向前进化。

 

平台+插件软件设计的优点:

1、 实现真正意义上的软件组件的即插即用

2、 在二进制级上集成软件,减少大量的软件重新编译与发布麻烦与时间;

3、 能够很好实现软件模块的分工开发,能够大量吸取他人的优长;

4、 可较好实现代码隐藏,保护知识产权。

 

插件接口的认识

 

    开发支持插件功能的应用程序必须解决一个问题:如何在主程序与插件间正确地互相通信。为了在主程序与插件之间能正确地互相通信,应该先制定一套通信标准,这套通信标准就是接口,主程序与插件只能通过制订好的接口进行通信。软件开发中,接口只是定义功能并规定调用功能的形式,而不包含功能的实现。接口实质上是软件模块的调用规范。

 

    就开发支持插件功能的应用程序而言,一般来说由主程序的开发者来制订接口,如果希望其他的开发人员能开发相关的插件,只要公开相关接口即可。接口功能一般由插件方实现。因为插件的实现也要调用主程序的功能,所以接口功能也可能由主程序来实现。也就是说,主程序与插件的信息流可能是双向的。

  

    接口的调用规范与功能实现互相分离有一个很大的优点:尽管不同的插件开发者对同一个接口的具体实现不同,但是在主程序中对这些插件的调用方式是一样的。如果有主程序实现的接口,在不同的插件中也可以用相同的使用方式调用主程序的功能。这极大的提高了应用程序的灵活性。

 

程序结构及其运行机制

 

    主程序中,插件管理部分用于管理插件的安装和删除,并将所有安装插件的信息保存到适合的地方,例如保存到注册表或配置文件中。主程序启动时,根据插件的配置信息家在插件模块,然后获得插件的输出函数或输出类的指针并加以保存,如果需要的话,可以向主程序增加界面接口元素,如菜单、工具条按钮等。在主程序中当点击与插件相关联的接口元素时,就会触发插件调用函数,在插件调用函数中使用主函数种所保存的插件信息调用插件中实现的功能。在调用插件输出函数时也可以把主程序中实现的接口传递给插件方。

 

插件原理

分类:  C/C++  其他2006-04-16 10:27  4371人阅读  评论(1)  收藏  举报

                                                                      插件原理             作者:周顺利

       插件作为软件的一种扩充方式,十分的方便.做的最好的应该是Winamp的皮肤插件了,通过插件软件可以扩充自己,而扩充的部分不需要自己针对每种情况都编写代码.只要软件提供一定的接口.然后针对具体插件的代码的编写工作就交给了插件制作人员.如果你的插件提供一个公开的接口的话,那么任何一个开发人员都可以针对你的插件编写它自己的插件.下面就研究一下插件的原理.

.插件的结构.

          插件的基本结构可以用类图描述如下:

             //不好意思Word里边编辑的图片竟然在这里不能显示.                                                       

<<接口>>

IPlugin

____________________

<<接口>>

IPlugin1

<<接口>>

IPlugin2

<<接口>>

IPlugin3

 

IPlugin31

 

IPlugin32

 

……………………….

代码描述如下:

       Class Iplugin()

{

Public:

       virtual ~IPlugin(){};

       virtual const std::string & GetName()=0;

virtual const versionInfo & Geterison()=0;

virtual const void About()=0;

};

Class IPlugin1:public IPlugin

{

       Public:

              virtual ~IPlugin1(){};

              //针对这个插件的具体实现的代码

              virtual bool DoSomething1()=0;

};

Class IPlugin2:public IPlugin

{

       Public:

              virtual ~IPlugin2(){};

              //针对这个插件的具体实现的代码

              virtual bool DoSomething2()=0;

};

Class IPlugin3:public IPlugin

{

       Public:

              virtual ~IPlugin3(){};

              //针对这个插件的具体实现的代码

              virtual bool DoSomething3()=0;

};

Class IPlugin31:public IPlugin3

{

       Public:

              IPlugin31();

              //针对这个插件的具体实现的代码

              virtual dosomething31();

              virtual void About();

};

Class IPlugin31:public IPlugin3

{

       Public:

              IPlugin31();

              //针对这个插件的具体实现的代码

              virtual dosomething31();

              virtual void About();

};

从上边的结构和代码我们可以看出插件一般是通过在软件中定义一个虚函数和一定的接口,然后在插件中继承这个虚函数,实现这些接口.如果需要实现几个插件的话,那么我们只要在中间再加上一层继承层次.

 

,插件的加载

       上边我们只说明了插件的结构,并没有说明当我们已经又一个些好代码的插件,我们改如何将我门的插件的代码插入我们软件的进程空间中去.通过上边的讨论我们知道,我门的插件是由不同的人员开发的,那么就可能我们的软件发行了以后,然后才有其他人员来开发插件.通常情况下就是这样的.那么就存在这样的问题.我们知道计算机中不同的进程是运行在不同的进程空间中的.也就是我们的软件和其他人员开发的插件应该如何运行在同一个进程空间中或者如何相互交换信息.

       幸好我们知道Windows底下有一个叫做DLL的东西,它可以是我们的后来写的代码插入到一个正在运行的程序的进程空间中.同样Linux底下也有一个叫做*.so文件.它就是我们Windows底下通常知道的DLL.这样我们写的插件就可以通过一个DLL导出一个第一部分讨论的子类,然后这个子类就是实现我们插件功能的代码,也就是我们的插件.这样我们就将我们的插件的代码插入到我们的软件当中了.

示意性代码如下:

#define IPLUGINDECL _declspec(dllexport)

extern “C” PLUGINDECL IPlugin* CreatePlugin(pluginManager &mgr)

 

PLUGINDECL IPlugin* CreatePlugin(PluginManager &mgr)

{

       return  new Plugin3;

}

 插件管理器

       有了插件和插件加载到软件当中去的方法后,我们还需要一个插件管理器来管理我们的插件.要不然谁来加载插件,谁来负责插件的卸载,谁来负责插件与我们软件的通信.插件管理器主要是先加载DLL:

1  HMODULE  hDll==::LoadLibrary(filename.c_srtr());

2  然后寻找插件输出的函数:

       CREATEPLUGIN pFunc=( CREATEPLUGIN)::GetProcAddress(hDll,_T(“CreatePlugin”));

       If(pFunc==null)

Findnext();

3         通过刚才我们得到的函数指针调用插件的输出函数实现我们插件的功能:

IPlugin* pPlugin=pFunc(*this);

4         插件的卸载.所需要做的工作就是从插件列表中移除插件,删除插件对象.并通过调用FreeLibrary(hDll)卸载DLL

 插件与程序之间的双向通信.

       插件免不了要和我们的程序之间进行通信.要实现插件和程序之间的通信,最清晰直观的方法就是通过插件管理器进行.通过上边的代码我们可以看到在插件构造的时候我们曾经将插件管理器作为一个参数传递给了插件构造函数,这样我们就可以在插件构造的时候得到程序的数据.插件应该具有什么级别的访问能力,对程序其他部分的访问能力限制的越多,插件对程序的依赖就越小,同时插件的能力相应的就要弱一些.如果对程序其他部分的访问能力限制的越少,插件的功能可能更强一些,但是对程序的依赖就越大.如果我们以后程序有一点改变,就可能导致别人开发的插件不能使用.

       好了,到这里插件的基本原理我就理解这么多.欢迎大家指正我的不足.


为了使大家对插件有更深入的了解,让我们先重温一下通常情况下创建和调用DLL的过程。
每一个程序员都知道,我们应当将某些类或者模块编译为DLL,然后在主程序中调用,关于这样做的目的和好处,我就不再啰嗦了。
假设我创建了一个名为TirayComm.dll的类库,并编写了一个UDP类用于UDP数据传输:
namespace Tiray.Net
{
    public class UDP
    {
 ...
 //port--本地侦听端口
 //ttl时间,以毫秒为单位
        public UDP(int port,short ttl)
        {
  ...
        }

 //初始化
        public void Init()
        {
  ...
        }

 //发送数据
 //data--待发送数据
 //remoteIP--远端IP地址
 //port--远端UDP端口
        public void Send(byte[] data,string remoteIP,int port)
        {
  ...
        }
 //关闭
        public void Close()
        {
   ...
        }
        
    }
}
我在主程序中创建UDP类的实例和调用UDP类方法的代码如下:
using Tiray.Net
   ...
   Tiray.Net.UDP udp=new UDP(25000,2000);
   udp.Int();
   ...
别忘了要先在项目中添加对TirayComm.dll的引用。
我先提醒大家一下,上面的步骤都是在你的IDE中完成的,也就是说是在你编写程序代码的过程中完成的。

下面我用我以前开发的一个软件项目为大家详细解释一下用C#实现插件的一些技术细节。
我曾经接到过一个SP的软件项目,为一家电台开发一个听众短信互动平台。在需求分析过程中,我意识到电台的短信互动平台需要非常灵活的功能扩展,因为一个电台往往有好几个频率,每个频率平均每个小时都是一个不同的节目,每一个节目对短信平台的要求都可能不同:有的节目仅仅要求短信平台将听众的短信显示给主持人;有的节目要求短信平台能够自动回复某些信息;有的节目如短信答题等要求短信平台能自动判断用户的答案是否正确,并能提供抽奖功能...... 而且最糟糕的是即使同一个节目,其内容也随时有可能更换或改版,然后对短信平台提出新的要求。
这时候我开始有了编写插件的想法。我希望当主持人提出一个新的短信应用要求时,我只需要编写一个插件来实现相关的应用逻辑,然后将插件安装到短信平台的特定目录下,就可以实现相应的功能,而无须对整个短信平台进行升级。同样,当主持人不再需要某个短信应用的时候,我只须简单地从特定目录中将相应的插件删除即可。
下面是我编写的插件的基类,请注意这是一个抽象类。从插件的意义说,其中的两个公共抽象方法和一个事件就是插件的接口规范定义。我没有用interface关键字来定义插件的接口,是因为我还有一些与短信网关有关的代码需要在这个类中实现,而且我也不想考虑在插件中实现多重继承的问题。实际上,也可以使用interface关键字来定义插件的接口。关于抽象类和接口的有关内容,大家可以到MSDN里寻找,我就不多说了。
namespace Tiray.SMS
{
 public abstract class Plugin
 {
  //插件的名称
  protected String pluginName;
  ...
  //OnReceive是接收到中国移动短信网关类CMPP30和中国联通短信网关类SGIP短信后的事件处理函数
  //destNumber--SP端的号码,如1066123456
  //phoneNumber--手机号码,如13812345678
  //Message--短消息
  public abstract void OnReceive(String destNumber,String phoneNumber,String message);
  
  //显示插件属性,用户可以调用此方方法察看和设置插件属性
  public abstract void ShowProperty();
  ...
  //从内存中销毁插件实例时的处理函数。
  public abstract void Finalize();  
  ...
  //插件的事件
  //通常是一个发送短消息请求
  public event PluginEventHandler PluginEvent;
 }
 //插件事件定义
 public delegate void PluginEventHandler(object sender, PluginEventArgs e);

}
下面是一个为交通类节目写的交通违法查询的短信插件实现。这个插件的作用是当用户发送一个车牌号到短信互动平台时,可以返回该车牌号在一段时间内的交通违法记录。这个插件被编译为一个名为Illegal.dll的类库,然后发布到主程序的“Plugin”子目录下。

using Tiray.SMS;
namespace Tiray.SMS.RadioService
{
 //交通违法查询短信插件
 public class IllegalQuery:Plugin
 {
  ...

  public IllegalQuery()
  {
   ...
   pluginName="交通违法行为查询";
   ...
   
  }
  public override void ShowProperty()
  {
   ...
  }
  public override void Finalize()
  {
   ...
  }

  public override void OnReceive(String destNumber,String phoneNumber,String message)
  {
   ...
   //对接收到的车牌号进行检验和处理
   string carNumber=CheckCarNumber(string message);
   if(carNumber!=string.Empty)
   {
    ...  
    //查询违法记录并发送到用户手机
    QueryAndSend(carNumber,phoneNumber);
    ...
   }
   ...
  }

 }
}
从上面的代码来看,我只是实现了一个很普通的类继承,似乎看不出插件有什么与众不同的地方。下面来看一看在主程序中是如何调用插件的。

...
using Tiray.SMS;
using System.Reflection;

...

  ...
  protected Hashtable n_htPlugin=null;
  ...
  protected void InitPlugin()
  {
   m_htPlugin=new Hashtable();

   string dir=Directory.GetCurrentDirectory()+@"\plugin";
   string[] files=Directory.GetFiles(strDir,"*.DLL");
   
   foreach(string file in files)
   {
    Tiray.SMS.Plugin plugin=null;
    try
    {
     System.Reflection.Assembly asm=System.Reflection.Assembly.LoadFile(files);
     Type[] types=asm.GetTypes();
     foreach(Type type in types)
     {
      if(type.BaseType.FullName=="Tiray.SMS.Plugin")
      {
       plugin=asm.CreateInstance(type.FullName) as Plugin;
       break;
      }
     }
    }
    catch(Exception ex)
    {
     ...
     plugin=null;
     ...
    }
    if(plugin!=null)
    {
     
     plugin.PluginEvent+=new PluginEventHandler(OnPlugin);
     m_htPlugin.Add(plugin.Name,plugin);
    }
   }
  }
   
  //来自插件的消息
  //通常是一个发送短消息请求
  protected void OnPlugin(object sender,PluginEventArgs e)
  {
   ...
  }

}
请注意插件实例是使用Sytem.Reflection.Assembly类的CreateInstance方法创建的。对比前面我提到过的UDP类的实例创建方法,我并没有在主程序中添加对Illegal.DLL的引用,程序中也没有直接对IllegalQuery类进行声明的代码。
在上一讲中,我提到过,插件的一个优点就是运行时的功能扩展,通过上面的代码,大家应该对插件的这一特性有一些了解了吧。
上面的只是一个很简单的例子,但是大家也已经看到,为了实现插件,我不得不在主程序中添加了更多的代码,如我创建了一个Hashtable用于在内存中保存每一个插件实例;我使用了比常规的类实例创建方法更多的代码来创建插件实例;虽然我没有列出插件事件处理函数的代码,但是可以想象,我一定要使用更多的代码来判断当前的事件是由哪一个插件引发的。实际上,插件的实现还需要考虑一些更复杂的问题,在第三讲中,我会对插件进行更深入的探讨。
现在,让我们回顾一下插件的两个基本特性及实现方法:
统一的调用接口
这是用abstract关键字定义一个抽象类或者用interface关键字定义一个接口来实现的;
运行时加载
这是使用System.Reflection.Assembly类的相关方法来实现的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值