这篇“使用C++实现插件模式时要注意哪些问题”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“使用C++实现插件模式时要注意哪些问题”文章吧。
插件,Plug-In,或者(IE/Edge称之为)加载项/Add-On,(Office称之为)外接程序/Add-In,(GIMP称之为)扩展/Extension,等等,总之看字面意思都是“额外增加功能”的这种东西,是一类开发模式。基本思路就是,研发软件本体的时候,外部需求不明确、直到使用期仍然经常会增加功能细节。为了把变动部分切割开,在设计的时候,通过对可变部分的归纳分析,对可变部分抽象出一套接口;每套外部需求用动态库之类的形式实现接口;软件本体按某种约定,加载动态库,并从中获取插件实例,通过接口来调用满足当时需求的功能实现。
可以看到,插件的思想,其实就是灵活运用“动态库”的动态加载能力,把对“接口”的实现移到软件本体之外,并用工厂模式来约束动态库的实现方式。
只要是具有动态加载能力的运行环境上,都可以使用插件模式来设计软件系统。极端一些的软件系统,甚至只提供基础平台,所有功能都由插件的方式提供,例如 Visual Studio Code 、 Eclipse 等。
用C++实现插件模式,一般是把下面这些功能组合起来:
用一个C++的带虚函数的基类来表示功能
约定动态库里的工厂模式接口
在一些动态库里提供实现虚函数的派生类
在动态库里实现工厂模式
不幸的是,由于各操作系统的动态库机制普遍是C风格的,用C++做动态库时候的坑,在用C++实现插件模式时,全都会遇到。比如:
C++的编译器差异导致的不互通
会导致必须用同一种(或兼容的)编译器来生成插件和软件本体。
名字改写(Name Mangling):参考:Wikipedia、C/C++中的名字空间与作用域示例详解 会导致加载插件时“找不到符号”
虚函数(表)实现:
会导致加载插件时可能不报错,但运行时候找不到正确的虚函数入口
操作系统机制导致的不互通
Windows上使用MSVCRT时的内存分配和回收
Windows每个模块的内存分配默认是在模块自己的堆里的,而Windows上的C运行时库(各种MSVCRT)为了封装出 malloc
、 free
等C函数的效果,建立了__crtheap
(2010及之前版本行为)或直接使用进程默认堆(2015及之后版本行为)[1]。这导致,即使是同一个编译器,静态链接VC运行时会采用本模块内部的堆来实现 malloc
等,而动态链接VC运行时则会采用MSVCRT动态库DLL的模块堆。需要解决好“谁申请谁释放”的问题,否则内存管理的地方容易出异常。
全局变量在模块间的共享问题
这里说的不良实现,使用时候未必会错或崩,但早晚要崩,或者会限制住插件的开发。以下用如下插件接口作为例子。
// IFilter.h /// 滤波器接口. class IFilter { protected: IFilter(); public: virtual ~IFilter(); public: /// 一个将输入复数数组处理为输出复数数组的函数. virtual void Filter(const std::complex<double>* acdIn, std::complex<double>* acdOut, size_t uLen) = 0; /// 获取当前实现的一些描述字符串. virtual std::string GetDescription() const = 0; }; // IFilter.cpp IFilter::IFilter() { } IFilter::~IFilter() { }
并约定插件实现中以如下形式提供工厂函数。
// FilterPluginDll.h #include "IFilter.h" /* 插件DLL应该提供如下函数 extern "C" int GetFilterPluginInDll(char* szFilterNamesBuf, size_t uBufLen); extern "C" IFilter* BuildFilterPlugin(const char* szFilterName); extern "C" void FreeFilterPlugin(IFilter* pFilter); */ typedef int (*PFNGetFilterPluginInDll)(char* szFilterNamesBuf, size_t uBufLen); typedef IFilter* (*PFNBuildFilterPlugin)(const char* szFilterName); typedef void (*PFNFreeFilterPlugin)(IFilter* pFilter);
比如,对插件只发布两个头文件;认为 IFilter
的构造和析构反正是空函数无所谓,直接写在类定义里。
这样,插件开发者自己生成插件DLL时,会在自己的DLL里链接进一份 IFilter::IFilter()
和 IFilter::~IFilter()
的实现,而软件本体里也有一份自己的实现。虽然看上去,如果编译器一样,两份实现是等同的,但考虑到它们使用了不同的模块堆,以及其它各种原因,插件DLL中的 IFilter
和软件本体里的 IFilter
并不是完全等同的。
这里应该由软件本体导出 IFilter::IFitler()
和 IFilter::~IFilter()
等接口类的共性成分的实现给插件,以免出现一些奇怪的问题。
比如,为了“简单”,只要求了 BuildFilterPlugin
工厂函数,认为可以由软件本体用 delete pFilter;
来释放插件实例。
用类似于Windows的COM风格的“放了一堆函数指针的结构体”来表示插件的接口定义;软件本体里为了使用方便,再用接口类包装一下。
以上就是关于“使用C++实现插件模式时要注意哪些问题”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。