九九热这里直有精品,1区二区三区在线播放,玖玖爱在线观看资源,国产aⅴ综合网,午夜福利男女,日本亚洲欧美三级,日韩无码黄色导航,内射少妇13区,中文字幕高清网

您身邊的軟件定制專家--9年開發(fā)經驗為您護航

18678812288
0531-88887250

如何寫一個c++插件化系統(tǒng)

文章作者:濟南軟件開發(fā) 時間:2016年12月20日

1.為什么需要插件化系統(tǒng)

  “編程就是構建一個一個自己的小積木, 然后用自己的小積木搭建大系統(tǒng)”。

 

  但是程序還是會比積木要復雜, 我們的系統(tǒng)必須要保證小積木能搭建出大的系統(tǒng)(必須能被組合),有必須能使各個積木之間的耦合降低到最小。

 

  傳統(tǒng)的程序結構中也是有模塊的劃分,但是主要有如下幾個缺點:

 

    a: c++二進制兼容

 

    b: 模塊對外暴露的東西過多,使調用者要關心的東西過多

 

    c: 封裝的模塊只是作為功能的實現者封裝,而不是接口的提供者

 

    d: 可替換性和可擴展性差

 

  而插件式的系統(tǒng)架構就是為了解決這樣的問題。插件化設計的優(yōu)點?插件化設計就是為了解決這些問題的,所以以上的缺點就是咱的優(yōu)點

 

2.插件話系統(tǒng)的原理

  指導性原則:“面向接口編程而不是實現編程”

  其接口的定義為interface, 其實轉換一下的意思是面向純虛類編程,當然也可以包裝成面向服務和組件編程。

  如我可以這樣定義一個接口(interface)

 

1

2

3

4

5

interfacecptf IRole{

  virtual cptf ::ulong getHealth() = 0;

  virtual cptf ::ulong getHurt() = 0;

  virtual wstring getName() = 0;

};

  插件的目標就是實現IRole, 業(yè)務層的目標就是調用IRole, 業(yè)務層不知道IRole具體是如何實現的,而實現者也不用關心業(yè)務層是如何調用的。

 

3.插件化系統(tǒng)的目標

  1). 使用者能通過規(guī)范,開發(fā)自己的插件,實用已有的插件,插件又能控制對外暴露的內容。

  2). 運行時候能動態(tài)安裝、啟動、停在、卸載

  3). 每一個插件提供一個或多個服務,其他插件是根據接口來獲取服務提供者

 

4. 一個插件化系統(tǒng)應該是怎么構成的

  OSGI,Java中影響力最大的插件化系統(tǒng)就是OSGI標準

  OSGI的定義:The dynamic module system for java

  借鑒osgi對插件系統(tǒng)的定義,我認為一個典型的插件系統(tǒng)應該有如下幾個方面構成:

  “基礎庫+微內核+系統(tǒng)插件+應用插件”

  其中微內核 負責如下功能:

 

    1、 負責插件的加載,檢測,初始化。

 

    2、 負責服務的注冊。

 

    3、 負責服務的調用。

 

    4、 服務的管理。

 

5. 一個簡單場景的隨想

  比如設計下如下的游戲場景:一個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));

}

復制代碼

  以上, 因為邏輯層和插件實現層都已經好了, 整個流程也已經跑通,但是還是的疑問:服務是怎么加載的?

 

6. 如何進行插件的加載以及服務的注冊

    借鑒OSGI, 我這里把系統(tǒng)設計為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的實現者,在編譯的規(guī)程中,把Create Hero的方法編譯到固定段中

    3. GameMian進行new的時候其實調用的是Dll固定段中的函數地址

    4. 利用 上面的cptf_offsetofclass 宏實現對IHero的

 

7. 服務的管理

  每一個服務都需要一個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實現

 

8. 基于插件的架構

    基于插件系統(tǒng)的架構:

 

     主要分三部分: 1. 使用其對象模型的主系統(tǒng)或主應用程序

                         2. 插件管理器

                         3. 插件

    所有的插件但是從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;

     };

  其實插件的內核并不復雜,復雜的是對插件接口的定義和封裝,如何根據不同的業(yè)務場景抽象出不同的interface。

 

9. 源代碼

   本文不是很水的理論,所有的理論都是經過代碼驗證的。 

     本文涉及到的代碼在我的github上,https://github.com/sld666666/cptf

     工程的目標是建立一個跨平臺的c++插件開發(fā)框架, 現在的是一個能成功在vc++下運行demo的插件化framework

     用了boost和stl,如果要深入了解core中的代碼,還需要對模板有了解, 水深請勿輕易嘗試

     當然有的看官會對core中的代碼非常熟悉,那可能你發(fā)現了, 我是山寨atl實現的

10. 今后改進的方向

     1. service如何釋放, 還在考慮是用野指針還是智能指針還是垃圾回收機制

     2. 錯誤處理

     3. 跨平臺和跨編譯器


想要了解更多詳情歡迎來電咨詢18678812288
登陸網址:m.h6244.cn。
聯系人:王經理。

若尔盖县| 河北省| 北碚区| 商洛市| 河北区| 永春县| 荃湾区| 老河口市| 大同县| 治县。| 凌云县| 浦县| 汤阴县| 岳阳市| 襄樊市| 盱眙县| 岢岚县| 镇赉县| 阿图什市| 肥东县| 双辽市| 凤山市| 宁安市| 馆陶县| 柘荣县| 罗江县| 和硕县| 泗洪县| 西峡县| 谷城县| 卢湾区| 万全县| 筠连县| 盐津县| 梁平县| 壶关县| 饶阳县| 井陉县| 东丽区| 高尔夫| 越西县|