「編程就是構建一個一個本身的小積木, 而後用本身的小積木搭建大系統」。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。