广告位联系
返回顶部
分享到

使用C++实现插件模式时的避坑要点(推荐)

C语言 来源:互联网 作者:酷站 发布时间:2022-08-07 07:31:41 人浏览
摘要

本文不打算严格地、用标准术语来讲前因后果。本文主要分析实践中常见的、因为对原理不清楚而搞出来的产品里的坑。 什么是插件模式和为什么要用插件模式 插件,Plug-In,或者(

本文不打算严格地、用标准术语来讲前因后果。本文主要分析实践中常见的、因为对原理不清楚而搞出来的产品里的坑。

什么是插件模式和为什么要用插件模式

插件,Plug-In,或者(IE/Edge称之为)加载项/Add-On,(Office称之为)外接程序/Add-In,(GIMP称之为)扩展/Extension,等等,总之看字面意思都是“额外增加功能”的这种东西,是一类开发模式。基本思路就是,研发软件本体的时候,外部需求不明确、直到使用期仍然经常会增加功能细节。为了把变动部分切割开,在设计的时候,通过对可变部分的归纳分析,对可变部分抽象出一套接口;每套外部需求用动态库之类的形式实现接口;软件本体按某种约定,加载动态库,并从中获取插件实例,通过接口来调用满足当时需求的功能实现。

可以看到,插件的思想,其实就是灵活运用“动态库”的动态加载能力,把对“接口”的实现移到软件本体之外,并用工厂模式来约束动态库的实现方式。

只要是具有动态加载能力的运行环境上,都可以使用插件模式来设计软件系统。极端一些的软件系统,甚至只提供基础平台,所有功能都由插件的方式提供,例如 Visual Studio Code 、 Eclipse 等。

C++实现插件模式

用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的模块堆。需要解决好“谁申请谁释放”的问题,否则内存管理的地方容易出异常。
    • 全局变量在模块间的共享问题

一些典型的不良实现

这里说的不良实现,使用时候未必会错或崩,但早晚要崩,或者会限制住插件的开发。以下用如下插件接口作为例子。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 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() { }

并约定插件实现中以如下形式提供工厂函数。

1

2

3

4

5

6

7

8

9

10

// 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风格的“放了一堆函数指针的结构体”来表示插件的接口定义;软件本体里为了使用方便,再用接口类包装一下。

参考文献

  • 一个程序员的修炼之路. 谈一谈Windows中的堆 [EB/OL]. https://blog.csdn.net/CJF_iceKing/article/details/119083770

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://www.cnblogs.com/logischerweise/p/16538530.html
相关文章
  • C++中类的六大默认成员函数的介绍

    C++中类的六大默认成员函数的介绍
    一、类的默认成员函数 二、构造函数Date(形参列表) 构造函数主要完成初始化对象,相当于C语言阶段写的Init函数。 默认构造函数:无参的构
  • C/C++实现遍历文件夹最全方法总结介绍

    C/C++实现遍历文件夹最全方法总结介绍
    一、filesystem(推荐) 在c++17中,引入了文件系统,使用起来非常方便 在VS中,可以直接在项目属性中调整: 只要是C++17即以上都可 然后头文件
  • C语言实现手写Map(数组+链表+红黑树)的代码

    C语言实现手写Map(数组+链表+红黑树)的代码
    要求 需要准备数组集合(List) 数据结构 需要准备单向链表(Linked) 数据结构 需要准备红黑树(Rbtree)数据结构 需要准备红黑树和链表适配策略
  • MySQL系列教程之使用C语言来连接数据库

    MySQL系列教程之使用C语言来连接数据库
    写在前面 知道了 Java中使用 JDBC编程 来连接数据库了,但是使用 C语言 来连接数据库却总是连接不上去~ 立即安排一波使用 C语言连接 MySQL数
  • 基于C语言实现简单学生成绩管理系统

    基于C语言实现简单学生成绩管理系统
    一、系统主要功能 1、密码登录 2、输入数据 3、查询成绩 4、修改成绩 5、输出所有学生成绩 6、退出系统 二、代码实现 1 2 3 4 5 6 7 8 9 10 11
  • C语言实现共享单车管理系统

    C语言实现共享单车管理系统
    1.功能模块图; 2.各个模块详细的功能描述。 1.登陆:登陆分为用户登陆,管理员登陆以及维修员登录,登陆后不同的用户所执行的操作
  • C++继承与菱形继承的介绍

    C++继承与菱形继承的介绍
    继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加
  • C/C++指针介绍与使用介绍

    C/C++指针介绍与使用介绍
    什么是指针 C/C++语言拥有在程序运行时获得变量的地址和操作地址的能力,这种用来操作地址的特殊类型变量被称作指针。 翻译翻译什么
  • C++进程的创建和进程ID标识介绍
    进程的ID 进程的ID,可称为PID。它是进程的唯一标识,类似于我们的身份证号是唯一标识,因为名字可能会和其他人相同,生日可能会与其他
  • C++分析如何用虚析构与纯虚析构处理内存泄漏

    C++分析如何用虚析构与纯虚析构处理内存泄漏
    一、问题引入 使用多态时,如果有一些子类的成员开辟在堆区,那么在父类执行完毕释放后,没有办法去释放子类的内存,这样会导致内存
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计