1. 程式人生 > >C++外掛架構淺談與初步實現

C++外掛架構淺談與初步實現

轉自:http://blog.csdn.net/zhouxuguang236/article/details/29365261

一、外掛架構初步介紹

    想到寫本部落格,也沒想到更好的名字,目前就先命這個名吧。說到外掛架構,或許大部分IT從業者都聽過或者某些牛人也自己實現過穩定高效的外掛框架。目前有很多軟體以及庫都是基於外掛架構,例如PS、我所在行業的GIS軟體如Arcgis、QGIS、還比如開源圖形引擎OGRE以及OSG,這些都是外掛架構,通過外掛架構來進行功能的擴充套件。那到底什麼是外掛架構呢?我的理解是系統執行時在需要某個功能的時候動態載入的模組,外掛通常用動態連結庫實現,當然也可以用靜態庫,例如一些

嵌入式系統中,比如iOS據說就不支援動態連結庫。

我們為什麼要用外掛架構呢?

    現代軟體工程已經從原先的通用程式庫逐步過渡到應用程式框架,比如一些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介面的例項。

  1. #ifdef PLUGINCORE_EXPORTS       
  2.     #ifdef __GNUC__
  3.             #define PLUGINCORE_API __attribute__((dllexport))
  4.         #else
  5.             #define PLUGINCORE_API __declspec(dllexport)
  6.     #endif
  7.     #else
  8.         #ifdef __GNUC__
  9.             #define PLUGINCORE_API __attribute__((dllimport))
  10.         #else
  11.             #define PLUGINCORE_API __declspec(dllimport)
  12.     #endif
  13. #endif
  14. extern"C" PLUGINCORE_API PluginInstance *StartPlugin();  
  15. extern"C" PLUGINCORE_API void StopPlugin();  
上面的StartPlugin就是動態庫載入進來時候需要訪問的符號,這個函式裡面去啟動這個外掛,StopPlugin是解除安裝外掛時需要呼叫的函式。
這裡用到了動態庫的匯入,關於動態庫不同平臺上有不同的副檔名以及載入函式,為了保持API的跨平臺性,我這裡簡單的封裝了動態庫載入和解除安裝的過程,用typedef void* HLIB;表示動態庫的控制代碼。下面這個類也呈現給讀者,不妥的也給建議。
  1. #ifndef DYNAMICLIB_INCLUDE
  2. #define DYNAMICLIB_INCLUDE
  3. //動態庫載入,取函式地址,供內部使用
  4. #include "Export.h"
  5. class DynamicLib  
  6. {  
  7. public:  
  8.     DynamicLib(void);  
  9.     ~DynamicLib(void);  
  10.     constchar* GetName() const;  
  11.     //裝載動態庫
  12.     bool LoadLib(constchar* strLibName);  
  13.     void* GetSymbolAddress(constchar* strSymbolName) const;  
  14.     void FreeLib();  
  15. private:  
  16.     HLIB m_hDynLib;     //動態庫控制代碼
  17.     char* m_pszLibName; //動態庫名字
  18. };  
  19. #endif
  20. #include "DynamicLib.h"
  21. DynamicLib::DynamicLib(void)  
  22. {  
  23.     m_hDynLib = NULL;  
  24.     m_pszLibName = NULL;  
  25. }  
  26. DynamicLib::~DynamicLib(void)  
  27. {  
  28.     if (m_hDynLib != NULL)  
  29.     {  
  30.         FreeLib();  
  31.     }  
  32.     if (m_pszLibName != NULL)  
  33.     {  
  34.         free(m_pszLibName);  
  35.         m_pszLibName = NULL;  
  36.     }  
  37. }  
  38. constchar* DynamicLib::GetName() const
  39. {  
  40.     return m_pszLibName;  
  41. }  
  42. #if defined(__unix__) || defined(unix)
  43. #include <dlfcn.h>
  44. bool DynamicLib::LoadLib(constchar* strLibName)  
  45. {  
  46.     std::string strName = strLibName;  
  47.     strName += ".so";  
  48.     m_hDynLib = dlopen(strName.c_str(), RTLD_LAZY);  
  49.     if( pLibrary == NULL )  
  50.     {  
  51.         return 0;  
  52.     }  
  53.     m_pszLibName = strdup(strLibName);  
  54.     return( 1 );  
  55. }  
  56. void* DynamicLib::GetSymbolAddress(constchar* strSymbolName) const
  57. {  
  58.     void    *pSymbol = NULL;  
  59.     if (m_hDynLib != NULL)  
  60.     {  
  61.         pSymbol = dlsym(m_hDynLib,strSymbolName);  
  62.     }  
  63.     return pSymbol;  
  64. }  
  65. void DynamicLib::FreeLib()  
  66. {  
  67.     if (m_hDynLib != NULL)  
  68.     {  
  69.         dlclose(m_hDynLib);  
  70.         m_hDynLib = NULL;  
  71.     }  
  72.     if (m_pszLibName != NULL)  
  73.     {  
  74.         free(m_pszLibName);  
  75.         m_pszLibName = NULL;  
  76.     }  
  77. }  
  78. #endif
  79. #ifdef _WIN32
  80. #include <Windows.h>
  81. bool DynamicLib::LoadLib(constchar* strLibName)  
  82. {  
  83.     std::string strName = strLibName;  
  84.     strName += ".dll";  
  85.     m_hDynLib = LoadLibrary(strName.c_str());  
  86.     if (m_hDynLib != NULL)  
  87.     {  
  88.         m_pszLibName = strdup(strLibName);  
  89.         return 1;  
  90.     }  
  91.     return 0;  
  92. }  
  93. void* DynamicLib::GetSymbolAddress(constchar* strSymbolName)