ToB企服应用市场:ToB评测及商务社交产业平台

标题: 用C++实现插件模式时的避坑要点 [打印本页]

作者: 十念    时间: 2022-9-5 22:04
标题: 用C++实现插件模式时的避坑要点
本文不打算严格地、用标准术语来讲前因后果。本文主要分析实践中常见的、因为对原理不清楚而搞出来的产品里的坑。
什么是插件模式和为什么要用插件模式

插件,Plug-In,或者(IE/Edge称之为)加载项/Add-On,(Office称之为)外接程序/Add-In,(GIMP称之为)扩展/Extension,等等,总之看字面意思都是“额外增加功能”的这种东西,是一类开发模式。基本思路就是,研发软件本体的时候,外部需求不明确、直到使用期仍然经常会增加功能细节。为了把变动部分切割开,在设计的时候,通过对可变部分的归纳分析,对可变部分抽象出一套接口;每套外部需求用动态库之类的形式实现接口;软件本体按某种约定,加载动态库,并从中获取插件实例,通过接口来调用满足当时需求的功能实现。
可以看到,插件的思想,其实就是灵活运用“动态库”的动态加载能力,把对“接口”的实现移到软件本体之外,并用工厂模式来约束动态库的实现方式。
只要是具有动态加载能力的运行环境上,都可以使用插件模式来设计软件系统。极端一些的软件系统,甚至只提供基础平台,所有功能都由插件的方式提供,例如 Visual Studio Code 、 Eclipse 等。
C++实现插件模式

用C++实现插件模式,一般是把下面这些功能组合起来:
不幸的是,由于各操作系统的动态库机制普遍是C风格的,用C++做动态库时候的坑,在用C++实现插件模式时,全都会遇到。比如:
一些典型的不良实现

这里说的不良实现,使用时候未必会错或崩,但早晚要崩,或者会限制住插件的开发。以下用如下插件接口作为例子。
  1. // IFilter.h
  2. /// 滤波器接口.
  3. class IFilter {
  4. protected:
  5.     IFilter();
  6. public:
  7.     virtual ~IFilter();
  8. public:
  9.     /// 一个将输入复数数组处理为输出复数数组的函数.
  10.     virtual void Filter(const std::complex<double>* acdIn, std::complex<double>* acdOut, size_t uLen) = 0;
  11.     /// 获取当前实现的一些描述字符串.
  12.     virtual std::string GetDescription() const = 0;
  13. };
复制代码
  1. // IFilter.cpp
  2. IFilter::IFilter() { }
  3. IFilter::~IFilter() { }
复制代码
并约定插件实现中以如下形式提供工厂函数。
  1. // FilterPluginDll.h
  2. #include "IFilter.h"
  3. /* 插件DLL应该提供如下函数
  4. extern "C" int GetFilterPluginInDll(char* szFilterNamesBuf, size_t uBufLen);
  5. extern "C" IFilter* BuildFilterPlugin(const char* szFilterName);
  6. extern "C" void FreeFilterPlugin(IFilter* pFilter);
  7. */
  8. typedef int (*PFNGetFilterPluginInDll)(char* szFilterNamesBuf, size_t uBufLen);
  9. typedef IFilter* (*PFNBuildFilterPlugin)(const char* szFilterName);
  10. typedef void (*PFNFreeFilterPlugin)(IFilter* pFilter);
复制代码
接口类没有提供二进制实现

比如,对插件只发布两个头文件;认为 IFilter 的构造和析构反正是空函数无所谓,直接写在类定义里。
这样,插件开发者自己生成插件DLL时,会在自己的DLL里链接进一份 IFilter::IFilter() 和 IFilter::~IFilter() 的实现,而软件本体里也有一份自己的实现。虽然看上去,如果编译器一样,两份实现是等同的,但考虑到它们使用了不同的模块堆,以及其它各种原因,插件DLL中的 IFilter 和软件本体里的 IFilter 并不是完全等同的。
这里应该由软件本体导出 IFilter::IFitler() 和 IFilter::~IFilter() 等接口类的共性成分的实现给插件,以免出现一些奇怪的问题。
工厂函数里没有正确设计“谁分配谁释放”

比如,为了“简单”,只要求了 BuildFilterPlugin 工厂函数,认为可以由软件本体用 delete pFilter; 来释放插件实例。
一种建议的实现方法

用类似于Windows的COM风格的“放了一堆函数指针的结构体”来表示插件的接口定义;软件本体里为了使用方便,再用接口类包装一下。
参考文献


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4