UNIX下C++實現動態載入物件

weixin_33766168發表於2017-10-09

VC裡面實現動態物件載入已經不是什麼新鮮事情了,很多的plug-in技術就是例子。Unix下,通過動態載入so獲得一個物件也不是什麼難事,不過對這個物件的管理就是一件比較麻煩的事情了。一般的需求如下:
  有class TMyObj,準確說TMyObj應該是一個介面,根據不同具體情況會有不同的實現,例如 TMyObj1、TMyObj2等等……而這些TMyObj1和TMyObj2分別儲存在不同的so當中,需要根據不同的時候load不同的so,建立相應的物件。由於這些物件都擁有TMyObj的介面,所以對於外部來說對這些類的使用就像對TMyObj的使用一樣。
  看起來好像比較簡單,只要在so裡面引出一個函式: 

None.gifTMyObj * onCreateObject(void);


  而函式在so中的具體實現就是建立不同的子類,例如在obj1.so中:

None.gif  TMyObj * onCreateObject(void)
ExpandedBlockStart.gif   return new TMyObj1; }


  使用的時候只需要動態load入obj1.so,並且找到onCreateObject函式的入口,就可以建立一個具有TMyObj介面的TMyObj1了。
  至於釋放物件,一般有兩種方法:
方法一:
  so中包含另外一個函式:

None.gif  void onDestroyObj(void * p)
ExpandedBlockStart.gif  {
InBlock.gif    TMyObj1 * tp = (TMyObj1 *)p;
InBlock.gif    delete tp;
ExpandedBlockEnd.gif  }

None.gif


  從so中匯出該函式,並在刪除物件的時候呼叫。
方法二:
  TMyObj的解構函式宣告為虛擬函式,那麼從so匯出的onCreateObject()建立的物件,直接執行delete刪除就行了,由於解構函式是虛擬函式,編譯器會正確的呼叫TMyObj1的解構函式。
  當然,方法二是比較簡單而優雅的方法,既然對於C++來說介面就相當於純虛擬函式,多增加一個析構的虛擬函式又何妨呢。但是無論使用哪種方法,都要注意一個問題,就是載入的obj1.so的生命週期要比最後一個TMyObj1的生存週期長。即只要記憶體中還存在TMyObj1物件,obj1.so就要一直在記憶體中,不能解除安裝。要保證這個同步,是比較麻煩的事情。下面就說說我的解決方法:
  
  首先,要選擇一個通用的載入so的lib,這個可以參考一下common c++的DSO(在file.h)裡面。(不想使用common c++?我也只是說“參考”而已)。這個支援DLL和so,通過成員函式void *operator[](const char *);獲得指定的symbol的入口。
  其次,就要選擇一個通用的SmartPtr。這個當然Loki是首選,Loki的SmartPtr的靈活性比boost的smart_ptr強多了,而且Loki也小巧的多。
  然後就要實現一個簡單的so的manager,其實應該說是一個動態object的factory:

None.gifclass TObjFactory : protected DSO
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif    TObjFactory(void);
InBlock.gif    
InBlock.gif    void load(const std::string & strPath);
InBlock.gif    void * createObj(voidconst throw (TSOException);
InBlock.gif  protected:
InBlock.gif    typedef void * (*funcCreate)(void ** p);
InBlock.gif    funcCreate  m_pCreator;
ExpandedBlockEnd.gif  }
;


  可以想象這個類幹些什麼:load就是載入相應的so,然後獲得so中onCreateObject函式的入口,並賦給成員m_pCreator。而createObj就是呼叫m_pCreator建立物件。不過有所不同的是 m_pCreator所指向的函式形式是void * funcCreate(void ** p),而多出來void **p用處就是可以讓so中的建構函式中產生的exception能夠傳遞出來。這個不能說不是so的麻煩之處,so中函式的exception不能被外部捕獲,所以只好這樣子做了。
  現在,關鍵的地方來了,就是要保證這個TObjFactory的生存週期了。選擇Loki的SmartPtr就能派上用場了。
  Loki的SmartPtr可以自己選定適用的StoragePolicy,這正是我們需要的,參考DefaultSPStorage,可以做我們的TMySOStoragePolicy:

None.giftemplate<class T>
None.gif   class TMySOStoragePolicy
ExpandedBlockStart.gif   {
InBlock.gif    dot.gif..
InBlock.gif   protected:
InBlock.gif    void Destroy()
ExpandedSubBlockStart.gif        
InBlock.gif         delete pointee_;
InBlock.gif         m_pFactory = SmartPtr<TObjFactory>();
ExpandedSubBlockEnd.gif        }
   
InBlock.gif   private:
InBlock.gif    SmartPtr<TObjFactory> m_pFactory;
InBlock.gif    StoredType       pointee_;
ExpandedBlockEnd.gif   }
;
None.gif

  顯而易見,這樣做的目的就是要保證釋放指標的時候就減少TObjFactory的引用計數。
  好了,現在就是主角了:
  

