“编程就是构建一个一个本身的小积木, 而后用本身的小积木搭建大系统”。java
可是程序仍是会比积木要复杂, 咱们的系统必需要保证小积木能搭建出大的系统(必须能被组合),有必须能使各个积木之间的耦合下降到最小。c++
传统的程序结构中也是有模块的划分,可是主要有以下几个缺点:git
a: c++二进制兼容程序员
b: 模块对外暴露的东西过多,使调用者要关心的东西过多github
c: 封装的模块只是做为功能的实现者封装,而不是接口的提供者算法
d: 可替换性和可扩展性差编程
而插件式的系统架构就是为了解决这样的问题。插件化设计的优势?插件化设计就是为了解决这些问题的,因此以上的缺点就是咱的优势windows
指导性原则:“面向接口编程而不是实现编程”
其接口的定义为interface, 其实转换一下的意思是面向纯虚类编程,固然也能够包装成面向服务和组件编程。
如我能够这样定义一个接口(interface)api
interfacecptf IRole{ virtual cptf ::ulong getHealth() = 0; virtual cptf ::ulong getHurt() = 0; virtual wstring getName() = 0; };
插件的目标就是实现IRole, 业务层的目标就是调用IRole, 业务层不知道IRole具体是如何实现的,而实现者也不用关心业务层是如何调用的。缓存
1). 使用者能经过规范,开发本身的插件,实用已有的插件,插件又能控制对外暴露的内容。
2). 运行时候能动态安装、启动、停在、卸载
3). 每个插件提供一个或多个服务,其余插件是根据接口来获取服务提供者
OSGI,Java中影响力最大的插件化系统就是OSGI标准
OSGI的定义:The dynamic module system for java
借鉴osgi对插件系统的定义,我认为一个典型的插件系统应该有以下几个方面构成:
“基础库+微内核+系统插件+应用插件”
其中微内核 负责以下功能:
一、 负责插件的加载,检测,初始化。
二、 负责服务的注册。
三、 负责服务的调用。
四、 服务的管理。
好比设计下以下的游戏场景:一个RPG游戏, 玩家控制一个英雄,在场景中有不一样的怪物,并且随着游戏的更新,
英雄等级的提高又会有不一样的怪物出现, 这里就想把怪物设计为插件。
首先工程是这样的布局的
首先要在作的是定义接口, 这里我须要一个英雄的接口,有须要一个怪物的接口。
interfacecptf IHero : public cptf ::core:: IDispatch , public IRole { virtual cptf ::ulong attack() = 0; }; interfacecptf IOgre : public cptf ::core:: IDispatch , public IRole { };
而后做为插件我须要实现一个Hero, 和多个Ogre
class Hero : public ServiceCoClass<Hero > , public ObjectRoot <SingleThreadModel> , public cptf ::core:: IDispatchImpl<IHero >{ class Wolf : public ServiceCoClass<Wolf > , public ObjectRoot<SingleThreadModel > , public cptf::core ::IDispatchImpl< IOgre> class Tiger : public ServiceCoClass<Tiger > , public ObjectRoot<SingleThreadModel > , public cptf::core ::IDispatchImpl< IOgre>
最后,在主工程用我要用到这些插件
void BattleMannager ::run() { hero_ = static_cast<IHero *>(serviceContainer_. getService(Hero_CSID , IHero_IID)); if (!hero_ )return; printHero(hero_ ); list<IService *> services = serviceContainer_ .getServices( IOgre_IID); list<IOgre *> ogres = CastUtils::parentsToChildren <IService, IOgre>(services ); for_each(ogres .begin(), ogres.end (), bind(&BattleMannager ::printOgre, _1)); services = serviceContainer_ .getServices( IHumanOgre_IID); list<IHumanOgre *> hummanOgres = CastUtils::parentsToChildren <IService, IHumanOgre>(services ); for_each(hummanOgres .begin(), hummanOgres.end (), bind(&BattleMannager ::printHumanOgre, _1)); }
以上, 由于逻辑层和插件实现层都已经好了, 整个流程也已经跑通,可是仍是的疑问:服务是怎么加载的?
借鉴OSGI, 我这里把系统设计为bundle+service的组合。 bundle是service的容器,service是功能的具体实现者。
在windows下,bundle用dll来表示。
那bundle在windwos下加载就很简单了LoadLibrary Api就好了
可是再c++中dll的接口还必需要考虑的一个问题就是c++的二进制兼容性:如今没有标准的 C++ ABI。这意味着,不一样编译器(甚至同一编译器的不一样版本)会编译出不一样的目标文件和库。这个问题致使的最显而易见的问题就是,不一样编译器会使用不一样的名称改写算法。这样对插件的接口来讲是致命的。固然咱们能够用c api来做为接口,可是这样势必会对总体的设计产生影响,并且做为一个装B的c++程序员,咱们怎么能容忍要借用低级语言的特性来实现咱们的功能呢。固然幸好还有另一种方式,那就是虚表。固然不是全部的c++编译器对虚表的实现也是不同的(好吧~~),可是至少主流(多主流~~不能肯定)的编译器虚表都是在对象的第一个位置。好吧,如今决定用虚表来对插件接口的实现了,因此咱们就能够用这样的方式来计算具体实现类的地址了
#define CPTF_PACKING 8 #define cptf_offsetofclass (base, derived) \ (( cptf::ulong )(static_cast< base*>((derived *)CPTF_PACKING))- CPTF_PACKING)
哇,好神奇的代码, 这个是为何呢。 这个就须要对c++内存对象模型须要深刻得了解了,可能须要拜读<c++内存对象模型>,这里篇幅有限这里就不解释了。可是若是有看官想要问“你为何这么天才能想出这样的写法?”,虽然我很想说我很天才,可是其实正是状况是我参考的atl中的源码,并且整个插件加载过程我都是山寨了atl中的相关代码的。
可是仍是有一个问题, 在GameMain中,认识的是IHero, 根本不知道有个Hero的实现,全部可能有这样的代码IHero* hero = New Hero() 这样动做。
那咱们要如何进行这样的new动做。 固然咱们说Hero是在Role dll中的, 在dll被加载的时候能够new Hero, 而后把hero对象的地址放到某个堆中,标志让GameMain使用。做为一个转换的伪设计人员, 我也是认为这样会有性能问题的, 我不只要作到加载, 还要作到懒加载。
那如何作到懒加载呢?
感谢微软,在vc++中有机制帮咱们作到,在其余的编译器中也会有其余的实现,可是这里咱们只作了vc++中的实现。
首先声明一个本身的段,段名能够叫cptf:
#pragma section ("CPTF$__a", read, shared ) #pragma section ("CPTF$__z", read, shared ) #pragma section ("CPTF$__m", read, shared )
而后在编译的时候,把具体实现的类的Create函数地址放到这个段中
#define CPTF_OBJECT_ENTRY_AUTO (class) \ __declspec(selectany ) AutoObjectEntry __objMap_##class = {class::clsid (), class:: creatorClass_::createInstance }; \ extern "C" __declspec( allocate("CPTF$__m" )) __declspec(selectany ) AutoObjectEntry* const __pobjMap_ ##class = &__objMap_ ##class; \ CPTF_OBJECT_ENTRY_PRAGMA(class )
最后在加载的时候,变量这个段,若是csid命中,则调用Create方法
inline bool cptfModuleGetClassObject( const CptfServiceEntities * cpfgModel , const cptf::IID & csid , const cptf::IID & iid , void** rtnObj) { bool rtn (false); assert(cpfgModel ); for (AutoObjectEntry ** entity = cpfgModel->autoObjMapFirst_ ; entity != cpfgModel ->autoObjMapLast_; ++entity) { AutoObjectEntry* obj = *entity; if (obj == NULL) continue; if (obj ->crateFunc != NULL && csid == obj-> iid){ rtn = obj ->crateFunc( iid, rtnObj ); break; } } return rtn ; }
总结下流程:
1. GameMian使用的是IHero,
2. Hero是IHero的实现者,在编译的规程中,把Create Hero的方法编译到固定段中
3. GameMian进行new的时候其实调用的是Dll固定段中的函数地址
4. 利用 上面的cptf_offsetofclass 宏实现对IHero的
每个服务都须要一个id来标志它, 这里就用guid, 命名为IID---interface id
每个服务的实现者也必需要有id来标志, 这也是一个guid, 命名为csid
咱们把服务和服务实现者的管理信息用配置文件管理起来,services.xml, 对Hero的定义
<service> <bundle>Role.dll</bundle> <csid>500851c0-7c2a-11e3-8c28-bc305bacf447</csid> <description>hero</description> <name>Hero</name> <serviceId>99f9dd8f-7c1a-11e3-9f9d-bc305bacf447</serviceId> <serviceName>IHero</serviceName> </service>
固然一个插件的管理器也是必须的, 管理Service的注册,缓存,析构、获取,查询等。这里用ServiceContainer实现
基于插件系统的架构:
interfacecptf IService{ virtual cptf ::ulong addRef() = 0; virtual cptf ::ulong release() = 0; virtual bool queryInterface( const cptf ::IID& iid, void**rntObj ) = 0; };
其实插件的内核并不复杂,复杂的是对插件接口的定义和封装,如何根据不一样的业务场景抽象出不一样的interface。