?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1.Z么需要插件化pȝ
“编E就是构Z个一个自q积木, 然后用自q积木搭建大pȝ”?/p>
但是E序q是会比U木要复杂, 我们的系l必要保证积木能搭徏出大的系l(必须能被l合Q,有必能使各个积木之间的耦合降低到最?/p>
传统的程序结构中也是有模块的划分Q但是主要有如下几个~点Q?/p>
a: c++二进制兼?/p>
b: 模块对外暴露的东西过多,使调用者要兛_的东西过?/p>
c: 装的模块只是作为功能的实现者封装,而不是接口的提供?/p>
d: 可替换性和可扩展性差
而插件式的系l架构就是ؓ了解册L问题。插件化设计的优点?插g化设计就是ؓ了解册些问题的Q所以以上的~点是q优点
2.插g话系l的原理
指导性原则:“面向接口编E而不是实现编E?/p>
其接口的定义为interface, 其实转换一下的意思是面向U虚cȝE,当然也可以包装成面向服务和组件编E?/p>
如我可以q样定义一个接?interface)
1
2
3
4
5
interfacecptf IRole{
virtual cptf ::ulong getHealth() = 0;
virtual cptf ::ulong getHurt() = 0;
virtual wstring getName() = 0;
};
插g的目标就是实现IRoleQ?业务层的目标是调用IRoleQ?业务层不知道IRole具体是如何实现的Q而实现者也不用兛_业务层是如何调用的?/p>
3.插g化系l的目标
1). 使用者能通过规范Q开发自q插gQ实用已有的插gQ插件又能控制对外暴露的内容?/p>
2). q行时候能动态安装、启动、停在、卸?/p>
3). 每一个插件提供一个或多个服务Q其他插件是Ҏ接口来获取服务提供?/p>
4. 一个插件化pȝ应该是怎么构成?/strong>
OSGIQJava中媄响力最大的插g化系l就是OSGI标准
OSGI的定义:The dynamic module system for java
借鉴osgiҎ件系l的定义Q我认ؓ一个典型的插gpȝ应该有如下几个方面构?
“基?微内?pȝ插g+应用插g?/p>
其中微内?负责如下功能Q?/p>
1?负责插g的加载,,初始化?/p>
2?负责服务的注册?/p>
3?负责服务的调用?/p>
4?服务的管理?/p>
5. 一个简单场景的随想
比如设计下如下的游戏场景Q一个RPG游戏Q?玩家控制一个英雄,在场景中有不同的怪物Q而且随着游戏的更斎ͼ
英雄{的提升又会有不同的怪物出现Q?q里想把怪物设计为插件?/p>
首先工程是这L布局?/p>
首先要在做的是定义接口, q里我需要一个英雄的接口Q有需要一个怪物的接口?/p>
interfacecptf IHero : public cptf ::core:: IDispatch
, public IRole {
virtual cptf ::ulong attack() = 0;
};
interfacecptf IOgre : public cptf ::core:: IDispatch
, public IRole {
};
然后作ؓ插g我需要实C个HeroQ?和多个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>
最后,在主工程用我要用到这些插?/p>
复制代码
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));
}
复制代码
以上Q?因ؓ逻辑层和插g实现层都已经好了Q?整个程也已l跑通,但是q是的疑问:服务是怎么加蝲的?
6. 如何q行插g的加载以及服务的注册
借鉴OSGIQ?我这里把pȝ设计为bundle+service的组合?bundle是service的容器,service是功能的具体实现者?/p>
在windows下,bundle用dll来表C?/p>
那bundle在windwos下加载就很简单了LoadLibrary Apip?
但是再c++中dll的接口还必须要考虑的一个问题就是c++的二q制兼容性:现在没有标准?C++ ABI。这意味着Q不同编译器Q甚臛_一~译器的不同版本Q会~译Z同的目标文g和库。这个问题导致的最显而易见的问题是Q不同编译器会用不同的名称改写法。这样对插g的接口来说是致命的。当然我们可以用c api来作为接口,但是q样势必会对整体的设计生媄响,而且作ؓ一个装B的c++E序员,我们怎么能容忍要借用低语言的特性来实现我们的功能呢。当然幸亏还有另外一U方式,那就是虚表。当然不是所有的c++~译器对虚表的实C是不一L(好吧~~)Q但是至主?多主~~不能定)的编译器虚表都是在对象的W一个位|。好吧,现在军_用虚表来Ҏ件接口的实现了,所以我们就可以用这L方式来计具体实现类的地址?/p>
#define CPTF_PACKING 8
#define cptf_offsetofclass (base, derived) \
(( cptf::ulong )(static_cast< base*>((derived *)CPTF_PACKING))- CPTF_PACKING)
哇,好神奇的代码Q?q个是ؓ什么呢?q个需要对c++内存对象模型需要深入得了解了,可能需要拜?lt;c++内存对象模型>Q这里篇q有限这里就不解释了。但是如果有看官惌问“你Z么这么天才能惛_q样的写法?”,虽然我很惌我很天才Q但是其实正是情冉|我参考的atl中的源码Q而且整个插g加蝲q程我都是山寨了atl中的相关代码的?nbsp;
但是q是有一个问题, 在GameMain中,认识的是IHeroQ?Ҏ不知道有个Hero的实玎ͼ所有可能有q样的代码IHero* hero = New Hero() q样动作?/p>
那我们要如何q行q样的new动作?当然我们说Hero是在Role dll中的Q?在dll被加载的时候可以new HeroQ?然后把hero对象的地址攑ֈ某个堆中Q标志让GameMain使用。作Z个{换的伪设计h员, 我也是认样会有性能问题的, 我不仅要做到加蝲Q?q要做到懒加载?/p>
那如何做到懒加蝲呢?
感谢微YQ在vc++中有机制帮我们做刎ͼ在其他的~译器中也会有其他的实现Q但是这里我们只做了vc++中的实现?/p>
首先声明一个自qD,D名可以叫cptfQ?/p>
#pragma section ("CPTF$__a", read, shared )
#pragma section ("CPTF$__z", read, shared )
#pragma section ("CPTF$__m", read, shared )
然后在编译的时候,把具体实现的cȝCreate函数地址攑ֈq个D中
#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 )
最后在加蝲的时候,变量q个D,如果csid命中Q则调用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 ;
}
复制代码
ȝ下流E:
1. GameMian使用的是IHero,
2. Hero是IHero的实现者,在编译的规程中,把Create Hero的方法编译到固定D中
3. GameMianq行new的时候其实调用的是Dll固定D中的函数地址
4. 利用 上面的cptf_offsetofclass 宏实现对IHero?/p>
7. 服务的管?/strong>
每一个服务都需要一个id来标志它Q?q里qguidQ?命名为IID---interface id
每一个服务的实现者也必须要有id来标志, q也是一个guidQ?命名为csid
我们把服务和服务实现者的理信息用配|文件管理v来,services.xml, 对Hero的定?/p>
复制代码
<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>
复制代码
当然一个插件的理器也是必ȝQ?理Service的注册,~存Q析构、获取,查询{。这里用ServiceContainer实现
8. Z插g的架?/strong>
Z插gpȝ的架构:
主要分三部分Q?1. 使用其对象模型的ȝl或d用程?/p>
2. 插g理?/p>
3. 插g
所有的插g但是从IService, 是参考Com中IUnkown
interfacecptf IService{
virtual cptf ::ulong addRef() = 0;
virtual cptf ::ulong release() = 0;
virtual bool queryInterface( const cptf ::IID& iid, void**rntObj ) = 0;
};
其实插g的内核ƈ不复杂,复杂的是Ҏ件接口的定义和封装,如何Ҏ不同的业务场景抽象出不同的interface?/p>
9. 源代?/strong>
本文不是很水的理论,所有的理论都是l过代码验证的?nbsp;
本文涉及到的代码在我的github上,https://github.com/sld666666/cptf
工程的目标是建立一个跨q_的c++插g开发框Ӟ 现在的是一个能成功在vc++下运行demo的插件化framework
用了boost和stlQ如果要深入了解core中的代码Q还需要对模板有了解, 水深请勿L试
当然有的看官会对core中的代码非常熟悉Q那可能你发CQ?我是山寨atl实现?/p>
10. 今后改进的方?/strong>
1. service如何释放Q?q在考虑是用野指针还是智能指针还是垃圑֛收机?/p>
2. 错误处理
3. 跨^台和跨编译器