C++插件,c++程序设计原理与实践
什么是插件机制插件是你开发一个成功的动态系统所需要的一种方式。基于插件的可扩展性是扩展和进化系统的最实用的安全方式。插件开发者可以为系统做增值工作,其他开发者或分工合作,也可以在不破坏现有核心功能的情况下,增加新的功能。插件可以促进关注点的分离,保证隐藏的实现细节,分离测试,最有实际意义。
比如强大的Eclipse的平台,其实就是一个骨架,所有的功能都是插件提供的。Eclipse IDE本身(包括UI和Java开发环境)只是挂在核心框架上的一系列插件。
插件机制还需要考虑一些问题,如错误处理、数据类型、版本控制、与框架代码和应用程序代码的分离等。或许,在应用框架容器中,可以使用lua脚本动态灵活地实现业务。
为什么要用插件机制?当然也有很多好处。这就是你用它的原因。为什么要用插件架构?
现代软件工程已经从最初的通用库逐渐过渡到应用框架。让我们假设一个场景。以C开发应用为例。我们的架构是基于APP DLL的传统架构,所有功能都混在一起。随着系统规模的增大,各种模块耦合在一起。当一个模块被修改时,其他模块也会受到影响。如果是这两个不同模块的开发者负责,需要提前沟通好,修改和维护都比较困难。那这个问题怎么解决呢?插件是一种选择。
业务模块解耦,易于维护,便于多人协同开发,易于扩展现有程序功能等。“编程就是把自己的小积木一个个搭建起来,然后用自己的小积木搭建一个大系统”。但是程序仍然会比积木更复杂。我们的系统必须保证小积木可以搭建一个大系统(必须组合在一起),而且要尽量减少积木之间的耦合。
传统的程序结构也分为模块,但它有以下缺点:
1: c二进制兼容性问题。
2.模块对外暴露了太多的东西,以至于调用方有太多的东西要关心。
3.封装的模块只是作为功能的实现者被封装,而不是接口的提供者。
4.可替代性和可扩展性差。
插件系统架构就是为了解决这个问题,插件机制符合设计模式的六原则(现在是七原则),是一个好的设计。在设计模式的六个原则中,提到了单一职责原则、开闭原则、接口隔离原则、里希特替代原则、依赖反转原则和迪米特定律。可以说插件机制几乎满足了这六个原则的所有条款,当然也有它带来的好处,所以学习和使用插件机制是很有必要的。
模式设计的七个原则是为了更好的代码可重用性、可读性、可靠性、可维护性和可扩展性。
单一责任原则:
即一个类应该只负责一个职责,这样可以降低类的复杂度,不至于因为改变一个而影响到另一个。提高类的可读性和可维护性,降低变更带来的风险。插件机制的每个插件模块都是一个单独的职责。
开闭原则:
一个软件实体,比如类、模块和函数,应该对扩展是开放的,对修改是封闭的。用抽象构建框架,用实现扩展细节。当软件需要更改时,尝试通过扩展软件实体的行为来实现更改,而不是修改现有的代码。这个原则认为插件机制的实施是不恰当的。
迪米特里定律:
一个对象应该保持对其他对象的最低限度的了解。与班级的关系越密切,耦合度越大。
迪米特里定律也被称为最不为人知原则,即一个类对它所依赖的类了解得越少越好。也就是说,不管依赖的类有多复杂,尽量把逻辑封装在类里面。除了提供的公开方法,不要向外界透露任何信息。插件机制的实现很好的诠释了这个规律。
隔离原则:
客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该基于最小的接口。从后面的插件实现过程中可以看出,插件提供的接口是最小的单元,是简洁和必要的。
依赖性逆转原则:
高层模块不应该依赖于低层模块,但两者都应该依赖于它们的抽象;抽象不应该依赖细节,细节应该依赖抽象。反转的原理是基于这样一个事实:抽象的事物比细节的可变性要稳定得多。基于抽象的架构比基于细节的架构要稳定得多。至于插件机制的实现,插件提供的接口可以看作是一个高层模块,独立于底层的实现细节。
里希特的替换原则:
所有引用父类的地方都必须能够透明地使用其子类的对象,子类可以在不改变程序逻辑的情况下替换父类对象。按照这个理解,引申的意思就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
重用原则:
这个原则解释了我们应该如何重用类。类重用可以通过“继承”和“合成”来实现。它最大的缺点是增加了类之间的依赖性。当父类改变时,它的子类也会被动地改变。由于继承的这些缺点,我们在重用类时应该优先使用“合成”。复用原则的核心思想是:如果在编写代码时需要使用其他类,尽量使用两个类之间合成/聚合的方式,而不是继承。我们可以通过类之间的“合成”来达到“重用”代码的目的。
插件机制实现原理的大致思路是:应用程序提供接口,由其他同事或第三方实现,并编译相应的动态链接库(即插件);把所有插件放在一个特定的目录下,应用会自动搜索目录,动态加载目录下的插件。
插件机制的实现过程:下面分享一下我们这位热心的技术大牛师傅设计插件的方法和思路。在此感谢!
首先,定义一个插件接口Iplugin.h
这有点类似于设计模式中的中介模式。所有插件模块都通过这个公共的中间人相互交互。
# ifndef service project _ IPLUGIN _ H
# define service project _ IPLUGIN _ H
#包含QString
#包含QStringList
IPlugin类
{
公共:
IPlugin()=default;
virtual ~ IPlugin()=default;
IPlugin(const IPlugin)=delete;
IPlugin运算符=(const IPlugin)=delete;
公共:
//获取支持的插件方法
虚拟const QStringList getSupportCommandList()const { return supportCommandList _;}
公共:
虚拟QString getVersionString()const=0;
//!\brief获取业务名称
//!\返回
虚拟QString getPluginName()const=0;
枚举插件State_type
{
idle_,
开始_,
开始_,
停止_,
停了_,
除了_,
};
//!获取插件的运行状态。
//!\返回
virtual pluginState _ type getPluginState()const=0;
//!获取插件运行状态的说明。
//!\参数状态
//!\返回
静态QString getpluginstatessage(const pluginState _ type状态)
{
QString消息=无消息;
开关(状态)
{
案例插件State_type:idle_:
msg=插件处于空闲状态;
打破;
案例插件State_type:starting_:
msg=插件在开始语句上。
打破;
案例插件State_type:started_:
msg=插件在开始的语句上。
打破;
案例插件状态_类型:停止_:
msg=插件在停止语句上。
打破;
案例插件State_type:stopped_:
msg=插件位于已停止的语句上。
打破;
案例插件state _ type:exception _:
msg=插件在excepting语句上。
打破;
}
返回msg
}
//!\brief业务执行的主接口方法
//!\param msg外部程序发送的通信协议(如json消息)
//!\返回错误消息
虚拟STD:error _ code exec(const QString msg)=0;
//!\简短服务停止运行。
//!\返回
虚拟STD:error _ code stop(){ return { };}
//!\简短释放动态库资源
//!\返回错误信息
虚拟STD:error _ code release()=0;
受保护:
QStringList supportCommandList _;
};
# endif//服务项目_ I插件_ H二、实现插件加载,注册等操作的管理类插件管理器。
遍历解放运动目录中的各个插件动态库,如插件1.dll,插件2.dll,等,完成插件的加载和注册。使用夸脱的QLibrary,(instance)lib-resolve( getInstance ),这里很关键,调用解决()函数找到动态链接库库中的getInstance函数,并强制转换为函数指针。后又强制转换为(IPlugin *)类型指针存储进QHash。
#包含QHash
#包含某个东西
#如果已定义(插件_管理器_构建_共享)
# define PLUGIN _ MANAGER _ EXPORT _ _ declspec(dll导出)
#否则
# define PLUGIN _ MANAGER _ EXPORT _ _ declspec(dllimport)
#endif
插件协议类;
类别库;
类插件_管理器_导出插件管理器:公共对象
{
q _对象
私人:
显式插件管理器(q object * parent=nullptr);
公共:
静态插件管理器* getInstance();
~插件管理器()覆盖;
静态const char * get库名();
静态const char * getLibraryVersion();
公共:
void loadAll();
void卸载all();
STD:error _ code load(const QString name);
STD:error _ code unload(const QString name);
QStringList getNames()const { return plugins _ .keys();}
IPlugin * get(const QString name);
私人:
QHash QString,IPlugin * plugins _;
QHash QString,QLibrary * libs _;
};//.
typedef void *(* instance)();
插件管理器:插件管理器(q对象*父):q对象(父)
{
}
PluginManager:~PluginManager()
{
卸载所有();
}
void PluginManager:loadAll()
{
QDir插件目录(qcore application:application dirpath()R (/./lib));
QStringList过滤器;
筛选QString(*%1 ).arg(LIB _ SUFFIX);
pluginDir.setNameFilters(过滤器);
auto entryinvolist=插件目录。entryinvolist();
对于(常量自动信息:entryInfoList)
{
auto lib=new QLibrary(qcore application:application dirpath()R (/./lib/)”信息。filename());
if (lib- isLoaded())
{
LOGGING_WARN(%s 已加载info.fileName().toStdString().c _ str());
继续;
}
if (lib- load())
{
auto func=(instance)lib-resolve( getInstance );
如果(函数)
{
auto plugin=(IPlugin *)func();
如果(插件)
{
auto plugin name=plugin-getPluginName();
如果(插件_。包含(插件名))
{
LOGGING_WARN(%s重复加载pluginName.toStdString().c _ str());
lib-unload();
lib-delete la ter();
lib=nullptr
继续;
}
插件_。insert(pluginName,plugin);
libs_ .insert(pluginName,lib);
日志记录_调试( %s版本:%s ,plugin- getPluginName().toStdString().c_str(),
插件- getVersionString().toStdString().c _ str());
}
其他
{
日志记录_错误( %s对象创建失败info.fileName().toStdString().c _ str());
lib-unload();
lib-delete la ter();
lib=nullptr
}
}
其他
{
日志记录_错误( %s找不到符号info.fileName().toStdString().c _ str());
lib-unload();
lib-delete la ter();
lib=nullptr
}
}
其他
{
LOGGING_ERROR(%s load失败。错误消息:%s ,info.fileName().toStdString().c_str(),
lib- errorString().toStdString().c _ str());
lib-delete la ter();
lib=nullptr
}
}
}
void PluginManager:unloadAll()
{
对于(自动项目:插件_)
{
项目发布().
}
对于(自动项目:libs_)
{
item-unload();
item-delete la ter();
item=nullptr
}
插件_。clear();
libs_ .clear();
}
.
IPlugin *插件管理器:get(const QString name)
{
如果(插件_。包含(名称))
{
返回插件_。值(名称);
}
日志记录_错误( %s找不到name.toStdString().c _ str());
返回指针
}
插件管理器*插件管理器:getInstance()
{
静态插件管理器w;
返回w;
}
const char *插件管理器:获取库名()
{
返回项目名称;
}
const char *插件管理器:getLibraryVersion()
{
返回项目_版本;
}三、插件模块的实现
各个插件模块对外提供一唯一的入口函数getInstance(),注意:该函数必须为外部 c 声明的。
在这个getInstance函数中,可以实例化具体的类对象(假设这个类对象实现了IPlugin接口)。
//myplugindll . h
extern C _ _ declspec(dll export)void * getInstance();
//myplugindll . CPP
void* getInstance()
{
静态my plugin 1 w;
return(void *)w;
}
//MyPlugin1.h
类MyPlugin1: public IPlugin
{
公共:
myplugin 1();
~MyPlugin1()覆盖;
公共:
QString getVersionString()常量覆盖;
//!\brief获取业务名称
//!\返回
QString getPluginName()常量覆盖;
//!获取插件的运行状态。
//!\返回
pluginState _ type getPluginState()const override;
//!\brief执行业务。
//!\param msg由外部程序发送的通信协议
//!\返回错误消息
STD:error _ code exec(const QString msg)覆盖;
//!\简短服务停止运行。
//!\返回
std:error_code stop()覆盖;
//!\简要发布动态库资源
//!\返回错误消息
std:error_code release()覆盖;
};
//MyPlugin1.cpp
.
#定义我的插件名称我的插件1
MyPlugin1:MyPlugin1()
{
//Key,这里是这个插件的插件名。插件管理类对象PluginManager会据此找到自己。
supportCommandList_。push_back(我的插件名);
}
MyPlugin1:~MyPlugin1()
{
}
QString myplugin 1:getVersionString()const
{
返回项目_版本;
}
QString myplugin 1:getPluginName()const
{
返回项目名称;
}
IPlugin:plugin state _ type my plugin 1:getPluginState()const
{
return IPlugin:starting _;
}
STD:error _ code myplugin 1:exec(const QString msge)
{
//业务功能实现.
}比如插件机制的使用,已经走过了前三步,准备工作已经做好。怎么用才能看到效果?
我们编写一个测试do _ plugin work (constqstringmsg,constqstringcmd)。
的cmd内容指定插件名称。
过程是遍历PluginManager中管理的所有插件名,找到对应的并传递调用参数msg。
如果实现了两个插件plugin1.dll和plugin2.dll,do _ pluginWork (hello , plugin1 ),将调用plugin1中的exec函数。
//.
do_pluginWork(const QString msg,const QString cmd)
{
auto allPluginNames=plugin manager:getInstance()-getNames();
for(常量自动名称:allPluginNames)
{
auto plugin ptr=plugin manager:getInstance()-get(name);
中频(即插即用)
{
if(plugin ptr-getSupportCommandList()。包含(cmd))
{
auto error code=plugin ptr-exec(msg);
if (errorCode.value())
{
LOGGING_ERROR([%s]结果消息:%s ,pluginPtr- getPluginName()。toStdString()。c_str(),
errorCode.message()。c _ str());
}
LOGGING_DEBUG(%s已执行。cmd.toStdString()。c _ str());
返回;
}
}
}
LOGGING_ERROR(找不到匹配命令。);
}最后,最后,享受使用吧,小伙伴?外挂机制不好看吗?
引用:用C实现的插件系统_猫的阳光博客C插件系统
设计模式的六个原则
c插件系统_ QQ的博客_ 32250025 _c插件
谈C插件架构及初步实现_不知道什么时候谈的博客_c插件
构建自己的C/C插件开发框架_加油,4ever的博客_ C插件框架
C/C:构建自己的插件框架_石头的博客_c插件框架
软件设计的七个原则,看了这一篇就够了_凹男兰博的博客one _综合复用原则
c插件开发_博客_c插件的_gnr_123
浅谈C插件框架及初步实现_周旭光博客-_c插件框架
,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。