C++外掛架構淺談與初步實現
轉自:http://blog.csdn.net/zhouxuguang236/article/details/29365261
一、外掛架構初步介紹
想到寫本部落格,也沒想到更好的名字,目前就先命這個名吧。說到外掛架構,或許大部分IT從業者都聽過或者某些牛人也自己實現過穩定高效的外掛框架。目前有很多軟體以及庫都是基於外掛架構,例如PS、我所在行業的GIS軟體如Arcgis、QGIS、還比如開源圖形引擎OGRE以及OSG,這些都是外掛架構,通過外掛架構來進行功能的擴充套件。那到底什麼是外掛架構呢?我的理解是系統執行時在需要某個功能的時候動態載入的模組,外掛通常用動態連結庫實現,當然也可以用靜態庫,例如一些
我們為什麼要用外掛架構呢?
現代軟體工程已經從原先的通用程式庫逐步過渡到應用程式框架,比如一些C++的庫,這些庫都是實現某一領域特定功能的,比如GDAL,實現各種空間資料格式的解析,這種庫通常不是基於外掛架構;應用程式框架比如Java裡面的三大框架。首先,假設一個場景,以C++開發應用程式為例,我們的架構是基於APP+DLL的傳統架構,所有的功能糅雜在一起,這樣隨著系統的日益龐大,各種模組之間耦合在一起,當修改其中一個模組時,其他模組也跟著一起受到影響,假如這兩個模組式不同的開發人員負責的,就需要事先溝通好,這樣就造成了修改維護的困難。那怎麼解決這個問題,外掛架構是一種選擇。那麼外掛架構究竟有哪些好處呢?
1、方便功能的擴充套件。比如在GIS引擎設計中,一般的做法是不把資料格式的解析放在GIS核心中,只是在核心中定義一些通用的資料載入解析的介面,然後通過外掛來實現某一特定格式的解析,這樣就可以擴充套件各種不同的資料格式,也方便移植。
2、更新量小。當底層的介面不變時,以外掛形式存在的功能很容易獨立於應用程式而更新,只需要引入新版本的外掛即可。相比釋出整個應用程式,這種方式的更新量小很多。
3、降低模組之間依賴,可以支援並行開發。比如兩個開發人員開發不同功能的外掛,他們就可以只關心自己外掛功能的實現,可以實現快速開發。
4、面向未來。當你的API到達一定穩定程度後,這時候你的API可能沒有更新的必要了。然而API的功能可以通過外掛來進一步演化,這使得API可以再長時期內保持其可用性和適用性,使得你的API可以不被拋棄。
二、外掛需要設計的東西
這裡我只考慮動態連結庫的情況。我們需要一種載入動態連結庫並訪問其中符號的機制。在一般的外掛系統中,外掛API和外掛管理器是必須要設計的。
外掛API。這個是建立外掛必須要提供的介面,C++實現的話就是一個抽象類,裡面只提供介面,具體功能交給外掛實現。這部分程式碼應該在你的核心API之內。
外掛管理器。外掛管理器負責外掛的載入、註冊以及解除安裝等功能,管理外掛的整個生命週期。該類一般都設計為單例模式,其實大部分資源管理的類一般都設計為單例模式。
外掛和核心API之間的關係如下。
當我們把外掛載入進來後,這時候還不知道外掛怎麼執行,為了讓外掛正常的執行,這時候需要知道核心API應該訪問哪個具體的函式實現外掛的正常運轉,定義的入口函式,這個可以通過匯出標準C介面方式實現外掛的初始化、停止等操作。
下面是具體的定義匯出符號和標準C介面的例項。
- #ifdef PLUGINCORE_EXPORTS
- #ifdef __GNUC__
- #define PLUGINCORE_API __attribute__((dllexport))
- #else
- #define PLUGINCORE_API __declspec(dllexport)
- #endif
- #else
- #ifdef __GNUC__
- #define PLUGINCORE_API __attribute__((dllimport))
- #else
- #define PLUGINCORE_API __declspec(dllimport)
- #endif
- #endif
- extern"C" PLUGINCORE_API PluginInstance *StartPlugin();
- extern"C" PLUGINCORE_API void StopPlugin();
上面的StartPlugin就是動態庫載入進來時候需要訪問的符號,這個函式裡面去啟動這個外掛,StopPlugin是解除安裝外掛時需要呼叫的函式。
這裡用到了動態庫的匯入,關於動態庫不同平臺上有不同的副檔名以及載入函式,為了保持API的跨平臺性,我這裡簡單的封裝了動態庫載入和解除安裝的過程,用typedef void* HLIB;表示動態庫的控制代碼。下面這個類也呈現給讀者,不妥的也給建議。
- #ifndef DYNAMICLIB_INCLUDE
- #define DYNAMICLIB_INCLUDE
- //動態庫載入,取函式地址,供內部使用
- #include "Export.h"
- class DynamicLib
- {
- public:
- DynamicLib(void);
- ~DynamicLib(void);
- constchar* GetName() const;
- //裝載動態庫
- bool LoadLib(constchar* strLibName);
- void* GetSymbolAddress(constchar* strSymbolName) const;
- void FreeLib();
- private:
- HLIB m_hDynLib; //動態庫控制代碼
- char* m_pszLibName; //動態庫名字
- };
- #endif
- #include "DynamicLib.h"
- DynamicLib::DynamicLib(void)
- {
- m_hDynLib = NULL;
- m_pszLibName = NULL;
- }
- DynamicLib::~DynamicLib(void)
- {
- if (m_hDynLib != NULL)
- {
- FreeLib();
- }
- if (m_pszLibName != NULL)
- {
- free(m_pszLibName);
- m_pszLibName = NULL;
- }
- }
- constchar* DynamicLib::GetName() const
- {
- return m_pszLibName;
- }
- #if defined(__unix__) || defined(unix)
- #include <dlfcn.h>
- bool DynamicLib::LoadLib(constchar* strLibName)
- {
- std::string strName = strLibName;
- strName += ".so";
- m_hDynLib = dlopen(strName.c_str(), RTLD_LAZY);
- if( pLibrary == NULL )
- {
- return 0;
- }
- m_pszLibName = strdup(strLibName);
- return( 1 );
- }
- void* DynamicLib::GetSymbolAddress(constchar* strSymbolName) const
- {
- void *pSymbol = NULL;
- if (m_hDynLib != NULL)
- {
- pSymbol = dlsym(m_hDynLib,strSymbolName);
- }
- return pSymbol;
- }
- void DynamicLib::FreeLib()
- {
- if (m_hDynLib != NULL)
- {
- dlclose(m_hDynLib);
- m_hDynLib = NULL;
- }
- if (m_pszLibName != NULL)
- {
- free(m_pszLibName);
- m_pszLibName = NULL;
- }
- }
- #endif
- #ifdef _WIN32
- #include <Windows.h>
- bool DynamicLib::LoadLib(constchar* strLibName)
- {
- std::string strName = strLibName;
- strName += ".dll";
- m_hDynLib = LoadLibrary(strName.c_str());
- if (m_hDynLib != NULL)
- {
- m_pszLibName = strdup(strLibName);
- return 1;
- }
- return 0;
- }
- void* DynamicLib::GetSymbolAddress(constchar* strSymbolName)