本文讨论一种简单却有效的插件体系结构,它使用C++,动态连接库,基于面向对象编程的思想。首先来看一下使用插件机制能给咱们带来哪些方面的好处,从而在适当时候合理的选择使用。html
1. 加强代码的透明度与一致性:java
由于插件一般会封装第三方类库或是其余人编写的代码,须要清晰地定义出接口,用清晰一致的接口来面对全部事情。你的代码也不会被转换程序或是库的特殊定制需求弄得乱七糟。linux
2. 改善工程的模块化:c++
你的代码被清析地分红多个独立的模块,能够把它们安置在子工程中的文件组中。这种解耦处理使得建立出的组件更加容易重用。程序员
3. 更短的编译时间:编程
若是仅仅是为了解释某些类的声明,而这些类内部使用了外部库,编译器再也不须要解析外部库的头文件了,由于具体实现是以私有的形式完成。windows
4. 更换与增长组件:设计模式
假如你须要向用户发布补丁,那么更新单独的插件而不是替代每个安装了的文件更为有效。当使用新的渲染器或是新的单元类型来扩展你的游戏时,能过向引擎提供一组插件,能够很容易的实现。数组
5. 在关闭源代码的工程中使用GPL代码:安全
通常,假如你使用了GPL发布的代码,那么你也须要开放你的源代码。然而,若是把GPL组件封装在插件中,你就没必要发布插件的源码。
介绍
先简单解释一下什么是插件系统以及它如何工做:在普通的程序中,假如你须要代码执行一项特殊的任务,你有两种选择:要么你本身编写,要么你寻找一个已经存在 的知足你须要的库。如今,你的要求变了,那你只好重写代码或是寻找另外一个不一样的库。不管是哪一种方式,都会致使你框架代码中的那些依赖外部库的代码重写。
如今,咱们能够有另一种选择:在插件系统中,工程中的任何组件再也不束缚于一种特定的实现(像渲染器既能够基于OpenGL,也能够选择Direct3D),它们会从框架代码中剥离出来,经过特定的方法被放入动态连接库之中。
所谓的特定方法包括在框架代码中建立接口,这些接口使得框架与动态库解耦。插件提供接口的实现。咱们把插件与普通的动态连接库区分开来是由于它们的加载方式 不一样:程序不会直接连接插件,而多是在某些目录下查找,若是发现便进行加载。全部插件均可以使用一种共同的方法与应用进行联结。
常见的错误
一些程序员,当进行插件系统的设计时,可能会给每个做为插件使用的动态库添加一个以下函数相似的函数:
[cpp] view plaincopy
而后它们让插件去提供一些类的实现。引擎用指望的对象名对加载的插件逐个进行查询,直到某个插件返回,这是典型的设计模式中“职责链”模式的作法。
一些更聪明的程序员会作出新的设计,使插件在引擎中注册本身,或是用定制的实现替代引擎内部缺省实现:
[html] view plaincopy
第一种设计的主要问题是:插件工厂建立的对象须要使用reinterpret_cast<>来进行转换。一般,插件从共同基类(这里指 PluginClass)派生,会引用一些不安全的感受。实际上,这样作也是没意义的,插件应该“默默”地响应输入设备的请求,而后提交结果给输出设备。
在这种结构下,为了提供相同接口的多个不一样实现,须要的工做变得异常复杂,若是插件能够用不一样名字注册本身(如Direct3DRenderer and OpenGLRenderer),可是引擎不知道哪一个具体实现对用户的选择是有效的。假如把全部可能的实现列表硬编码到程序中,那么使用插件结构的目的也 没有意义了。
假如插件系统经过一个框架或是库(如游戏引擎) 实现,架构师也确定会把功能暴露给应用程序使用。这样,会带来一些问题像如何在应用程序中使用插件,插件做者如何引擎的头文件等,这包含了潜在的三者之间版本冲突的可能性。
单独的工厂
接口,是被引擎清楚定义的,而不是插件。引擎经过定义接口来指导插件作什么工做,插件具体实现功能。
咱们让插件注册本身的引擎接口的特殊实现。固然直接建立 插件实现类的实例并注册是比较笨的作法。这样使得同一时刻全部可能的实现同时存在,占用内存与CPU资源。解决的办法是工厂类,它惟一的目的是在请求时建立另外类的实例。
若是引擎定义了接口与插件通讯,那么也应该为工厂类定义接口:
[cpp] view plaincopy
选择1: 插件管理器
接下来应该考虑插件如何在引擎中注册它们的工厂,引擎又如何实际地使用这些注册的插件。一种选择是与存在的代码很好的接合,这经过写插件管理器来完成。这使得咱们能够控制哪些组件容许被扩展。
[cpp] view plaincopy
当引擎须要一个渲染器时,它会访问插件管理器,看哪些渲染器已经经过插件注册了。而后要求插件管理器建立指望的渲染器,插件管理器因而使用工厂类来生成渲染器,插件管理器甚至不须要知道实现细节。
插件由动态库组成,后者导出一个能够被插件管理器调用的函数,用以注册本身:
[cpp] view plaincopy
插件管理器简单地在特定目录下加载全部dll文件,检查它们是否有一个名为registerPlugin()的导出函数。固然也可用xml文档来指定哪些插件要被加载。
选择 2: 完整地集成Fully Integrated
除了使用插件管理器,也能够从头设计代码框架以支持插件。最好的方法是把引擎分红几个子系统,构建一个系统核心来管理这些子系统。可能像下面这样:
[cpp] view plaincopy
这 里有两个子系统,它们使用”Server”做为后缀。第一个Server内部维护一个有效图像加载器的列表,每次当用户但愿加载一幅图片时,图像加载器被一一查询,直到发现一个特定 的实现能够处理特定格式的图片。
另外一个子系统有一个GraphicsDrivers的列表,它们做为Renderers的工厂来使用。能够是 Direct3DgraphicsDriver或是OpenGLGraphicsDrivers,它们分别负责Direct3Drenderer与 OpenGLRenderer的建立。引擎提供有效的驱动列表供用户选择使用,经过安装一个新的插件,新的驱动也能够被加入。
版本
在上面两个可选择的方法中,不强制要求你把特定的实现放到插件中。假如你的引擎提供一个读档器的默认实现,以支持自定义文件包格式。你能够把它放到引擎自己,当StorageServer 启动时自动进行注册。
如今还有一个问题没有讨论:假如你不当心的话,与引擎不匹配(例如,已通过时的)插件会被加载。子系统类的一些变化或是插件管理器的改变足以致使内存布局的 改变,当不匹配的插件试图注册时可能发生冲突甚至崩溃。比较讨厌的是,这些在调试时难与发现。
幸运的是,辨认过期或不正确的插件很是容易。最可靠的是方法是在你的核心系统中放置一个预处理常量。任何插件都有一个函数,它能够返回这个常量给引擎:
[cpp] view plaincopy
在 这个常量被编译到插件后,当引擎中的常量改变时,任何没有进行从新编译的插件它的 getExpectedEngineVersion ()方法会返回之前的那个值。引擎能够根据这个值,拒绝加载不匹配的插件。为了使插件能够从新工做,必须从新编译它。
固然,最大的危险是你忘记了更新常量值。不管如何,你应该有个自动版本管理工具帮助你。
英文原版:http://www.nuclex.org/articles/cxx/4-building-a-better-plugin-architecture
分类: 概念百科2012-07-07 19:18 801人阅读 评论(0) 收藏 举报
摘要:
基于插件的应用系统拥有良好的可扩充性、可定制性和可维护性。
<!--[if !supportLists]-->1. <!--[endif]-->引言
插件是近年来十分常见的一种技术。插件结构有助于编写有良好的扩充和定制功能的应用程序。许多软件甚至操做系统或其外壳程序都使用了这种技术,著名的使用插件机制的软件是Winamp, Winamp早期的成功虽然在于其快速的解码引擎,但在MP3播放器中可以保特长久的霸主地位。也正是因为内置了健全的插件功能后期的Winamp中增长的MIDI、 MOD,、WAVE等音乐格式的播放功能彻底是靠插件实现的。本文将论述插件技术的基本原理,并给出三种不一样的实现插件系统的方法。最重要的部分则是插件与主程序之间的交互插件,通常是一个遵循了某些特定规则的DLL,而主程序将全部插件接口在内存中的地址传递给插件插件则根据这些地址来调用插件接口完成所需功能获取所需资源等。
<!--[if !supportLists]-->2. <!--[endif]-->插件系统的基本原理
插件的木质是在不修改程序主体的状况下对软件功能进行增强。当插件的接口被公开时任何人均可以本身制做插件来解决一些操做上的不便或增长一些功能。一个插件框架包括两个部分:主程序(host)和插件((plug-in)。主程序便是“包含”插件的程序。插件必须实现若干标准接口,由主程序在与插件通讯时调用。
编程实现方面包括两个部分:一部分是主体程序的插件处理机制,用来进行初始化每一个插件的过程,而且管理好每一个插件接口。另外一部分是插件的接口函数定义,将全部的插件接口函数进行封装。以便开发者自由调用。
<!--[if !supportLists]-->3. <!--[endif]-->插件系统的开发
本文将经过一个摸拟的音频播放器(使用VC++ 6。0)来介绍插件的三种实现方法
(1)普通的输出函数的DLL方式
(2)使用C++的多态性。
(3)使用COM类别(category)机制。
首先对此音频播放器做如下说明:①这不是一个真的播放器。固然也不能真地播放音频文件。②每一个插件支持一种格式的音频文件如Wma、mp3等,经过增长插件可使系统支持更多的音频格式。③插件接口简单,其功能实现只是弹出一个对话框代表哪一个插件的哪一个功能被调用了。制做这个播放器的真正目的是演示插件技术的原理和实现方法只要掌握了其原理和方法就彻底能够开发出有用的插件系统
无论用什么手段实现插件和主程序之间的交互必须有一个协议,对于方法(1)这个协议是一系列的函数。这些函数由插件DLL引出由主程序调用。对于方法〔2)协议则是一个(或多个)基类一般是抽象类,插件须要构造一个类来继承此抽象类并实现其接口方法,再为主程序提供一个建立和销毁此实现类的对象的公共方法这个公共方法理所固然也应成为协议的一部分。对于方法(3)则是一个(或多个)COM接口插件是一个COM组件,它实现了这些接口,并注册到约定的组件类别tcomponent category)下。
通常音频播放器都有这样一些基本功能:装载音频文件(LoadFlle)、播放(Play)、暂停(Pause),中止((Stop)。咱们的播放器将提供这四个功能,但主程序自己并不会直接实现这些功能而是调用插件的实现,上文已经说过。每一个插件支持一种音频格式,因此每一个插件的功能实现都是不一样的。在主程序打开某个格式的音频文件时,根据文件扩展名来决定调用哪一个插件的功能,主程序能够在启动时加载全部插件,也能够在打开文件时动态加载所需插件,甚至能够在启动时加载一部分经常使用的插件,而在须要时加载其他插件开发者能够有很高的自由度,如今咱们来详细讨论三种实现方法,
3.1第一种方法
3.1.1插件的实现
咱们建立一个动态连接库PIugl.dll,为了支持四个基本功能,它输出相应的四个函数
void LoadFile(const char szHeName)
void Play(),
void Pause(),
void Stop(),
这些函数能够简单实现为只弹出一个消息框,代表是哪一个插件的哪一个函数被调用了, 为了使主程序在运行时能知道这个插件能够支持什么格式的音频又件插件程序还应输出一个函数供主程序查询用
void GetSupportedformat(char* szFomrat)
至此,这个插件就制做完了,能够依样画葫芦再作一个PIug2,dll它‘支持‘,wma文件,下面来看主程序的实现。
3.1.2主程序的实现
主程序是一个基于对话框的标准Windows程序,它启动时会搜索约定目录(能够约定全部插件都存放在主程序所在目录的Plugins子目录下),并使用Wia32函数LoadLrbrary加载全部插件,每加载一个插件DLL,就调用另外一个Wun32函数GetProcAddress获取引出函数
GetSupportedformat的地址,并调用此函数返回插件所支持的格式名(便是音频文件的扩展名),而后把(格式名,DLL句柄)二元组保存下来,当用户经过菜单打开文件时,主程序会根据扩展名决定调用哪一个插件的LoadFile函数,并指明此插件DLL的句柄为当前使用的插件的DLL句柄(好比保存到变量m_hlnst中),此后当用户
经过按钮调用Play等其余功能时就调用句柄为m_hlnst的插件的相应功能,如:
typedef void (PLAY)();
if(m_hlnst)
{
PLAY=GetProcAddress(m_hlnst, "Play");
PLAY();
}
另外,当程序退出时,应该调用FreeLibrary函数卸载插件,
到此为止第一种实现插件系统的方法就介绍完了,能够看出,其实现的关键在于插件输出、函数的约定以及把插件所支持的格式名映射到插件DLL的句柄,后面将会看到,实际上每一种实现都是基于这种原理只不过是方式不一样而已。
3.2第二种方法
第一种实现方法彻底是结构化程序设计,存在接口不易维护等缺点,从而咱们天然而然想到面向对象的解决方案——把API封装到类里。
3.2.1插件的实现
咱们定义抽象类以下
class ICppPlugin
{
public:
ICppPlugin ();
virtual void ICppPluginIcon()=0;
virtual void Release()=0;
virtual void GetSupportedFormat(char* szFormat)=0;
virtual void Load(constchar* szHeName)=0;
virtual void Play()=0;
virtual void Stop()=0;
virtual void Pause()=0;
};
其中Release成员函数将在后面介绍,其余成员函数意义与第一种实现中的同名函数相同,插件程序须要实现此抽象类,每一个插件都有不一样的实现而主程序仅经过接口(抽象类)来访问它们,如今来制做一个插件CppPlugin I,dll,它包含继承于ICppPlugin的类CppPlugin1。
class CppPlugin1 : public ICppPlugin //实现代码略
为使主程序能建立CppPlugin1对象,插件输出一个函数
bool CreateObject(void* pObj) {
pObj=new CppPluginlo;
return *pObj!=NULL;
}
对象是在动态库中建立的,也应该在动态库中被销毁,这就是ICppplugin的成员函数Release的做用。当主程序使用完对象后,须要调用此函数来销毁对象。它的实现以下:
void CppPIugin1::Release() { delete this; //删除本身}
咱们还能够再制做更多的插件,这些插件只须要给出ICppPlugin的不一样实现,即改变类
Cppplugin1的成员函数实现便可,如今来看主程序的处理过程。
3.2.2主程序的实现
插件的加载过程与第一种方法类似,所不一样的是加载DLL后,首先调用的是插件程序的CreateObject输出函数来建立对象:
typedef boot (*CreatoObject)(void *pObj);
//定义一个函数指针类型,获取Create06ject的地址,hInst为DLL的句柄
Create0bject CreatcObj=(CreateObject)GetProcAddtess(hInst, "Create0bject");
ICppPlugin* pObj = 0; //定义一个ICPPPlugjn的指针
CreateObj((void**)&pObj);//建立对象
接下来查询插件所支持的格式名,本方式中GetSupportedFormat已成为ICppPlugin的成员函数。
CString str;
pObj->GetSupportedFotmat(str,GetBuffer(8));
str.ReleaseBuffer;
另外,须要保存的除(格式名,DLL句柄)二元组映射外,还需保存(格式名,建立对象函数指针)二元组映射以备后用。
fstr存放的是格式名字符串的小写形式
m_formatMap [fstr」= hInst;
m_factoryMap[fstr] = CreatcObj;
一样在打开文件时选择使用哪一个插件,m_pObj存放当前使用的对象的指针,定义以下:ICppplugin *m_p0bj=0;//在程序初始化时要把它置为0
if(m_pObj){
m_pObj->Release();
m_pObj=0;
}
m_factoryMap [strExj((void")&m_pObj); //用CreatcObject
m_pObj->LoadFile((LPCSTR)strFileName); //strFileName是音频文件全路径名
之后就可使用m_pObj来调用其余操做了,例如:
if(m_pObj) m_pObj->Play();
在主程序退出时须要卸载DLL,没必要重复。
如今第二种实现插件系统的方式也介绍完了,这种方式基于C++的多态性,须要注意的是对象的建立和销毁方式,
3.3第三种方法
第二种实现方法其实已是组件化程序的雏形了,能够胜任开发小型的插件系统,若要开发大中型的系统,则须要彻底组件化的设计,COM (Component Object Model,组件对象模型)实际上就是一个实现插件的极好技术,基于COM创建的插件系统,主程序和各个插件能够用不一样的编程语言写成((C++, VB, Delplu,Java等),COM能使它们无缝地结合在一块儿,篇幅所限本文不详细介绍COM的原理与编程。
在这种实现方法中,插件是一个COM组件,确切地说,插件程序做为COM组件程序,包含了一个或多个COM对象。这些COM对象都实现了相同的COM接口,主程序经过这个COM接口来访问COM对象,即COM接口是主程序与插件通讯的惟一手段几好比播放器插件所包含的COM对象都实现了以下COM接口(IDL定义):
interface ICppPlugin : fUnknown
Plugin、子目录下,可是由于COM组件对COM客户是位置透明的,因此主程序须要知道的已不是插件的具休位置和名字而是COM组件的CLSID或ProglD,能够选择把这些信息存放到指定的注册表子键下,也能够放到ini文件中等等,然而更好的方式是使用COM的组件类别
(Component Category)索引机制,
COM容许实现者能够把相关的一组COM类组织到逻辑组(即组件类别)中,一般一个类别(category)中的全部COM类都实现同一组接口这些COM类共享同一个类别ID,称为CATID (category ID), CATID也是GUID它做为COM类的属牲被保存在注册表中COM类的"Implemented Categories’子键下在组件自注册时加入,每一个类别在注朋表中都有它本身惟一的子键,由它的CATID命名,
另外,系统提供一个称为组件类别管理器(component category manager)的COM类,它实现了ICatRegrster和ICatlafomauon接口,分别用来注册和查询类别信息,
因而基于COM的插件系统就能够这样实现:
(1)注册一个组件类别CATID_Plugin,
(2)插件实现包含实现了ICppPlugin接口的COM类并注册为CATID_Plugin类别,
(3〕主程序在启动时使用组件类别管理器查询CATID一Plugin类别信息,获得此类别的全部COM类的CLSID,并建立相应的COM对象,获取其ICppPlugin接口,而后调用接口的GetSupportedFormat,方法获得该插件所支持的格式名,保存(格式名,ICppPlugin接口指针)映射。
(4)程序在打开音频文件时根据扩展名决定使用哪一个ICppPlugin接口指针调用LoadFile方法,并设置当前使用的接口指针m_pICppPlugin为该接口指针。
(5)之后的操做(Play等)都使用m_pICppPlugin来调用直到打开不一样类型的文件。
(6)程序退出时释放掉COM对象并释放COM库所占用的资源。
详细代码这里再也不给出。
至此三种建立插件系统的方式都介绍完毕,程序使用Visual C++6,0开发,在Windows 2000Server上运行经过,
3.4 小结
上文所演示的例子中调用是单向的,即由插件暴露出接口,由主程序来调用,在实际应用中主程序也彻底能够暴露出接口,由插件来调用从而使系统更加灵活,三种方法从结构化程序设计到面向对象的方法再到基于组件的软件开发,难度依次升高,功能逐渐强大,系统也愈来愈灵活,根据所要建立的插件系统的不一样开发人员能够选择合适的实现方式,掌握技术原理是容易的,其实真正困难的是如何进行详细的应用分析,抽象出合适的接口,这样才能使整个插件系统拥有强大的可扩展性灵活性、健壮性和良好的可维护性。
HRESULT LoadFile(BSTR bstrFileName);
HRESULT GetSupportedFormat([out,retval) BSTR pbstrFormat);
HRESULT Play();
HRESULT Stop();
HRESULT Pause(); };
因而,插件的开发就是COM组件的开发,这里再也不详述,惟一的问题是主程序如何知道哪些是它能使用的插件(就是COM组件)。前两种实现中,咱们须要插件的具体位置和名字,因此约定插件都存放在主程序所在目录的。
<!--[if !supportLists]-->4. <!--[endif]-->结语
插件做为特殊的组件,具有组件的全部优秀的特性,这些特性使其在开发,推广,应用方面有重要的现实意义,基于插件技术的软件开发可使产品专业化标准化系列化,经过不一样规格和系列的插件的组合,能够快速地完成应用系统原型而经过对插件的局部修改来知足客户的需求和升级。
<!--[if !supportLists]-->5. <!--[endif]-->参考书目
Windows高级编程指南(第三版),清华大学出版社1999,6,
设计模式-可复用面向对象软件的基础,机械工业出版社2000,9,
COM本质论,中国电力出版社2001s,
分类: C/C++2010-04-10 23:39 1986人阅读 评论(0) 收藏 举报
框架eclipse插件servicestreamosgi测试
——初步设想
最近一直在学习OSGI方面的知识。买了一本《OSGI原理和最佳实践》,但是尚未到。遗憾的是,OSGI目前的几个开源框架只支持Java,对C和C++都不支持的。惋惜咱们公司目前主要的开发语言仍是c和c++,即使是引进OSGI,所得的好处范围有限。而我对松散耦合的模块化开发向往已久。查了一下OSGI对C++支持的好像是有一个开源项目,不过好像应用范围很小。而SCA标准中是有对C++实现模型的支持的,可是几个开源的框架目前还只支持JAVA。
昨天看了丁亮的转载的一篇博客《C/C++:构建你本身的插件框架 》,原文的连接:http://blog.chinaunix.net/u/12783/showart_662937.html 。看了一下里面讲的方法,本身却是能够实现。因此有了构建本身的c/c++插件开发框架的想法。今天先写一下初步的设想。
C/C++插件开发框架的要素
BlueDavy有一篇介绍服务框架要素的文章(连接:http://www.blogjava.net/BlueDavy/archive/2009/08/28/172259.html )。个人插件框架也要考虑、解决如下的几个问题:
一、如何注册插件;
二、如何调用插件;
三、如何测试插件;
四、插件的生命周期管理;
五、插件的管理和维护;
六、插件的组装;
七、插件的出错处理;
八、服务事件的广播和订阅(这个目前尚未考虑要支持);
其中有几个点很重要:1)插件框架要可以使模块松散耦合,作到真正的面向接口编程;2)框架要支持自动化测试:包括单元测试,集成测试;3)简化部署;4)支持分布式,模块能够调用框架外的插件。
采用的技术
插件框架要解决的一个问题就是插件的动态加载能力。这里可使用共享库的动态加载技术。固然,为了简单,第一步只考虑作一个linux下的插件框架。
整体结构
框架的整体结构上,参考OSGI的“微内核+系统插件+应用插件”结构。这里要好好考虑一下把什么作在内核中。关于微内核结构,之前我作个一个微内核流程引擎,会在后面有时间和你们分享。
框架中模块间的数据传送,有两种解决方法:一是普元采用的XML数据总线的作法。优势是扩展性好,可读性好。可是速度有些慢。二是采用我熟悉的信元流。优势的效率高,访问方便,可是可读性差一点,另外跨框架的数据传送,须要考虑网络字节序的问题。
对于框架间的通讯,经过系统插件封装,对应用插件隐藏通讯细节。
部署
努力作到一键式部署。
——整体功能
在这一系列的上一个文章中,介绍了构建C/C++插件开发框架的初步设想,下面我会一步步的向下展开,来实现个人这个设想。
今天主要谈一下我对这个框架的功能认识,或是指望。昨天看了一篇关于持续集成能力成熟度模型 的一篇文章,受此启发,我对此框架的认识渐渐清晰。
这个框架能够当作咱们公司底层产品(交换机,资源服务器等)的基础设施。上层基于java开发的产品能够直接在OSGI上开发。
核心功能:
一、最重要的一个功能是,提供一个模块化的编程模型,促进模块化软件开发,真正的实现针对接口编程。
二、提供一个有助于提升模块可重用性的基础设施。
三、提供一个C/C++插件的运行环境。
四、提供一个动态插件框架,插件能够动态更改,而无需重启系统。这个功能虽然不难实现,可是用处好像不是很大。
--------------------------------------------------------------------------------
扩展部分功能:
一、支持分布式系统结构,多个运行框架组合起来造成一个系统,对模块内部隐藏远程通信细节。
二、支持系统的分层架构。
三、可以和其余的开发框架进行集成,好比OSGI,SCA等。
四、多个运行框架中,可以实现对运行框架的有效管理。
五、概念上要实现相似于SCA中component(构件),composite(组合构件),Domain(域)的概念。
--------------------------------------------------------------------------------
开发部分功能:
一、为了简化开发,开发一个Eclipse插件,用于开发框架中的C/C++插件。可以根据插件开发向导,最终生成符合插件规范的公共代码,配置文件,Makefile文件等。
--------------------------------------------------------------------------------
调试部分功能:
一、提供一个统一的日志处理函数,能够集成Log4cpp。
二、提供模块间的消息日志,以及框架对外的接口日志。
三、提供消息和日志的追踪功能,能将和某事件相关的消息和日志单独提取出来。
四、提供资源监测功能,监测对资源(内存,套接字,文件句柄等)的使用状况。
--------------------------------------------------------------------------------
测试部分功能:
一、集成一些单元测试框架,好比unitcpp,达到自动化单元测试的目标。
二、本身实现自动化集成测试框架,而且开发相应的Eclipse插件,简化集成测试(利用脚本和信元流)。
三、集成原有的自动化功能测试框架flowtest,而且开发相应的Eclipse插件,简化功能测试。
四、实现性能测试,监测框架。
--------------------------------------------------------------------------------
部署部分功能:
一、实现自动化部署。特别是在分布式应用的状况下。
二、提供一个命令行程序,经过命令更改系统配置,管理插件。
——整体结构
这几天为了设计插件开发框架,尝试用了一下发散思惟来思考问题。中间看过依赖注入,AOP(面向方面编程),以及契约式设计等。虽然有些工具没法直接使用,可是这些思想仍是能够借鉴的,好比依赖注入,契约式设计。至于AOP,和工具相关性较大,虽然思想不错,可是没法直接在C++中使用。
我设计的插件间的依赖不是经过接口实现的,而是经过插件间的数据(信元流)。而信元流的检测可使用契约来检查。
插件开发框架的整体结构
微内核 :
一、 负责插件的加载,检测,初始化。
二、 负责服务的注册。
三、 负责服务的调用。
四、 服务的管理。
扩展层:
一、 日志的打印。
二、 消息(信元流)的解释,将二进制格式解释为文本。便于定位。
三、 消息和日志的追踪。
分布式处理层:
一、 用于和其余的框架通讯。
二、 和其余的框架搭配,造成一个分布式的系统。
自动化测试框架层:
一、 集成 cppunit 。
二、 自动化集成测试框架。
三、 自动化功能测试框架。
和第三方框架集成层:
1 、和 第三方框架 集成层。
——核心层设计和实现
上面一篇文章大体描述了一下插件开发框架总体结构。这篇描述一下核心层的设计和实现。
至于核心层的设计,我想借鉴 一下微内核的思想。核心层只负责实现下面几个功能:
一、 插件的加载,检测,初始化。
二、 服务的注册。
三、 服务的调用。
四、 服务的管理。
插件的加载,检测,初始化
插件的加载利用linux共享库的动态加载技术。具体的方法能够看一下IBM网站的一篇资料《Linux 动态库剖析》 。
服务的注册
服务的注册与调用采用表驱动的方法。核心层中维护一个服务注册表。
//插件间交互消息类型
typedef enum __Service_Type
{
Service_Max,
}Service_Type;
//插件用于和其余插件通讯接口函数,由插件提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//驱动表
typedef PF_Invoke_Service_Func Service_Drive_Table[Service_Max];
驱动表是一个数组,下标为插件间交互消息类型,成员为插件提供的接收的消息处理函数,由插件初始化的时候,调用插件框架的的注册函数注册到驱动表。
插件的初始化实现为:
//插件用于注册处理的消息类型的函数,由插件框架提供。
typedef RET_RESULT (*PF_Service_Register_Func)(Service_Type service_type);
//插件用于和其余插件通讯接口函数,由插件框架提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//插件回复响应函数。插件收到异步请求后,处理完成后,发送响应消息给请求的插件。由插件框架提供
typedef void (*PF_Send_Response_Func)(PRsp_Ele_Stream pele_str);
//初始化插件信息
typedef struct Plugin_Init_St
{
PF_Service_Register_Func register_func;//服务注册函数,要注册一系列的枚举值。插件能够处理的服务枚举值
PF_Invoke_Service_Func invoke_serv_func;//和其余组件交互时,调用的用于和其余组件交互的函数。发送请求消息。
PF_Send_Response_Func send_rsp_func;//再设计一个回复响应消息的接口。收到异步请求后,处理完毕后通知请求模块处理结果。
} Plugin_Init_St, *PPlugin_Init_St;
//初始化插件函数,相似于构造函数。由插件提供,供插件框架加载插件时初始化插件使用。
void PF_Init_Plugin(PPlugin_Init_St pinit_info);
插件在函数PF_Init_Plugin中调用函数register_func来注册插件要处理的消息类型。
服务的调用
//信元结构体
typedef struct Ele_St
{
Ele_Tag tag;
Ele_Length len;
Ele_Value value;
PEle_St next;
}Ele_St, *PEle_St;
//请求消息,信元流格式。
typedef struct Req_Ele_Stream
{
Plugin_ID src_id;//源插件id
Service_Type req_type;//请求类型
PEle_St ele;
} Req_Ele_Stream, *PReq_Ele_Stream;
//响应消息,信元流格式。
typedef struct Rsp_Ele_Stream
{
Plugin_ID dest_id;//目的插件id
Service_Type req_type;//响应对应的请求的类型。
Execute_Result result;//记录执行结果
Execute_Reason reason;//记录执行结果的缘由
PEle_St ele;
} Rsp_Ele_Stream, *PRsp_Ele_Stream;
//接收插件调用服务请求函数,由插件提供,入参为请求信元流。返回值为响应信元流,用于同步请求处理。
PRsp_Ele_Stream PF_Receive_Invoke_Proc(PReq_Ele_Stream pele_str);
//插件收到响应消息的处理入口函数,由插件提供。如此为响应信元流。
void PF_Receive_Rsponse_Porc(PRsp_Ele_Stream pele_str);
插件间的依赖关系是经过信元流来实现的。至于信元流的使用在个人另外一篇博客《使用信元流(TLVStream)规范、简化模块(C/C++)间交互 》 中有描述。插件对外的接口都是统一的。
若是插件要和其余的插件通讯,则调用PF_Init_Plugin函数的传递的服务调用接口: invoke_serv_func。插件框架根据信元流的类型,查找驱动表,找到对应的服务接收函数。插件用函数PF_Receive_Invoke_Proc接受其余插件的请求,此函数是插件想插件框架主动注册到驱动表的。
若是服务时同步的,这直接经过此函数返回,返回的信息在响应信元流中。若是是异步的请求,这插件在处理完成后,经过 send_rsp_func函数来发送响应。
插件的卸载 //卸载插件时调用的函数,相似于析构函数。由插件提供,供插件框架卸载插件时调用。 void PF_Destroy_Func();