None.giftemplate<class T>
None.gif  class TDObj : public SmartPtr<T,RefCounted,DisallowConversion,AssertCheck,TMySOStoragePolicy>
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif    TDObj(void);
InBlock.gif    TDObj(const TDObj & obj);
InBlock.gif    dot.gif..
InBlock.gif    
InBlock.gif  protected:
InBlock.gif    friend class TDObjManager;
InBlock.gif    TDObj(T * p, SmartPtr<TObjFactory> pManager);
ExpandedBlockEnd.gif  }
;
None.gif  
None.gif  class TDObjManager
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif    dot.gifdot.gif
InBlock.gif    template<class T>
InBlock.gif     static TDObj<T>  createObj(const std::string & strKeyName)
ExpandedSubBlockStart.gif     {
InBlock.gif       SmartPtr<TObjFactory> pFactory = getFactoryByName(strKeyName);
InBlock.gif       //這裡面可以做很多事情了,例如訪問記憶體,查詢相應的Factory;或者讀取配置檔案、讀入新的so並建立新的Factory。
InBlock.gif       
//或者根據一些淘汰演算法,先淘汰記憶體的Factory,然後重新載入新的Factory等等。
InBlock.gif
       std::auto_ptr<T> _au( static_cast<T *>(pFactory->createObj()) );
InBlock.gif       return TDObj<T>( _au.release(), pFactory);
ExpandedSubBlockEnd.gif     }

ExpandedBlockEnd.gif  }
;
None.gif

  
  以後用起來就簡單多了:
  

None.gifclass TMyObj
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif   virtual ~TMyObj(void);
InBlock.gif   virtual int func(void) = 0;
ExpandedBlockEnd.gif  }
;
None.gif  
None.gif  TDObj<TMyObj> obj1 = TDObjManager::createObj<TMyObj>( "obj1.so") );
None.gif  TDObj<TMyObj> obj2 = TDObjManager::createObj<TMyObj>( "obj2.so") );
None.gif  
None.gif  cout << obj1->func() << endl;
None.gif  cout << obj2->func() << endl;
None.gif


  說了這麼久,都是主程式的呼叫,而so中應該如何呢?其實也很簡單:
  

None.gifclass TMyObj1 : public TMyObj
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif    TMyObj1(void);
InBlock.gif    ~TMyObj1(void);
InBlock.gif
InBlock.gif    static void onStaticInit(void);
InBlock.gif    static void onStaticDestroy(void);
InBlock.gif    static const char * getVersion(void);
InBlock.gif    static const char * getObjectName(void);
InBlock.gif    
InBlock.gif    virtual int  func(void);
ExpandedBlockEnd.gif  }
;
None.gif  
None.gif  DECLARE_SO_INTERFACE(TMyObj1);
None.gif
None.gif

  
  DECLARE_SO_INTERFACE其實是一個為了方便編寫程式而定義的巨集:

None.gif#define DECLARE_SO_INTERFACE(x) extern "C" { \
None.gif    void onInstallDLL(void);   \
None.gif    void onUninstallDLL(void);   \
None.gif    const char * onGetVersion(void); \
None.gif    const char * onObjectName(void); \
None.gif    void * onCreateObject(void ** ppException);  \
None.gif   }; \
ExpandedBlockStart.gif   void onInstallDLL(void{ x::onStaticInit(); }    \
ExpandedBlockStart.gif   void onUninstallDLL(void{ x::onStaticDestroy(); }  \
ExpandedBlockStart.gif   const char * onGetVersion(voidreturn x::getVersion(); }  \
ExpandedBlockStart.gif   const char * onObjectName(voidreturn x::getObjectName(); } \
ExpandedBlockStart.gif   void * onCreateObject(void ** pException) { \
ExpandedSubBlockStart.gif    try { \
InBlock.gif     *pException = NULL; x * p = new x(); return (void *)p; \
ExpandedSubBlockStart.gif    }
catch(std::exception & e) { \
InBlock.gif     *pException = new std::exception(e); \
InBlock.gif     return NULL;  \
ExpandedSubBlockEnd.gif    }
 \
ExpandedBlockEnd.gif   }

None.gif

   
  可以看到除了匯出onCreateObject函式以外,還匯出了:
  TMyObj1::onStaticInit用於載入so的時候執行初始化操作;
  TMyObj1::onStaticDestroy用於解除安裝so的時候執行清理操作;
  TMyObj1::getVersion 獲得物件的版本資訊
  TMyObj1::onObjectName 獲得物件名資訊等
  可以擴充套件前面的TObjFactory,實現這些功能。

  同理,我們可以做obj2.so:

None.gifclass TMyObj2 : public TMyObj
ExpandedBlockStart.gif  {
InBlock.gif  public:
InBlock.gif   TMyObj2(void);
InBlock.gif   ~TMyObj2(void);
InBlock.gif  
InBlock.gif   static void onStaticInit(void);
InBlock.gif   static void onStaticDestroy(void);
InBlock.gif   static const char * getVersion(void);
InBlock.gif   static const char * getObjectName(void);
InBlock.gif  
InBlock.gif   virtual int  func(void);
ExpandedBlockEnd.gif  }
;
None.gif  
None.gif  DECLARE_SO_INTERFACE(TMyObj2);
None.gif

  
  
  另外,一個值得討論的問題是:C++由於沒有反射機制,所以無法實現設值注入和構造注入,只能實現介面注入。不過一般來說也已經足夠使用了。

相關文章