`

C++插件架构浅谈与初步实现

    博客分类:
  • C#
 
阅读更多

一、插件架构初步介绍

    想到写本博客,也没想到更好的名字,目前就先命这个名吧。说到插件架构,或许大部分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接口的实例。

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  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.   
  15. extern "C" PLUGINCORE_API PluginInstance *StartPlugin();  
  16.   
  17. extern "C" PLUGINCORE_API void StopPlugin();  
上面的StartPlugin就是动态库加载进来时候需要访问的符号,这个函数里面去启动这个插件,StopPlugin是卸载插件时需要调用的函数。
这里用到了动态库的导入,关于动态库不同平台上有不同的扩展名以及加载函数,为了保持API的跨平台性,我这里简单的封装了动态库加载和卸载的过程,用typedef void* HLIB;表示动态库的句柄。下面这个类也呈现给读者,不妥的也给建议。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. #ifndef DYNAMICLIB_INCLUDE  
  2. #define DYNAMICLIB_INCLUDE  
  3.   
  4. //动态库加载,取函数地址,供内部使用  
  5. #include "Export.h"  
  6.   
  7. class DynamicLib  
  8. {  
  9. public:  
  10.     DynamicLib(void);  
  11.     ~DynamicLib(void);  
  12.   
  13.     const char* GetName() const;  
  14.   
  15.     //装载动态库  
  16.     bool LoadLib(const char* strLibName);  
  17.   
  18.     void* GetSymbolAddress(const char* strSymbolName) const;  
  19.   
  20.     void FreeLib();  
  21.   
  22. private:  
  23.     HLIB m_hDynLib;     //动态库句柄  
  24.     char* m_pszLibName; //动态库名字  
  25. };  
  26.   
  27. #endif  
  28.   
  29. #include "DynamicLib.h"  
  30.   
  31. DynamicLib::DynamicLib(void)  
  32. {  
  33.     m_hDynLib = NULL;  
  34.     m_pszLibName = NULL;  
  35. }  
  36.   
  37. DynamicLib::~DynamicLib(void)  
  38. {  
  39.     if (m_hDynLib != NULL)  
  40.     {  
  41.         FreeLib();  
  42.     }  
  43.   
  44.     if (m_pszLibName != NULL)  
  45.     {  
  46.         free(m_pszLibName);  
  47.         m_pszLibName = NULL;  
  48.     }  
  49. }  
  50.   
  51. const char* DynamicLib::GetName() const  
  52. {  
  53.     return m_pszLibName;  
  54. }  
  55.   
  56. #if defined(__unix__) || defined(unix)  
  57. #include <dlfcn.h>  
  58.   
  59. bool DynamicLib::LoadLib(const char* strLibName)  
  60. {  
  61.     std::string strName = strLibName;  
  62.     strName += ".so";  
  63.     m_hDynLib = dlopen(strName.c_str(), RTLD_LAZY);  
  64.     if( pLibrary == NULL )  
  65.     {  
  66.         return 0;  
  67.     }  
  68.     m_pszLibName = strdup(strLibName);  
  69.   
  70.     return( 1 );  
  71. }  
  72.   
  73. void* DynamicLib::GetSymbolAddress(const char* strSymbolName) const  
  74. {  
  75.     void    *pSymbol = NULL;  
  76.   
  77.     if (m_hDynLib != NULL)  
  78.     {  
  79.         pSymbol = dlsym(m_hDynLib,strSymbolName);  
  80.     }  
  81.   
  82.     return pSymbol;  
  83. }  
  84.   
  85. void DynamicLib::FreeLib()  
  86. {  
  87.     if (m_hDynLib != NULL)  
  88.     {  
  89.         dlclose(m_hDynLib);  
  90.         m_hDynLib = NULL;  
  91.     }  
  92.   
  93.     if (m_pszLibName != NULL)  
  94.     {  
  95.         free(m_pszLibName);  
  96.         m_pszLibName = NULL;  
  97.     }  
  98. }  
  99.   
  100. #endif  
  101.   
  102. #ifdef _WIN32  
  103. #include <Windows.h>  
  104.   
  105. bool DynamicLib::LoadLib(const char* strLibName)  
  106. {  
  107.     std::string strName = strLibName;  
  108.     strName += ".dll";  
  109.     m_hDynLib = LoadLibrary(strName.c_str());  
  110.     if (m_hDynLib != NULL)  
  111.     {  
  112.         m_pszLibName = strdup(strLibName);  
  113.         return 1;  
  114.     }  
  115.   
  116.     return 0;  
  117. }  
  118.   
  119. void* DynamicLib::GetSymbolAddress(const char* strSymbolName) const  
  120. {  
  121.     if (m_hDynLib != NULL)  
  122.     {  
  123.         return (void*)GetProcAddress((HMODULE)m_hDynLib,strSymbolName);  
  124.     }  
  125.   
  126.     return NULL;  
  127. }  
  128.   
  129. void DynamicLib::FreeLib()  
  130. {  
  131.     if (m_hDynLib != NULL)  
  132.     {  
  133.         FreeLibrary((HMODULE)m_hDynLib);  
  134.         m_hDynLib = NULL;  
  135.     }  
  136.   
  137.     if (m_pszLibName != NULL)  
  138.     {  
  139.         free(m_pszLibName);  
  140.         m_pszLibName = NULL;  
  141.     }  
  142. }  
  143.   
  144. #endif  
差点忘了,插件系统必须设计的插件API和插件管理器都还没说,其实插件管理器是插件实例的集合,插件管理器提供了加载和卸载某一插件的功能,下面是插件API以及插件管理器的实例。

 


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. #ifndef PLUGININSTANCE_INCLUDE  
  2. #define PLUGININSTANCE_INCLUDE  
  3.   
  4.   
  5. #include "Export.h"  
  6.   
  7. class PLUGINCORE_API PluginInstance  
  8. {  
  9. public:  
  10.     explicit PluginInstance(const std::string &strName);  
  11.     virtual ~PluginInstance(void);  
  12.   
  13.     virtual bool Load() = 0;  
  14.   
  15.     virtual bool UnLoad() = 0;  
  16.   
  17.     //返回插件名字,带后缀,如dll等  
  18.     virtual std::string GetFileName() const = 0;  
  19.   
  20.     //返回插件的名字,不带后缀  
  21.     virtual std::string GetDisplayName() const = 0;  
  22.   
  23. private:  
  24.     PluginInstance(const PluginInstance &rhs);  
  25.     const PluginInstance &operator=(const PluginInstance &rhs);  
  26. };  
  27.   
  28. //插件加载和卸载时调用的函数  
  29. typedef PluginInstance *( *START_PLUGIN_FUN )();  
  30. typedef void( *STOP_PLUGIN_FUN )();  
  31.   
  32. #endif  
  33.   
  34.   
  35.   
  36. #ifndef PLUGINMANAGER_INCLUDE   
  37. #define PLUGINMANAGER_INCLUDE  
  38.   
  39. #include "Export.h"  
  40.   
  41. class PluginInstance;  
  42. class DynamicLib;  
  43.   
  44. class PLUGINCORE_API PluginManager  
  45. {  
  46. public:  
  47.     static PluginManager &GetInstance();  
  48.   
  49.     bool LoadAll();  
  50.   
  51.     PluginInstance* Load(const std::string &strName,int &errCode);  
  52.   
  53.     bool LoadPlugin(PluginInstance *pPlugin);  
  54.   
  55.     bool UnLoadAll();  
  56.   
  57.     bool UnLoad(const std::string &strName);  
  58.   
  59.     bool UnLoadPlugin(PluginInstance *pPlugin);  
  60.   
  61.     std::vector<PluginInstance *> GetAllPlugins();  
  62.   
  63. private:  
  64.     PluginManager(void);  
  65.     ~PluginManager(void);  
  66.     PluginManager(const PluginManager &rhs);  
  67.     const PluginManager &operator=(const PluginManager &rhs);  
  68.   
  69.     std::vector<PluginInstance *> m_vecPlugins;   //插件实例句柄  
  70.     std::map<std::string,DynamicLib *> m_vecPluginLibs;   //插件模块句柄  
  71. };  
  72.   
  73. #endif  


插件管理器可以通过系统的配置文件预先配置好加在哪些插件,一般可用XML配置。

 

 

 

有了上面的介绍之后,就该开始介绍整个插件加载和卸载的流程了,先来介绍怎么进行加载的。加载的函数式PluginInstance* Load(const std::string &strName,int &errCode);
这个函数的功能是传入一个不带后缀的插件动态库名字,如果插件管理器中没有该插件就加载到系统中,并在插件列表中注册,若存在的话就在插件列表中访问该名字的插件,返回该插件实例。该函数的实现如下:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. PluginInstance* PluginManager::Load(const std::string &strName,int &errCode)  
  2. {  
  3.     std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.find(strName);  
  4.     if (iter == m_vecPluginLibs.end())  //不存在就需要插入  
  5.     {  
  6.         DynamicLib* pLib = new DynamicLib;  
  7.         if (pLib != NULL)  
  8.         {  
  9.             pLib->LoadLib(strName.c_str());  
  10.             m_vecPluginLibs.insert(make_pair(strName,pLib));  
  11.             START_PLUGIN_FUN pFun = (START_PLUGIN_FUN)pLib->GetSymbolAddress("StartPlugin");  
  12.             if (pFun != NULL)  
  13.             {  
  14.                 PluginInstance* pPlugin = pFun();  
  15.                 errCode = 1;  
  16.   
  17.                 return pPlugin;  
  18.             }  
  19.   
  20.             errCode = 0;  
  21.             return NULL;  
  22.         }  
  23.     }  
  24.   
  25.     else if (iter != m_vecPluginLibs.end())     //如果存在,在插件列表里面寻找名字是strName的插件  
  26.     {  
  27.         for (int i = 0; i < m_vecPlugins.size(); i ++)  
  28.         {  
  29.             if (strName == m_vecPlugins[i]->GetDisplayName())  
  30.             {  
  31.                 errCode = 1;  
  32.                 return m_vecPlugins[i];  
  33.             }  
  34.         }  
  35.     }  
  36.   
  37.     errCode = 0;  
  38.     return NULL;  
  39.       
  40. }  

 

从上面的过程可以看出,首先检测插件是否存在,如果存在,就在插件列表中查找该插件直接返回该插件实例。如果不存在,就需要先创建一个DynamicLib* pLib = new DynamicLib;,然后通过pLib导入名字为strName的插件动态库文件,再将这个模块句柄和名字加入到模块列表中,然后通过DynamicLib的GetSymbolAddress的函数获得函数名为StartPlugin的函数指针,最后通过这个函数指针进行回调返回插件实例以及将该插件注册到插件列表中,这个函数的在插件中的具体实现如下:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. <pre code_snippet_id="382558" snippet_file_name="blog_20140608_5_7634943" name="code" class="cpp">static PluginInstance *pPlugin = NULL;  
  2.   
  3.  PluginInstance* StartPlugin()  
  4.  {  
  5.      pPlugin = new ShapePlugin("shapefile");  
  6.      PluginManager::GetInstance().LoadPlugin(pPlugin);  
  7.   
  8.      return pPlugin;  
  9.  }</pre><pre code_snippet_id="382558" snippet_file_name="blog_20140608_5_7634943" name="code" class="cpp"></pre>  
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. <pre code_snippet_id="382558" snippet_file_name="blog_20140608_6_7002871" name="code" class="cpp">bool PluginManager::UnLoad(const std::string &strName)  
  2. {  
  3.     std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.begin();  
  4.     for (; iter != m_vecPluginLibs.end(); ++iter )  
  5.     {  
  6.         DynamicLib *pLib = iter->second;  
  7.         if (NULL == pLib)  
  8.         {  
  9.             continue;  
  10.         }  
  11.         if (strcmp(pLib->GetName(),strName.c_str()) == 0)  
  12.         {  
  13.             STOP_PLUGIN_FUN pFun = (STOP_PLUGIN_FUN)pLib->GetSymbolAddress("StopPlugin");  
  14.             if (pFun != NULL)  
  15.             {  
  16.                 pFun();  
  17.             }  
  18.   
  19.             pLib->FreeLib();  
  20.             delete pLib;  
  21.   
  22.             //然后从列表中删除  
  23.             m_vecPluginLibs.erase(iter);  
  24.             return true;  
  25.         }  
  26.     }  
  27.   
  28.     return false;  
  29. }</pre>  

 

 

ShapePlugin就是继承于PluginInstance的一个插件。
 
插件卸载的过程正好相反,下面也给出实现代码。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. bool PluginManager::UnLoad(const std::string &strName)  
  2. {  
  3.     std::map<std::string,DynamicLib *>::iterator iter = m_vecPluginLibs.begin();  
  4.     for (; iter != m_vecPluginLibs.end(); ++iter )  
  5.     {  
  6.         DynamicLib *pLib = iter->second;  
  7.         if (NULL == pLib)  
  8.         {  
  9.             continue;  
  10.         }  
  11.         if (strcmp(pLib->GetName(),strName.c_str()) == 0)  
  12.         {  
  13.             STOP_PLUGIN_FUN pFun = (STOP_PLUGIN_FUN)pLib->GetSymbolAddress("StopPlugin");  
  14.             if (pFun != NULL)  
  15.             {  
  16.                 pFun();  
  17.             }  
  18.   
  19.             pLib->FreeLib();  
  20.             delete pLib;  
  21.   
  22.             //然后从列表中删除  
  23.             m_vecPluginLibs.erase(iter);  
  24.             return true;  
  25.         }  
  26.     }  
  27.   
  28.     return false;  
  29. }  



 

这样整个插件的流程就走通了,万里长征第一步,现在只是实现了插件的注册和调用,下面一部就要实现怎么去实现具体插件和怎么实现插件之间的通信了。

三、后记

本文主要是自己在探索C++插件实现机制的一些个人理解,虽然功能还很少,但是一些基本的东西还是有了。本插件实例的代码的下载地址为:http://download.csdn.net/detail/zhouxuguang236/7466253
注意下载后请先看说明。
分享到:
评论

相关推荐

    C++ 插件框架 Pluma Framework

    Pluma Framework 是一个专为C++开发者设计的开源插件管理系统,它旨在简化应用程序与动态链接库(DLL)之间的交互,让动态加载和管理插件变得更加高效和便捷。Pluma 的核心特性包括其轻量级的设计、面向对象的编程...

    plugin-architecture-example.zip_c++ 插件模式_m_LoadedPlugins_插件_插件架构

    在C++编程中,插件架构是一种设计模式,它允许应用程序在运行时动态加载和卸载功能模块,这些模块称为“插件”。这种模式对于扩展软件功能、保持核心代码的简洁性和灵活性至关重要。本示例将详细介绍如何实现一个C++...

    C++实现简单的MVC框架

    在IT行业中,MVC(Model-View-Controller)是一种广泛应用于软件开发的架构模式,尤其在Web应用领域中。这个模式将应用程序分为三个主要部分,每个部分都有明确的责任,从而提高了代码的可维护性和可扩展性。本项目...

    C++实现Office插件

    开发者需要了解这些对象及其方法,以实现插件的各种功能。 5. **事件处理**:在C++插件中,可以注册事件处理程序来响应Office应用中的特定事件,如文件打开、保存或用户输入等。这需要理解COM事件模型以及如何在C++...

    c++实现跨浏览器插件

    标题中的“C++实现跨浏览器插件”是指利用C++编程语言开发一种插件,使其能在不同的网络浏览器中运行。这种技术通常涉及到浏览器插件的API接口,特别是NPAPI(Netscape Plugin Application Programming Interface)...

    浅谈C++模板实现模块间参数传递及回调

    一篇关于C++模板 设计模式 软件架构的文档,里面实现了一个泛化的命令模式和观察者模式

    CDT eclipse c++插件

    CDT eclipse c++/c 插件,对应的eclipse版本为4.2.0

    一个实现BS结构的c++程序

    你可以使用Node.js的Electron框架将C++与HTML/JS结合,创建桌面应用,这在某种程度上模拟了B/S架构。 6. **数据交换格式**:JSON或XML常用于客户端和服务器之间交换数据。C++有多个库如jsoncpp或pugixml用于解析和...

    C++中的微服务架构实现:深入解析与实践

    本文将详细介绍C++中微服务架构的实现,包括核心概念、关键技术、以及如何使用C++构建微服务。 C++在微服务架构中的应用提供了高性能和灵活性的结合。通过使用gRPC、ZeroMQ和专门的C++微服务框架,开发者可以构建出...

    使用C++开发EXCEL插件

    "使用C++开发EXCEL插件" 本书旨在介绍如何使用C++语言开发EXCEL插件,从而扩展EXCEL的功能。作者首先介绍了EXCEL的强大功能,包括数学计算函数和统计分析功能,并强调了使用C++语言开发插件的必要性。使用C++语言...

    C++跨平台插件实例

    在C++中,通常使用动态链接库(DLL on Windows,.so on Linux,dylib on macOS)来实现插件。这种设计允许插件和主程序独立编译和更新,提高了软件的维护性和升级效率。 为了实现跨平台的插件机制,我们需要关注...

    AIDE插件包,(c c++插件)

    有的人可能用AIDE编程,AIDE可以在手机上编写Java,C/C++,html,Android等等。...这是AIDE的C C++插件包 把文件夹解压到SD卡目录,安装钛备份 用钛备份进行恢复(压缩包里有钛备份) by 旅游的苹果 AIDE_3.1.3专用

    基于Qt的C++架构实例(模型MVC在C++后端管理系统应用)

    本资源是一个完整的Qt5.9Creator工程代码,主要功能是总结一个Qt下的MVC架构。该架构主要分为控制层、UI界面层、模型层,具体的理论讲解,可以参考博主这篇博客: ...

    C++插件开发技术白皮书

    - 接口实现类:实现具体的接口,使插件能够与平台的核心框架进行交互。 #### 四、C++插件开发平台的使用流程 - 开发者首先需要根据项目需求选择合适的组件进行开发。 - 完成开发后,将插件部署到平台上。 - 平台...

    CDT,eclipse C++插件离线安装包

    下载这个压缩包后,用户无需在线搜索和下载各个组件,只需将包含的文件复制到Eclipse安装目录,即可实现CDT插件的快速安装。这种方式在网络环境不稳定或者速度慢的地区特别有用。 在提供的压缩包文件中,有两个关键...

    插件框架C++20110920

    在C++中,插件通常是一个动态链接库(DLL),它实现了特定的接口或者协议,以便与主程序进行交互。这种设计使得程序具有高度的可扩展性,开发者可以随时增加或更新功能,而无需重新编译和部署整个应用程序。 该框架...

    C++跨平台插件实例.

    Boost.Python则允许C++代码与Python脚本进行交互,利用Python的易用性和跨平台特性,实现C++插件的跨平台目标。 实现C++跨平台插件涉及到以下几个关键点: 1. **接口定义**:创建一组公共的C接口,这些接口定义了...

    C/S架构的C++实现

    本项目通过C++语言实现了C/S架构,运用了socket编程来建立通信,提供了TCP服务和UDP服务。 TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输协议,它确保了数据的顺序和完整性。在TCP服务中,...

    Visual C++网络游戏建模与实现pdf+源码

    《Visual C++网络游戏建模与实现》是一本深入探讨如何使用Microsoft的Visual C++编程环境来构建网络游戏的专业书籍。本书的重点在于理论与实践相结合,旨在帮助读者理解和掌握网络游戏的开发技术,同时也提供了丰富...

Global site tag (gtag.js) - Google Analytics