一、插件架構初步介紹
想到寫本博客,也沒想到更好的名字,目前就先命這個名吧。說到插件架構,或許大部分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接口的實例。
#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);
const char* GetName() const;
//裝載動態庫
bool LoadLib(const char* strLibName);
void* GetSymbolAddress(const char* 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;
}
}
const char* DynamicLib::GetName() const
{
return m_pszLibName;
}
#if defined(__unix__) || defined(unix)
#include <dlfcn.h>
bool DynamicLib::LoadLib(const char* 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(const char* 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(const char* 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(const char* strSymbolName) const
{
if (m_hDynLib != NULL)
{
return (void*)GetProcAddress((HMODULE)m_hDynLib,strSymbolName);
}
return NULL;
}
void DynamicLib::FreeLib()
{
if (m_hDynLib != NULL)
{
FreeLibrary((HMODULE)m_hDynLib);
m_hDynLib = NULL;
}
if (m_pszLibName != NULL)
{
free(m_pszLibName);
m_pszLibName = NULL;
}
}
#endif
差點忘了,插件系統必須設計的插件API和插件管理器都還沒說,其實插件管理器是插件實例的集合,插件管理器提供了加載和卸載某一插件的功能,下面是插件API以及插件管理器的實例。
#ifndef PLUGININSTANCE_INCLUDE
#define PLUGININSTANCE_INCLUDE
#include "Export.h"
class PLUGINCORE_API PluginInstance
{
public:
explicit PluginInstance(const std::string &strName);
virtual ~PluginInstance(void);
virtual bool Load() = 0;
virtual bool UnLoad() = 0;
//返回插件名字,帶後綴,如dll等
virtual std::string GetFileName() const = 0;
//返回插件的名字,不帶後綴
virtual std::string GetDisplayName() const = 0;
private:
PluginInstance(const PluginInstance &rhs);
const PluginInstance &operator=(const PluginInstance &rhs);
};
//插件加載和卸載時調用的函數
typedef PluginInstance *( *START_PLUGIN_FUN )();
typedef void( *STOP_PLUGIN_FUN )();
#endif
#ifndef PLUGINMANAGER_INCLUDE
#define PLUGINMANAGER_INCLUDE
#include "Export.h"
class PluginInstance;
class DynamicLib;
class PLUGINCORE_API PluginManager
{
public:
static PluginManager &GetInstance();
bool LoadAll();
PluginInstance* Load(const std::string &strName,int &errCode);
bool LoadPlugin(PluginInstance *pPlugin);
bool UnLoadAll();
bool UnLoad(const std::string &strName);
bool UnLoadPlugin(PluginInstance *pPlugin);
std::vector<PluginInstance *> GetAllPlugins();
private:
PluginManager(void);
~PluginManager(void);
PluginManager(const PluginManager &rhs);
const PluginManager &operator=(const PluginManager &rhs);
std::vector<PluginInstance *> m_vecPlugins; //插件實例句柄
std::map<std::string,DynamicLib *> m_vecPluginLibs; //插件模塊句柄
};
#endif
插件管理器可以通過系統的配置文件預先配置好加在哪些插件,一般可用XML配置。
有了上面的介紹之後,就該開始介紹整個插件加載和卸載的流程了,先來介紹怎麼進行加載的。加載的函數式PluginInstance* Load(const std::string &strName,int &errCode);
這個函數的功能是傳入一個不帶後綴的插件動態庫名字,如果插件管理器中沒有該插件就加載到系統中,並在插件列表中註冊,若存在的話就在插件列表中訪問該名字的插件,返回該插件實例。該函數的實現如下:
PluginInstance* PluginManager::Load(const std::string &strName,int &errCode)
{
std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.find(strName);
if (iter == m_vecPluginLibs.end()) //不存在就需要插入
{
DynamicLib* pLib = new DynamicLib;
if (pLib != NULL)
{
pLib->LoadLib(strName.c_str());
m_vecPluginLibs.insert(make_pair(strName,pLib));
START_PLUGIN_FUN pFun = (START_PLUGIN_FUN)pLib->GetSymbolAddress("StartPlugin");
if (pFun != NULL)
{
PluginInstance* pPlugin = pFun();
errCode = 1;
return pPlugin;
}
errCode = 0;
return NULL;
}
}
else if (iter != m_vecPluginLibs.end()) //如果存在,在插件列表裏面尋找名字是strName的插件
{
for (int i = 0; i < m_vecPlugins.size(); i ++)
{
if (strName == m_vecPlugins[i]->GetDisplayName())
{
errCode = 1;
return m_vecPlugins[i];
}
}
}
errCode = 0;
return NULL;
}
從上面的過程可以看出,首先檢測插件是否存在,如果存在,就在插件列表中查找該插件直接返回該插件實例。如果不存在,就需要先創建一個DynamicLib* pLib = new DynamicLib;,然後通過pLib導入名字爲strName的插件動態庫文件,再將這個模塊句柄和名字加入到模塊列表中,然後通過DynamicLib的GetSymbolAddress的函數獲得函數名爲StartPlugin的函數指針,最後通過這個函數指針進行回調返回插件實例以及將該插件註冊到插件列表中,這個函數的在插件中的具體實現如下:
static PluginInstance *pPlugin = NULL; PluginInstance* StartPlugin() { pPlugin = new ShapePlugin("shapefile"); PluginManager::GetInstance().LoadPlugin(pPlugin); return pPlugin; }
bool PluginManager::UnLoad(const std::string &strName)
{
std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.begin();
for (; iter != m_vecPluginLibs.end(); ++iter )
{
DynamicLib *pLib = iter->second;
if (NULL == pLib)
{
continue;
}
if (strcmp(pLib->GetName(),strName.c_str()) == 0)
{
STOP_PLUGIN_FUN pFun = (STOP_PLUGIN_FUN)pLib->GetSymbolAddress("StopPlugin");
if (pFun != NULL)
{
pFun();
}
pLib->FreeLib();
delete pLib;
//然後從列表中刪除
m_vecPluginLibs.erase(iter);
return true;
}
}
return false;
}
ShapePlugin就是繼承於PluginInstance的一個插件。
插件卸載的過程正好相反,下面也給出實現代碼。
bool PluginManager::UnLoad(const std::string &strName)
{
std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.begin();
for (; iter != m_vecPluginLibs.end(); ++iter )
{
DynamicLib *pLib = iter->second;
if (NULL == pLib)
{
continue;
}
if (strcmp(pLib->GetName(),strName.c_str()) == 0)
{
STOP_PLUGIN_FUN pFun = (STOP_PLUGIN_FUN)pLib->GetSymbolAddress("StopPlugin");
if (pFun != NULL)
{
pFun();
}
pLib->FreeLib();
delete pLib;
//然後從列表中刪除
m_vecPluginLibs.erase(iter);
return true;
}
}
return false;
}
這樣整個插件的流程就走通了,萬里長征第一步,現在只是實現了插件的註冊和調用,下面一部就要實現怎麼去實現具體插件和怎麼實現插件之間的通信了。