一、插件架构初步介绍

想到写本博客,也没想到更好的名字,目前就先命这个名吧。说到插件架构,或许大部分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. const char* GetName() const;
  11. //装载动态库
  12. bool LoadLib(const char* strLibName);
  13. void* GetSymbolAddress(const char* 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. const char* DynamicLib::GetName() const
  39. {
  40. return m_pszLibName;
  41. }
  42. #if defined(__unix__) || defined(unix)
  43. #include <dlfcn.h>
  44. bool DynamicLib::LoadLib(const char* 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(const char* 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(const char* 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(const char* strSymbolName) const
  94. {
  95. if (m_hDynLib != NULL)
  96. {
  97. return (void*)GetProcAddress((HMODULE)m_hDynLib,strSymbolName);
  98. }
  99. return NULL;
  100. }
  101. void DynamicLib::FreeLib()
  102. {
  103. if (m_hDynLib != NULL)
  104. {
  105. FreeLibrary((HMODULE)m_hDynLib);
  106. m_hDynLib = NULL;
  107. }
  108. if (m_pszLibName != NULL)
  109. {
  110. free(m_pszLibName);
  111. m_pszLibName = NULL;
  112. }
  113. }
  114. #endif
差点忘了,插件系统必须设计的插件API和插件管理器都还没说,其实插件管理器是插件实例的集合,插件管理器提供了加载和卸载某一插件的功能,下面是插件API以及插件管理器的实例。

  1. #ifndef PLUGININSTANCE_INCLUDE
  2. #define PLUGININSTANCE_INCLUDE
  3. #include "Export.h"
  4. class PLUGINCORE_API PluginInstance
  5. {
  6. public:
  7. explicit PluginInstance(const std::string &strName);
  8. virtual ~PluginInstance(void);
  9. virtual bool Load() = 0;
  10. virtual bool UnLoad() = 0;
  11. //返回插件名字,带后缀,如dll等
  12. virtual std::string GetFileName() const = 0;
  13. //返回插件的名字,不带后缀
  14. virtual std::string GetDisplayName() const = 0;
  15. private:
  16. PluginInstance(const PluginInstance &rhs);
  17. const PluginInstance &operator=(const PluginInstance &rhs);
  18. };
  19. //插件加载和卸载时调用的函数
  20. typedef PluginInstance *( *START_PLUGIN_FUN )();
  21. typedef void( *STOP_PLUGIN_FUN )();
  22. #endif
  23. #ifndef PLUGINMANAGER_INCLUDE
  24. #define PLUGINMANAGER_INCLUDE
  25. #include "Export.h"
  26. class PluginInstance;
  27. class DynamicLib;
  28. class PLUGINCORE_API PluginManager
  29. {
  30. public:
  31. static PluginManager &GetInstance();
  32. bool LoadAll();
  33. PluginInstance* Load(const std::string &strName,int &errCode);
  34. bool LoadPlugin(PluginInstance *pPlugin);
  35. bool UnLoadAll();
  36. bool UnLoad(const std::string &strName);
  37. bool UnLoadPlugin(PluginInstance *pPlugin);
  38. std::vector<PluginInstance *> GetAllPlugins();
  39. private:
  40. PluginManager(void);
  41. ~PluginManager(void);
  42. PluginManager(const PluginManager &rhs);
  43. const PluginManager &operator=(const PluginManager &rhs);
  44. std::vector<PluginInstance *> m_vecPlugins;   //插件实例句柄
  45. std::map<std::string,DynamicLib *> m_vecPluginLibs;   //插件模块句柄
  46. };
  47. #endif

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

有了上面的介绍之后,就该开始介绍整个插件加载和卸载的流程了,先来介绍怎么进行加载的。加载的函数式PluginInstance* Load(const std::string &strName,int &errCode);
这个函数的功能是传入一个不带后缀的插件动态库名字,如果插件管理器中没有该插件就加载到系统中,并在插件列表中注册,若存在的话就在插件列表中访问该名字的插件,返回该插件实例。该函数的实现如下:
  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. return pPlugin;
  17. }
  18. errCode = 0;
  19. return NULL;
  20. }
  21. }
  22. else if (iter != m_vecPluginLibs.end())     //如果存在,在插件列表里面寻找名字是strName的插件
  23. {
  24. for (int i = 0; i < m_vecPlugins.size(); i ++)
  25. {
  26. if (strName == m_vecPlugins[i]->GetDisplayName())
  27. {
  28. errCode = 1;
  29. return m_vecPlugins[i];
  30. }
  31. }
  32. }
  33. errCode = 0;
  34. return NULL;
  35. }

从上面的过程可以看出,首先检测插件是否存在,如果存在,就在插件列表中查找该插件直接返回该插件实例。如果不存在,就需要先创建一个DynamicLib* pLib = new DynamicLib;,然后通过pLib导入名字为strName的插件动态库文件,再将这个模块句柄和名字加入到模块列表中,然后通过DynamicLib的GetSymbolAddress的函数获得函数名为StartPlugin的函数指针,最后通过这个函数指针进行回调返回插件实例以及将该插件注册到插件列表中,这个函数的在插件中的具体实现如下:

  1. static PluginInstance *pPlugin = NULL;
  2. PluginInstance* StartPlugin()
  3. {
  4. pPlugin = new ShapePlugin("shapefile");
  5. PluginManager::GetInstance().LoadPlugin(pPlugin);
  6. return pPlugin;
  7. }
  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. pLib->FreeLib();
  19. delete pLib;
  20. //然后从列表中删除
  21. m_vecPluginLibs.erase(iter);
  22. return true;
  23. }
  24. }
  25. return false;
  26. }
