一、插件架构初步介绍
想到写本博客,也没想到更好的名字,目前就先命这个名吧。说到插件架构,或许大部分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; }
这样整个插件的流程就走通了,万里长征第一步,现在只是实现了插件的注册和调用,下面一部就要实现怎么去实现具体插件和怎么实现插件之间的通信了。三、后记
本文主要是自己在探索C++插件实现机制的一些个人理解,虽然功能还很少,但是一些基本的东西还是有了。本插件实例的代码的下载地址为:http://download.csdn.net/detail/zhouxuguang236/7466253 注意下载后请先看说明。