UNIX下C++實現動態載入物件
VC裡面實現動態物件載入已經不是什麼新鮮事情了,很多的plug-in技術就是例子。Unix下,通過動態載入so獲得一個物件也不是什麼難事,不過對這個物件的管理就是一件比較麻煩的事情了。一般的需求如下:
有class TMyObj,準確說TMyObj應該是一個介面,根據不同具體情況會有不同的實現,例如 TMyObj1、TMyObj2等等……而這些TMyObj1和TMyObj2分別儲存在不同的so當中,需要根據不同的時候load不同的so,建立相應的物件。由於這些物件都擁有TMyObj的介面,所以對於外部來說對這些類的使用就像對TMyObj的使用一樣。
看起來好像比較簡單,只要在so裡面引出一個函式:
而函式在so中的具體實現就是建立不同的子類,例如在obj1.so中:
{ return new TMyObj1; }
使用的時候只需要動態load入obj1.so,並且找到onCreateObject函式的入口,就可以建立一個具有TMyObj介面的TMyObj1了。
至於釋放物件,一般有兩種方法:
方法一:
so中包含另外一個函式:
{
TMyObj1 * tp = (TMyObj1 *)p;
delete tp;
}
從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:
{
public:
TObjFactory(void);
void load(const std::string & strPath);
void * createObj(void) const throw (TSOException);
protected:
typedef void * (*funcCreate)(void ** p);
funcCreate m_pCreator;
};
可以想象這個類幹些什麼: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:
class TMySOStoragePolicy
{
..
protected:
void Destroy()
{
delete pointee_;
m_pFactory = SmartPtr<TObjFactory>();
}
private:
SmartPtr<TObjFactory> m_pFactory;
StoredType pointee_;
};
顯而易見,這樣做的目的就是要保證釋放指標的時候就減少TObjFactory的引用計數。
好了,現在就是主角了:
class TDObj : public SmartPtr<T,RefCounted,DisallowConversion,AssertCheck,TMySOStoragePolicy>
{
public:
TDObj(void);
TDObj(const TDObj & obj);
..
protected:
friend class TDObjManager;
TDObj(T * p, SmartPtr<TObjFactory> pManager);
};
class TDObjManager
{
public:
template<class T>
static TDObj<T> createObj(const std::string & strKeyName)
{
SmartPtr<TObjFactory> pFactory = getFactoryByName(strKeyName);
//這裡面可以做很多事情了,例如訪問記憶體,查詢相應的Factory;或者讀取配置檔案、讀入新的so並建立新的Factory。
//或者根據一些淘汰演算法,先淘汰記憶體的Factory,然後重新載入新的Factory等等。
std::auto_ptr<T> _au( static_cast<T *>(pFactory->createObj()) );
return TDObj<T>( _au.release(), pFactory);
}
};
以後用起來就簡單多了:
{
public:
virtual ~TMyObj(void);
virtual int func(void) = 0;
};
TDObj<TMyObj> obj1 = TDObjManager::createObj<TMyObj>( "obj1.so") );
TDObj<TMyObj> obj2 = TDObjManager::createObj<TMyObj>( "obj2.so") );
cout << obj1->func() << endl;
cout << obj2->func() << endl;
說了這麼久,都是主程式的呼叫,而so中應該如何呢?其實也很簡單:
{
public:
TMyObj1(void);
~TMyObj1(void);
static void onStaticInit(void);
static void onStaticDestroy(void);
static const char * getVersion(void);
static const char * getObjectName(void);
virtual int func(void);
};
DECLARE_SO_INTERFACE(TMyObj1);
DECLARE_SO_INTERFACE其實是一個為了方便編寫程式而定義的巨集:
void onInstallDLL(void); \
void onUninstallDLL(void); \
const char * onGetVersion(void); \
const char * onObjectName(void); \
void * onCreateObject(void ** ppException); \
}; \
void onInstallDLL(void) { x::onStaticInit(); } \
void onUninstallDLL(void) { x::onStaticDestroy(); } \
const char * onGetVersion(void) { return x::getVersion(); } \
const char * onObjectName(void) { return x::getObjectName(); } \
void * onCreateObject(void ** pException) { \
try { \
*pException = NULL; x * p = new x(); return (void *)p; \
}catch(std::exception & e) { \
*pException = new std::exception(e); \
return NULL; \
} \
}
可以看到除了匯出onCreateObject函式以外,還匯出了:
TMyObj1::onStaticInit用於載入so的時候執行初始化操作;
TMyObj1::onStaticDestroy用於解除安裝so的時候執行清理操作;
TMyObj1::getVersion 獲得物件的版本資訊
TMyObj1::onObjectName 獲得物件名資訊等
可以擴充套件前面的TObjFactory,實現這些功能。
同理,我們可以做obj2.so:
{
public:
TMyObj2(void);
~TMyObj2(void);
static void onStaticInit(void);
static void onStaticDestroy(void);
static const char * getVersion(void);
static const char * getObjectName(void);
virtual int func(void);
};
DECLARE_SO_INTERFACE(TMyObj2);
另外,一個值得討論的問題是:C++由於沒有反射機制,所以無法實現設值注入和構造注入,只能實現介面注入。不過一般來說也已經足夠使用了。
相關文章
- UNIX巧妙實現動態路徑
- js動態載入實現提高網頁載入速度JS網頁
- 優雅的實現動態載入 css、jsCSSJS
- 動態載入css方法實現和深入解析CSS
- jquery如何實現動態載入CSS檔案jQueryCSS
- 藉助 Webpack 靜態分析能力實現程式碼動態載入Web
- Umi + qiankun 實現動態載入子應用路由路由
- 實現js檔案動態載入程式碼例項JS
- vue 實現tab切換動態載入不同的元件Vue元件
- 簡單實現Crystal Report的動態載入 (轉)
- Linux下的靜態庫、動態庫和動態載入庫Linux
- DLL動態庫動態載入
- Spring Boot 如何熱載入 jar 實現動態外掛?Spring BootJAR
- Spring Boot 如何熱載入jar實現動態外掛?Spring BootJAR
- Java動態獲取某個介面下所有的實現類物件集合Java物件
- 滾動載入圖片(懶載入)實現原理
- 用C++實現下載檔案的功能C++
- Spring Cloud Nacos實現動態配置載入的原始碼分析SpringCloud原始碼
- js實現的動態載入css檔案簡單介紹JSCSS
- javascript實現的動態載入css檔案程式碼例項JavaScriptCSS
- js實現的動態載入css外部樣式表程式碼JSCSS
- 利用html5實現的loadding動態載入效果HTML
- jQuery實現的非同步動態載入css和js檔案jQuery非同步CSSJS
- vue 動態載入元件Vue元件
- Java動態載入類Java
- jQuery實現的動態載入指令碼檔案程式碼例項jQuery指令碼
- C/C++ 實現動態資原始檔釋放C++
- Unix下設計動態庫的方法《精通Unix下C語言程式設計與專案實踐》(一)薦C語言程式設計
- Go實現啟動引數載入Go
- JavaScript代理模式,怎麼實現物件的動態代理?JavaScript模式物件
- C#動態建立介面的實現例項物件C#物件
- java實現下載器(以及建立一個URL物件)Java物件
- 狀態模式(c++實現)模式C++
- C++中物件的動態建立與釋放C++物件
- React Native 中實現動態匯入React Native
- 實現動態自動匹配輸入的內容
- linux 下動態連結實現原理Linux
- 實現js檔案動態載入的幾種方式簡單介紹JS