[转]用C++实现插件体系结构
本文讨论一种简单却有效的插件体系结构,它使用C++,动态链接库,基于面向对象编程的思想。首先来看一下使用插件机制能给我们带来哪些方面的好处,从而在适当时候合理的选择使用。
1. 增强代码的透明度与一致性:
因为插件通常会封装第三方类库或是其他人编写的代码,需要清晰地定义出接口,用清晰一致的接口来面对所有事情。你的代码也不会被转换程序或是库的特殊定制需求弄得乱七糟。
2. 改善工程的模块化:
你的代码被清析地分成多个独立的模块,可以把它们安置在子工程中的文件组中。这种解耦处理使得创建出的组件更加容易重用。
3. 更短的编译时间:
如果仅仅是为了解释某些类的声明,而这些类内部使用了外部库,编译器不再需要解析外部库的头文件了,因为具体实现是以私有的形式完成。
4. 更换与增加组件:
假如你需要向用户发布补丁,那么更新单独的插件而不是替代每一个安装了的文件更为有效。当使用新的渲染器或是新的单元类型来扩展你的游戏时,能过向引擎提供一组插件,可以很容易的实现。
5. 在关闭源代码的工程中使用GPL代码:
一般,假如你使用了GPL发布的代码,那么你也需要开放你的源代码。然而,如果把GPL组件封装在插件中,你就不必发布插件的源码。
介绍
先简单解释一下什么是插件系统以及它如何工作:在普通的程序中,假如你需要代码执行一项特殊的任务,你有两种选择:要么你自己编写,要么你寻找一个已经存在 的满足你需要的库。现在,你的要求变了,那你只好重写代码或是寻找另一个不同的库。无论是哪种方式,都会导致你框架代码中的那些依赖外部库的代码重写。
现在,我们可以有另外一种选择:在插件系统中,工程中的任何组件不再束缚于一种特定的实现(像渲染器既可以基于OpenGL,也可以选择Direct3D),它们会从框架代码中剥离出来,通过特定的方法被放入动态链接库之中。
所谓的特定方法包括在框架代码中创建接口,这些接口使得框架与动态库解耦。插件提供接口的实现。我们把插件与普通的动态链接库区分开来是因为它们的加载方式 不同:程序不会直接链接插件,而可能是在某些目录下查找,如果发现便进行加载。所有插件都可以使用一种共同的方法与应用进行联结。
常见的错误
一些程序员,当进行插件系统的设计时,可能会给每一个作为插件使用的动态库添加一个如下函数类似的函数:
[cpp] view plaincopy
- PluginClass *createInstance(const char*);
然后它们让插件去提供一些类的实现。引擎用期望的对象名对加载的插件逐个进行查询,直到某个插件返回,这是典型的设计模式中“职责链”模式的做法。
一些更聪明的程序员会做出新的设计,使插件在引擎中注册自己,或是用定制的实现替代引擎内部缺省实现:
[html] view plaincopy
- Void dllStartPlugin(PluginManager &pm);
- Void dllStopPlugin(PluginManager &pm);
第一种设计的主要问题是:插件工厂创建的对象需要使用reinterpret_cast<>来进行转换。通常,插件从共同基类(这里指 PluginClass)派生,会引用一些不安全的感觉。实际上,这样做也是没意义的,插件应该“默默”地响应输入设备的请求,然后提交结果给输出设备。
在这种结构下,为了提供相同接口的多个不同实现,需要的工作变得异常复杂,如果插件可以用不同名字注册自己(如Direct3DRenderer and OpenGLRenderer),但是引擎不知道哪个具体实现对用户的选择是有效的。假如把所有可能的实现列表硬编码到程序中,那么使用插件结构的目的也 没有意义了。
假如插件系统通过一个框架或是库(如游戏引擎) 实现,架构师也肯定会把功能暴露给应用程序使用。这样,会带来一些问题像如何在应用程序中使用插件,插件作者如何引擎的头文件等,这包含了潜在的三者之间版本冲突的可能性。
单独的工厂
接口,是被引擎清楚定义的,而不是插件。引擎通过定义接口来指导插件做什么工作,插件具体实现功能。
我们让插件注册自己的引擎接口的特殊实现。当然直接创建 插件实现类的实例并注册是比较笨的做法。这样使得同一时刻所有可能的实现同时存在,占用内存与CPU资源。解决的办法是工厂类,它唯一的目的是在请求时创建另外类的实例。
如果引擎定义了接口与插件通信,那么也应该为工厂类定义接口:
[cpp] view plaincopy
- template<typename Interface>
- class Factory {
- virtual Interface *create() = 0;
- };
- class Renderer {
- virtual void beginScene() = 0;
- virtual void endScene() = 0;
- };
- typedef Factory<Renderer> RendererFactory;
选择1: 插件管理器
接下来应该考虑插件如何在引擎中注册它们的工厂,引擎又如何实际地使用这些注册的插件。一种选择是与存在的代码很好的接合,这通过写插件管理器来完成。这使得我们可以控制哪些组件允许被扩展。
[cpp] view plaincopy
- class PluginManager {
- void registerRenderer(std::auto_ptr<RendererFactory> RF);
- void registerSceneManager(std::auto_ptr<SceneManagerFactory> SMF);
- };
当引擎需要一个渲染器时,它会访问插件管理器,看哪些渲染器已经通过插件注册了。然后要求插件管理器创建期望的渲染器,插件管理器于是使用工厂类来生成渲染器,插件管理器甚至不需要知道实现细节。
插件由动态库组成,后者导出一个可以被插件管理器调用的函数,用以注册自己:
[cpp] view plaincopy
- void registerPlugin(PluginManager &PM);
插件管理器简单地在特定目录下加载所有dll文件,检查它们是否有一个名为registerPlugin()的导出函数。当然也可用xml文档来指定哪些插件要被加载。
选择 2: 完整地集成Fully Integrated
除了使用插件管理器,也可以从头设计代码框架以支持插件。最好的方法是把引擎分成几个子系统,构建一个系统核心来管理这些子系统。可能像下面这样:
[cpp] view plaincopy
- class Kernel {
- StorageServer &getStorageServer() const;
- GraphicsServer &getGraphicsServer() const;
- };
- class StorageServer {
- //提供给插件使用,注册新的读档器
- void addArchiveReader(std::auto_ptr<ArchiveReader> AL);
- // 查询所有注册的读档器,直到找到可以打开指定格式的读档器
- std::auto_ptr<Archive> openArchive(const std::string &sFilename);
- };
- class GraphicsServer {
- // 供插件使用,用来添加驱动
- void addGraphicsDriver(std::auto_ptr<GraphicsDriver> AF);
- // 获取有效图形驱动的数目
- size_t getDriverCount() const;
- //返回驱动
- GraphicsDriver &getDriver(size_t Index);
- };
这 里有两个子系统,它们使用”Server”作为后缀。第一个Server内部维护一个有效图像加载器的列表,每次当用户希望加载一幅图片时,图像加载器被一一查询,直到发现一个特定 的实现可以处理特定格式的图片。
另一个子系统有一个GraphicsDrivers的列表,它们作为Renderers的工厂来使用。可以是 Direct3DgraphicsDriver或是OpenGLGraphicsDrivers,它们分别负责Direct3Drenderer与 OpenGLRenderer的创建。引擎提供有效的驱动列表供用户选择使用,通过安装一个新的插件,新的驱动也可以被加入。
版本
在上面两个可选择的方法中,不强制要求你把特定的实现放到插件中。假如你的引擎提供一个读档器的默认实现,以支持自定义文件包格式。你可以把它放到引擎本身,当StorageServer 启动时自动进行注册。
现在还有一个问题没有讨论:假如你不小心的话,与引擎不匹配(例如,已经过时的)插件会被加载。子系统类的一些变化或是插件管理器的改变足以导致内存布局的 改变,当不匹配的插件试图注册时可能发生冲突甚至崩溃。比较讨厌的是,这些在调试时难与发现。
幸运的是,辨认过时或不正确的插件非常容易。最可靠的是方法是在你的核心系统中放置一个预处理常量。任何插件都有一个函数,它可以返回这个常量给引擎:
[cpp] view plaincopy
- // Somewhere in your core system
- #define MyEngineVersion 1;
- // The plugin
- extern int getExpectedEngineVersion() {
- return MyEngineVersion;
- }
在 这个常量被编译到插件后,当引擎中的常量改变时,任何没有进行重新编译的插件它的 getExpectedEngineVersion ()方法会返回以前的那个值。引擎可以根据这个值,拒绝加载不匹配的插件。为了使插件可以重新工作,必须重新编译它。
当然,最大的危险是你忘记了更新常量值。无论如何,你应该有个自动版本管理工具帮助你。
英文原版:http://www.nuclex.org/articles/cxx/4-building-a-better-plugin-architecture
软件插件技术的原理与实现
分类: 概念百科2012-07-07 19:18 801人阅读 评论(0) 收藏 举报
构建自己的C/C++插件开发框架
分类: C/C++2010-04-10 23:39 1986人阅读 评论(0) 收藏 举报
框架eclipse插件servicestreamosgi测试
其中有几个点很重要:1)插件框架要能够使模块松散耦合,做到真正的面向接口编程;2)框架要支持自动化测试:包括单元测试,集成测试;3)简化部署;4)支持分布式,模块可以调用框架外的插件。
采用的技术
插件框架要解决的一个问题就是插件的动态加载能力。这里可以使用共享库的动态加载技术。当然,为了简单,第一步只考虑做一个linux下的插件框架。
框架的总体结构上,参考OSGI的“微内核+系统插件+应用插件”结构。这里要好好考虑一下把什么做在内核中。关于微内核结构,以前我做个一个微内核流程引擎,会在后面有时间和大家分享。
对于框架间的通信,通过系统插件封装,对应用插件隐藏通信细节。
在这一系列的上一个文章中,介绍了构建C/C++插件开发框架的初步设想,下面我会一步步的向下展开,来实现我的这个设想。
今天主要谈一下我对这个框架的功能认识,或是期望。昨天看了一篇关于持续集成能力成熟度模型 的一篇文章,受此启发,我对此框架的认识渐渐清晰。
这个框架可以当做我们公司底层产品(交换机,资源服务器等)的基础设施。上层基于java开发的产品可以直接在OSGI上开发。
1、最重要的一个功能是,提供一个模块化的编程模型,促进模块化软件开发,真正的实现针对接口编程。
4、提供一个动态插件框架,插件可以动态更改,而无需重启系统。这个功能虽然不难实现,但是用处好像不是很大。
--------------------------------------------------------------------------------
1、支持分布式系统结构,多个运行框架组合起来形成一个系统,对模块内部隐藏远程通讯细节。
5、概念上要实现类似于SCA中component(构件),composite(组合构件),Domain(域)的概念。
--------------------------------------------------------------------------------
1、为了简化开发,开发一个Eclipse插件,用于开发框架中的C/C++插件。能够根据插件开发向导,最终生成符合插件规范的公共代码,配置文件,Makefile文件等。
--------------------------------------------------------------------------------
3、提供消息和日志的追踪功能,能将和某事件相关的消息和日志单独提取出来。
4、提供资源监测功能,监测对资源(内存,套接字,文件句柄等)的使用情况。
--------------------------------------------------------------------------------
1、集成一些单元测试框架,比如unitcpp,达到自动化单元测试的目标。
2、自己实现自动化集成测试框架,并且开发相应的Eclipse插件,简化集成测试(利用脚本和信元流)。
3、集成原有的自动化功能测试框架flowtest,并且开发相应的Eclipse插件,简化功能测试。
--------------------------------------------------------------------------------
我设计的插件间的依赖不是通过接口实现的,而是通过插件间的数据(信元流)。而信元流的检测可以使用契约来检查。
2、 消息(信元流)的解释,将二进制格式解释为文本。便于定位。
上面一篇文章大致描述了一下插件开发框架整体结构。这篇描述一下核心层的设计和实现。
至于核心层的设计,我想借鉴 一下微内核的思想。核心层只负责实现下面几个功能:
1、 插件的加载,检测,初始化。
2、 服务的注册。
3、 服务的调用。
4、 服务的管理。
插件的加载,检测,初始化
插件的加载利用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();
转载于:https://www.cnblogs.com/FongLuo/p/4565826.html
[转]用C++实现插件体系结构相关推荐
- 插件体系结构软件开发方法研究
插件体系结构软件开发方法研究 计算机软件与理论专业 研究生 一觉亮天 随着计算机技术的发展,软件体系结构和开发方法也在发生着重大变化.为了高效率地进行软件开发,并且开发出高质量的产品,人们一直在寻求更 ...
- 我的SharpDevelop插件之一:知识管理器(2006年博客迁移)
一. 插件编写背景 通过近几年来对SharpDevelop的学习和研究,一直很感叹大师们对插件体系结构的灵活设计.有幸一年来,由于工作环境较以前轻松一些,故在工作之余,写了几个SharpDevelop ...
- Eclipse插件安装方式
Eclipse插件 Eclipse 是一个已经完全设计好的平台,是用于构建和集成应用的开发工具.平台本身不会提供大量的最终用户功能,平台的价值在于它的促进作用:根据插件模型来快速开发集成功能部件. 平 ...
- 曾经的 Java IDE 王者 Eclipse 真的没落了?21 款插件让它强大起来!
俗话说,好马配好鞍,才能展现千里马的实力.一名好的开发者,必定要有一套好的开发工具才能打造出最好的产品给用户.要论世界上最好用的 IDE 是哪一种?有人会选择老牌的 Visual Studio 或是 ...
- 软件体系结构期末复习
软件体系结构期末复习 标签(空格分隔): 未分类 回顾课本和TTP课件 内容总概 章节回顾 第1章.软件体系结构概论 0.软件体系结构的发展过程经历了四个阶段: (1)无体系结构阶段.(2)萌芽阶段. ...
- 10款好用Eclipse插件
古人有云,工欲善其事,必先利其器.Eclipse作为世界上著名的跨平台的自由集成开发环境(IDE),无疑是开发中手中的利器,那么Eclipse插件就是这把利器的磨刀石.然而,我们许多Java初学者却不 ...
- Eclispe 体系结构综述(一)
一.Eclispe插件体系结构概述 Eclispe是世界上最大的开源组织的产品,是一个基于Java的可扩展的开发平台,就其本身来说,它只是一个框架和一组服务,用于通过组件构建开发环境. Eclipse ...
- 国产最强开源 API 网关,没有之一,不接受任何反驳!
以下文章来源方志朋的博客,回复"666"获面试宝典 " 这篇文章由刚哥授权分享,刚哥是 Splunk Information Technology 的架构师,Linked ...
- jQuery之父强烈推荐之jQuery实战
jQuery之父强烈推荐 点击查看 媒体评论: "本书令我惊喜--这是一部深入透彻的著作,jQuery项目本身都从中获益匪浅.相信它将成为你学习和使用jQuery的理想资源.". ...
- 什么是反射,为什么有用?
什么是反射,为什么有用? 我对Java特别感兴趣,但是我认为原理在任何语言中都是相同的. #1楼 例: 以一个远程应用程序为例,该应用程序为您的应用程序提供了一个对象,该对象是使用其API方法获得的. ...
最新文章
- 一个“人工智能Python机器学习与深度学习”课程表
- 对话高通孟樸:5G发展离不开中国,高通坚定pick全球化
- 网站压力测试工具webbench简介、安装、使用
- opencv进阶学习笔记11:cannny边缘检测,直线检测,圆检测
- element ui 多个子组件_ElementUI 技术揭秘(2) 组件库的整体设计
- python实例方法、类方法@classmethod、静态方法@staticmethod和属性方法@property区别
- spring 源码深度解析_spring源码解析之SpringIOC源码解析(下)
- 如何在arcmap中使用取色器
- 实用软件资源下载地址集合
- 安装mysql不是Mariadb_解决centos7 中 使用mariadb 安装mysql不成功的问题
- SiamRPN++理解
- python--pandas长宽数据转换
- 系统异常SVC与PendSV指令及CM3 处理器内部寄存器分析
- 团队作业8——Beta 阶段冲刺6th day
- u盘安装原版win10
- TL431与PC817光耦在开关电源中的应用
- 3行代码爬取京东数据
- 解决实例化servlet报错500
- 5.6.3 列表到字典的函数,针对好玩游戏的物品清单
- ECharts(3)
热门文章
- 如何在form初始化时自动隐藏FOLDER列
- 一站式学习Wireshark(三):应用Wireshark IO图形工具分析数据流
- eigrp 负载均衡和非负载均衡实验
- CentOS 修改默认语言
- delphi三层架构中注册服务器
- VMware vSphere 5.1 群集深入解析(三)
- 数据库、SQL脚本、存储过程执行准则(*****)
- C# 获取系统图标类
- 三层交换机连接路由器时配置OSPF注意事项
- 金陵科技学院计算机科学与技术,计算机科学与技术专业考试大纲-金陵科技学院.DOC...