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接口的实例。

#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注意下载后请先看说明。

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

时间: 2024-10-18 14:13:02

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

前端架构浅谈

前端架构浅谈 0.前注 鉴于作者本人的能力有限(非常有限),并且依然在学习中,因此本文的高度和深度必然有所欠缺. 欢迎(并且非常欢迎)大家来批评指正,如果能详细的说明问题在哪里,如何解决和改正,那么就太感谢了!!! 我最喜欢听有理有据的批评了!! 本人QQ:20004604,邮箱:[email protected],期待你的交流. 1.为什么要有一个好的架构 首先明确一点,架构是为需求服务的. 前端架构存在的目的,就我个人理解来说,有以下几点: 1.提高代码的可读性. 一个好的架构,代码的可读性

超融合架构浅谈

为什么叫浅谈呢,因为就是自己的观点,受知识所限难免偏颇.数据中心里面真正的东西总结起来就是三大部件:计算.存储和网络.这三大部件的演变过程是:硬件-虚拟化-融合. 在传统独立硬件时代,计算资源就是服务器,存储也是独立的硬件设备,因为服务器和存储之间的相互连接就构成了网络.不管是TCP/IP还是FC,这些构成服务器和存储之间连接的网络部分,随着服务器硬件的升级和存储的硬件的升级而成为瓶颈.为什么不把碍事的连接网络去掉,将最强的直接融合在一起,即服务器和存储硬件融合在一起,并且实现扁平化.在超融合时

iOS应用架构浅谈

缘由 从事iOS工作一年多了,主要从事QQ钱包SDK开发和财付通app维护,随着对业务的慢慢熟悉,最近在思考这两款应用架构设计的思想,刚好昨天在微信里看了一篇iOS大牛对终端应用架构的分享,乘热打铁,下面浅谈下我对ios应用架构设计的理解,写的不好或不对的地方,欢迎大家拍砖,我们一起来探讨. 假如问你一个iOS or Android app的架构,你会从哪些方面来说呢? 不要急着给出你的答案,可以先在你的脑子里思考3分钟,再看下面我要讲的内容. 其实对于iOS客户端应用的架构来说,复杂度不亚于服

iOS 应用架构浅谈

当我们讨论客户端应用架构的时候,我们在讨论什么? 其实市面上大部分应用不外乎就是颠过来倒过去地做以下这些事情: 简单来说就是调API,展示页面,然后跳转到别的地方再调API,再展示页面. App确实就是主要做这些事情,但是支撑这些事情的基础,就是做架构要考虑的事情. 调用网络API 页面展示 数据的本地持久化 动态部署方案 上面这四大点,稍微细说一下就是: 如何让业务开发工程师方便安全地调用网络API?然后尽可能保证用户在各种网络环境下都能有良好的体验? 页面如何组织,才能尽可能降低业务方代码的

iOS开发项目架构浅谈:MVC与MVVM

MVC MVC,Model-View-Controller,我们从这个古老而经典的设计模式入手.采用 MVC 这个架构的最大的优点在于其概念简单,易于理解,几乎任何一个程序员都会有所了解,几乎每一所计算机院校都教过相关的知识.而在 iOS 客户端开发中,MVC 作为官方推荐的主流架构,不但 SDK 已经为我们实现好了 UIView.UIViewController 等相关的组件,更是有大量的文档和范例供我们参考学习,可以说是一种非常通用而成熟的架构设计.但 MVC 也有他的坏处.由于 MVC 的

Angular2的模块架构浅谈

一.根模块.子模块与惰性加载 先说根模块.一个ng2应用至少要有一个根模块,包含ng2自带的BrowserModule,并声明为引导模块,在应用启动时将从此模块展开.随着应用的扩大,所有的事情都在一个模块中完成难免会变乱(某种程度上看ng1应用就是这么做的,并且细分了控制器来拆分应用,这其实浪费了最顶层模块的意义),所以自然而然能想到,可以将系统分为多个模块,每个模块都只做各自的事情而互不干扰,所以进一步的思路就是,用来根模块来引导程序并管理所有子模块(通过路由定向以及为它们提供全局配置与服务实

[nRF51822] 14、浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(科普类干货)

蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到耳机.游戏手柄.音响.电视, 再到手环.电子秤.智能医疗器械(血糖仪.数字血压计.血气计.数字脉搏/心率监视器.数字体温计.耳温枪.皮肤水分计等), 再到智能家居等领域均占有一席之地. 而蓝牙低功耗(BLE)是在蓝牙4.0协议上修改以适用低功耗应用场景的一种蓝牙协议. 随着上一股智能消费类电子大潮的到来,BLE的各种应用也像雨后春笋般在市场上铺开. 如果想 紧跟蓝牙协议的最新动态 ,可以在https://www.b

浅谈HTML5单页面架构(二)——backbone + requirejs + zepto + underscore

本文转载自:http://www.cnblogs.com/kenkofox/p/4648472.html 上一篇<浅谈HTML5单页面架构(一)——requirejs + angular + angular-route>探讨了angular+requirejs的一个简单架构,这一篇继续来看看backbone如何跟requirejs结合. 相同地,项目架构好与坏不是说用了多少牛逼的框架,而是怎么合理利用框架,让项目开发更流畅,代码更容易管理.那么带着这个目的,我们来继续探讨backbone. 首

浅谈HTML5单页面架构(一)——requirejs + angular + angular-route

本文转载自:http://www.cnblogs.com/kenkofox/p/4643760.html 心血来潮,打算结合实际开发的经验,浅谈一下HTML5单页面App或网页的架构. 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验最好,更像原生app.简单来说,单页面App不需要频繁切换网页,可以局部刷新,整个加载流畅度会好很多. 废话就不多说了,直接到正题吧,浅谈一下我自己理解的几种单页面架构: 1.requirejs