ShapePlugin就是继承于PluginInstance的一个插件。
 
插件卸载的过程正好相反,下面也给出实现代码。
  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. pLib->FreeLib();
  19. delete pLib;
  20. //然后从列表中删除
  21. m_vecPluginLibs.erase(iter);
  22. return true;
  23. }
  24. }
  25. return false;
  26. }
这样整个插件的流程就走通了,万里长征第一步,现在只是实现了插件的注册和调用,下面一部就要实现怎么去实现具体插件和怎么实现插件之间的通信了。

三、后记

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


C++插件架构浅谈与初步实现的更多相关文章

  1. 基于puppet分布式集群管理公有云多租户的架构浅谈

    基于puppet分布式集群管理公有云多租户的架构浅谈 一.架构介绍   在此架构中,每个租户的业务集群部署一台puppet-master作为自己所在业务集群的puppet的主服务器,在每个业务集群所拥 ...

  2. iOS应用架构浅谈

    (整理至http://www.cocoachina.com/ios/20150414/11557.html) 缘由 从事iOS工作一年多了,主要从事QQ钱包SDK开发和财付通app维护,随着对业务的慢 ...

  3. JavaWeb应用开发架构浅谈

    本文就我所经历和使用过的技术和框架, 讨论 Java / Javascript 技术组合构成的Web 应用架构. 一. 概述 Web 应用架构可以划分为两大子系统:前端子系统和后台子系统. 前端子系统 ...

  4. Nopcommerce架构浅谈之文件结构

    应该是在两年前了,在拜读园子里大神的文章时偶然了解到有个叫NopCommerce的商城系统,苦于没有时间,各种耽误,其中研究过一段时间,也就是一个星期时间,后来又耽搁了,直到最近,随着项目进入间歇期, ...

  5. iOS 应用架构浅谈

    当我们讨论客户端应用架构的时候,我们在讨论什么? 其实市面上大部分应用不外乎就是颠过来倒过去地做以下这些事情: 简单来说就是调API,展示页面,然后跳转到别的地方再调API,再展示页面. App确实就 ...

  6. Nopcommerce架构浅谈之架构层次

    前面谈到了系统的文件,从文件结构中我们也可以看出Nop的层次划分还是非常清晰,下面我将介绍下Nop的架构层次,并对每个层做简要的介绍,先看我画的层次图. 这个系统基本上按照了ddd的形式做了划分,我本 ...

  7. Linux架构浅谈

    以下图为基础: 最内层是硬件,最外层是用户常用的应用,比如说firefox浏览器,evolution查看邮件,一个计算流体模型等等.硬件是物质基础,而应用提供服务.但在两者之间,还要经过一番周折. 还 ...

  8. Lombok插件看法浅谈

    背景 最近接触的几个工程中Lombok插件出现频率比较高,趁机了解一下原理. 简要说明: 受益于JSR 269 API,程序可以在编译阶段对AST进行节点的操作,从而注入相关的功能结点,从而包含在最终 ...

  9. Docker 基础底层架构浅谈

    docker学习过程中,免不了需要学习下docker的底层技术,今天我们来记录下docker的底层架构吧! 从上图我们可以看到,docker依赖于linux内核的三个基本技术:namespaces.C ...

随机推荐

  1. win7系统 无线上网卡 共享网络,设置成wifi热点

    给家人买了一个新的智能手机,用的移动神州行套餐,没有开通3G,想更新一些应用软件,于是想到能不能用电脑上的无线上网卡. 在网上找到了一方法,试了一下,还真是可以. 步骤如下: 用无线上网卡拨号上网,并 ...

  2. (轉)JSON.stringify 语法实例讲解

    作用:这个函数的作用主要是为了系列化对象的. 可能有些人对系列化这个词过敏,我的理解很简单.就是说把原来是对象的类型转换成字符串类型(或者更确切的说是json类型的).就这么简单.打个比方说,你有一个 ...

  3. BZOJ 2733 [HNOI2012]永无乡 - 启发式合并主席树

    Description 1: 查询一个集合内的K大值 2: 合并两个集合 Solution 启发式合并主席树板子 Code #include<cstdio> #include<cst ...

  4. mysql 清空所有表数据重置自增ID

    1.登录mysql客户端 mysql -u root -p 输入密码进入 2.生成 sql 语句文件 SELECT CONCAT('TRUNCATE TABLE ',TABLE_NAME,';') i ...

  5. 异常Throwable

    1.有效处理java异常三原则 java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮,易于调试.异常之所以是一种强大的调试手段,在于其回答了以下三个问题: 什么出了错 ...

  6. eclipse项目两个红点

    Description Resource Path Location Type Unbound classpath container: 'JRE Sy 选中项目右键build path 选择libr ...

  7. 【UI测试】--合理性

  8. firefox 之 event兼容写法

    event 在 IE 和 FF(Firefox) 中是不兼容的,IE 中可以直接使用 event 对象,而 FF 中则不可以,解决方法之一如下: var event = window.event || ...

  9. collection tree protocol

    本文所属图书 > 传感网原理与技术 本书根据<高等院校物联网工程专业发展战略研究报告暨专业规范(试行)>和物联网工程本科专业的教学需要,结合传感网的最新发展及其应用现状编写而成.主要 ...

  10. 关于SSH中tomcat下中文名称图片不显示的问题

    最近做一个SSH框架的项目,用tomcat发布,需要上传图片到指定路径,然后再将图片显示在页面上.有一个问题:如果是英文名称的图片,就正常显示,可如果是中文的,它就是显示不出来,于是乎,在网上各种百度 ...