0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一个用C++ 开发的可用于管理插件的开源架构 Pluma

工程师邓生 来源:OSCHINA 社区 作者:悠然红茶 2022-09-16 11:18 次阅读

1. 概述

Pluma 是一个用 C++ 开发的可用于管理插件的开源架构。该架构是个轻量级架构,非常易于理解。

Pluma 架构有以下基本概念:

1)插件的外在行为体现为一个纯虚类,可以叫作插件接口

2)继承于同一个插件接口的若干派生类,被认为属于同一种插件,可以叫作插件类;

3)每一个插件接口或插件类都有个一一对应的 Provider 类,其中,插件接口对应的 Provider 类里会定义一个特殊字符串常量:PLUMA_PROVIDER_TYPE,表示这一类 “插件 Provider” 共同的类型名称,而这个类型名称其实就是插件接口的类名字符串。

4)多个插件类可以被放入一个插件动态库中,而这个动态库文件名(不包括后缀部分)可以叫作 “插件名”。

5)插件机制使用者可以在自己的架构中包含一个 Pluma 管理类,该类支持从所指定的位置加载一个或多个插件动态库,并将每个插件类对应的 Provider,记录进内部的表中。

6)插件机制使用者可以在合适时机,利用 Pluma 获取内含的插件 Provider,并调用某个插件 Provider 的 create () 函数,创建出对应的插件对象。

7)使用完插件对象后,不要忘了 delete 它。

现在我们画一张示意图:


d2a127d2-3432-11ed-ba43-dac502259ad0.png

2. Pluma 管理类

我们刚刚也说了,插件机制使用者可以包含一个 Pluma 管理类。

该类继承于 PluginManager 类。

【pluma-1.1/include/pluma/PluginManager.hpp】


poYBAGMj66-AKxy3AAGt4DC0U_8150.jpg 从上面的 load () 函数和 loadFromFolder () 函数可以看出,插件管理器既允许用户单独加载某个插件动态库,也允许批量性加载某个目录下所有的插件动态库。另外,值得注意的是,getProviders () 函数是 protected 的成员,也就是说,这套架构是不希望用户直接使用这个 PluginManager 类的,即便用了,你也拿不到 Provider。正确的做法是,使用 PluginManager 的子类:Pluma 管理类。    另外,上面的成员变量 libraries,就是记录所有已加载的插件动态库的映射表。而成员变量 host 则负责记录每个插件类对应的 Provider 信息。之所以被称为 host(宿主),是针对插件而言的。也就是说插件本身实际上是没资格知道其真实宿主的全貌的,它只能访问和它相关的很小一部分数据而已,因此 Pluma 将这一小部分数据整理成一个 host 代理,供插件使用。     Pluma 管理类的代码截选如下: 【pluma-1.1/include/pluma/Pluma.hpp】

pYYBAGMj69KAN3DEAABukF5mWyc123.jpg

请大家注意上面代码中最后一行,这个 Pluma.hpp 还真是有点手黑,偷偷摸摸 #include 了个 Pluma.inl 文件,其实展开来就是 acceptProviderType () 和 getProviders () 这两个模板函数的实现。Pluma.inl 文件的内容如下: 【pluma-1.1/include/pluma/Pluma.inl】

poYBAGMj6--AMVfqAAEv_7Rt3N4028.jpg

看到了吧,重新定义了个 getProviders (),还搞成一个模板函数,在函数体内会反过来通过模板参数,进一步得到所涉及的插件 Provider 的 PLUMA_PROVIDER_TYPE 信息,这个技巧挺重要。也就是说,外界传来的是 vector,而函数内部可以推断出 ProviderType::PLUMA_PROVIDER_TYPE。将 PLUMA_PROVIDER_TYPE 传入父类的 PluginManager::getProviders () 函数,就可以拿到符合所指类型的所有 Provider。 我们画一张 Pluma 简图,后面再细说相关细节: d2bc2a14-3432-11ed-ba43-dac502259ad0.png 同一类插件类,会对应一个 ProviderInfo 节点,该节点内部的 providers 列表,记录着同属一类的若干 Provider。

2.1 Host 代理 【pluma-1.1/include/pluma/Host.hpp】

pYYBAGMj7B2AScFnAAFEhApOE-c560.jpgpYYBAGMj7CSAASNtAABxdymdDgU500.jpg

正如前文所说,Host 代理是针对插件而言的。而 Host 只有一个 public 成员函数 add (),说明其主要对外行为就是让插件将对应的 provider 注册进 Host。

3. 插件类和其对应的 Provider 类

在说了一大堆插件管理类代码后,现在终于要开始说插件部分了。前文已经说过,插件的外在行为体现为一个纯虚类,可以叫作插件接口。我们现在就以 Pluma 源码中给出的例子为准,来说明一些细节。

3.1 Warrior 接口和 WarriorProvider 类

Pluma 中的插件接口例子是 Warrior,其源码截选如下: 【pluma-1.1/example/src/interface/Warrior.hpp】


pYYBAGMj7D-AJVCHAABCyGY1gt4715.jpg 这个接口里只象征性的写了一个成员函数 getDescription (),大家明白意思即可。      需要注意的是类定义之后的那句 PLUMA_PROVIDER_HEADER,这个宏负责定义和插件接口对应的 Provider 类。相关的宏定义如下: 【pluma-1.1/include/pluma/Pluma.hpp】

pYYBAGMj7FWAEQF4AAEHMJvauLA297.jpg

基于这些宏定义,我们可以将 PLUMA_PROVIDER_HEADER (Warrior) 展开为:

poYBAGMj7HOAK2VDAAC7dO0qpzE457.jpg

代码很清晰,为 Warrior 接口声明一个配套的 WarriorProvider 类。这个类里包含着重要的 PLUMA_PROVIDER_TYPE 常量,以及最关键的 create () 函数。 Warrior 的实现文件更加简单: 【pluma-1.1/example/src/interface/Warrior.cpp】

poYBAGMj7IiATyndAAAdbDAFUyo300.jpg

也在使用宏,展开宏后可见:

poYBAGMj7JuAfQEfAABlUGbcW7E941.jpg

因为 Warrior 本身是个纯虚类,所以 WarriorProvider 里也不用实现 create () 函数。

3.2 Warrior 派生类和派生 Provider

在 pluma 源码的例子中,提供了三个 Warrior 派生类,SimpleWarrior、Eagle 和 Jaguar。默认的是 SimpleWarrior,它被集成进 example/src/host 目录。也就是说,即便我们一个额外的插件库都不提供,示例至少还可以使用 SimpleWarrior。而 Eagle 和 Jaguar 则位于 example/src/plugin 目录,可以打包进一个插件动态库。 【pluma-1.1/example/src/host/SimpleWarrior.hpp】

poYBAGMj7LSARMTfAABc8jwniCk595.jpg

前文我们已经看到,对于插件接口(Warrior)来说,用到的宏是PLUMA_PROVIDER_HEADER(Warrior),现在针对实际插件类(SimpleWarrior),会用到另一个宏PLUMA_INHERIT_PROVIDER(SimpleWarrior, Warrior)。这个宏的定义如下: 【pluma-1.1/include/pluma/Pluma.hpp】

pYYBAGMj7MyAbcGLAABcjVqQTZo262.jpg

展开后可见:

poYBAGMj7N-Aa6YVAAA8D8bxK8o324.jpg

很简单,就是在完成 Provider 的核心使命,提供一个创建插件类对象的 create () 函数。与 SimpleWarriorProvider 类似,另外两个 Warrior 派生类 Eagle 和 Jaguar 大体也是这么写的。示意图如下: d2e2adc4-3432-11ed-ba43-dac502259ad0.png 在研究 Pluma 所给示例时,我已事先将 Pluma 封装成静态库了,现在要把 Eagle 和 Jaguar 编译并封装成一个动态库,就需要链接 Pluma 静态库,除此之外,还需要编译其他一些辅助文件,列举如下: 1)Connector.cpp 2)dllmain.cpp 3)Eagle.hpp 4)Jaguar.hpp 5)Warrior.cpp 其中 Connector.cpp 文件,是插件动态库向外界 Host 注册自己所有 Provider 的地方。它必须实现一个 connect () 函数,代码截选如下:

poYBAGMj7P6AI6h3AABiredLvDQ088.jpg

我们先不要着急分析上面的 connect () 动作,可以先跟着我看看插件的加载流程,后文我们就会知道,connect () 只是加载流程的一环而已。

4. 插件加载流程

我们看一下 Pluma 架构所给例子的 main () 函数,就可以了解插件的加载流程了:

pYYBAGMj7RiABMrSAAELwFnSM8M836.jpg

其中和加载插件相关的句子主要就是 pluma.acceptProviderType 和 pluma.load 两句了。前者主要负责在 Host 的knownTypes映射表中添加一个 ProviderInfo 节点,后者负责加载插件动态库,并将动态库里匹配的 Provider 指针记入 ProviderInfo 节点。

4.1 pluma.acceptProviderType<>()

我们先说 pluma.acceptProviderType 一句。在前文介绍 Pluma.inl 文件的内容时,我们已经看到一个叫作 acceptProviderType 的模板函数了,当时没有细说,现在我把它的代码再贴一下: 【pluma-1.1/include/pluma/Pluma.inl】

poYBAGMj7TiAck7tAABsH0sPDoI046.jpg

里面调用的是 PluginManager 基类的 registerType () 函数。 我们前文主要关心的是 PLUMA_PROVIDER_TYPE,现在再说一下后两个参数。PLUMA_INTERFACE_VERSION 表示管理器当前应该使用的插件接口的版本,因为我们不能确定更高版本的插件接口会不会增加或删除成员函数,所以这个值其实是个限定值,如果后续用户尝试加载更高版本的插件,那么是无法通过校验的。 第三个参数 PLUMA_INTERFACE_LOWEST_VERSION 则是限定最低值,如果尝试加载比这个值更低版本的插件,肯定也是不会通过的。 在刚刚看到的 main () 函数里,是这样写的:

pluma.acceptProviderType();

也就是说,Pluma 插件管理器对 Warrior 接口对应的 WarriorProvider 类感兴趣。而当初定义 Warrior 时,在 Warrior.cpp 文件里的确指明了 WarriorProvider 能限定的当前版本号和最低版本号:

PLUMA_PROVIDER_SOURCE(Warrior, 1, 1);

这些类型信息、版本号限定信息都会被注册在 Host 的 knownTypes 映射表中,每种接口类型对应一个 ProviderInfo 节点。注册动作的代码如下:

【pluma-1.1/src/pluma/PluginManager.cpp】

pYYBAGMj7WyAEUAbAABNgvDLs0g044.jpg

【pluma-1.1/src/pluma/Host.cpp】

pYYBAGMj7YKAdyMMAABiYEWlb3o152.jpg

当然,新加的 ProviderInfo 节点的 providers 列表是个空列表,待后续再添加 Provider * 内容。

4.2 pluma.load()

接着,我们继续看 main () 函数里调用的 pluma.load (),其实调用的是其父类 PluginManager 的 load ()。相关代码截选如下:

【pluma-1.1/src/pluma/PluginManager.cpp】

poYBAGMj7ZiANhKSAADTuumCVjE057.jpg

可以看到,一开始就在着手加载动态库,并调用动态库里的 connect () 函数。前文我们实际上已经列举过示例代码里的 connect () 函数了,现在再贴一次:

poYBAGMj7a-ALQ-rAABA98TDc7Y693.jpg

前文在阐述到 connect () 时,暂时没有细说 add () 动作,现在我们来看看它的代码:

【pluma-1.1/src/pluma/Host.cpp】

poYBAGMj7cSAWJqUAACPOR6pUWQ601.jpg

上面代码中那个 plumaGetType () 函数其实是 Provider 的私有成员,一般人访问不了,但 Host 是它的友元类,所以可以访问。代码中会先校验待添加的 Provider 是否合格,如果合格则以 plumaGetType () 返回值为 key 值,并向临时映射表 addRequests 中添加该 Provider 指针。所谓合格是指,这个 Provider 的类型是 Host 感兴趣的,并且其版本号也是合适的。

值得注意的是,待添加的 Provider*,只是临时先放进一个 addRequests 映射表中。addRequests 映射表的定义如下:

【pluma-1.1/include/pluma/Host.hpp】

poYBAGMj7d6AEQzAAAAviHyQa3M128.jpg

那么这个临时性的 addRequests 映射表的内容会怎样处理呢?说起来也简单,会被 “搬移” 进 Host 的 knownTypes 映射表中某个 ProviderInfo 的内部列表去。main () 在调用完 connect () 函数后,调用的 confirmAddictions () 就是做这个事情的:

【pluma-1.1/src/pluma/Host.cpp】

poYBAGMj7fiAGhc6AAC7cpn6_Q8730.jpg

我们画一张调用关系图看看:


d302c168-3432-11ed-ba43-dac502259ad0.png   

我们可以通过这张调用关系图回顾一下,主要流程就是在加载插件动态库,并执行动态库里的 connect () 函数。该函数会将动态库里可用的所有 Provider * 记入 Host 的 knownTypes 映射表中。

同时,动态库对应的 DLibrary 对象,也会插入 Pluma 管理类内部的 libraries 映射表中。

为了巩固知识,我们把前文的两张图再整合一下。


d348a0ca-3432-11ed-ba43-dac502259ad0.png

5. 使用插件 Provider

5.1 pluma.getProviders()

在 Providers 都添加进 Pluma 管理类后,我们就可以在需要时获取 provider 了,为此 Pluma 类提供了 getProviders () 函数:

【pluma-1.1/src/pluma/PluginManager.cpp】

poYBAGMj7iOAJtYcAAA8tMoiTbU450.jpg

【pluma-1.1/src/pluma/Host.cpp】

pYYBAGMj7jaAPO0HAABhoNso11Q109.jpg

代码很简单,就是帮使用者把感兴趣的某类插件 Provider 全部找出来。如果当初我们已经通过 acceptProviderType () 注册了对应的类型(PLUMA_PROVIDER_TYPE),那么至少可以拿到一个 list,否则就只能拿到 NULL 了。如果我们可以拿到若干 Provider,就可以调用其 create () 函数创建对应的插件对象了。

当工作做完后,用户应该及时 delete 掉之前创建出的插件对象。在程序退出之前,用户应该调用 pluma.unloadAll () 删除所有插件 Provider 及 DLibrary 对象。DLibrary 对象析构时,会自动关闭已经打开的动态链接库。

【pluma-1.1/src/pluma/PluginManager.cpp】

pYYBAGMj7lGALRXEAAEA--e0ITw849.jpg

6. 结束

至此,Pluma 架构的主体代码就分析完毕了,希望对大家有所帮助。


审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 管理器
    +关注

    关注

    0

    文章

    246

    浏览量

    18502
  • 字符串
    +关注

    关注

    1

    文章

    578

    浏览量

    20506
  • 开源架构
    +关注

    关注

    0

    文章

    8

    浏览量

    6951

原文标题:聊聊Pluma插件管理框架

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Chrome浏览器插件

    、什么是浏览器插件 浏览器插件是依附于浏览器,用来拓展网页能力的程序。插件具有监听浏览器事件、获取和修改网页元素、拦截网络请求、添加快捷菜单等功能。使用浏览器
    的头像 发表于 11-18 17:12 293次阅读
    写<b class='flag-5'>一</b><b class='flag-5'>个</b>Chrome浏览器<b class='flag-5'>插件</b>

    使用OpenVINO GenAI API在C++中构建AI应用程序

    许多桌面应用程序是使用 C++ 开发的,而将生成式AI(GenAI)功能集成到这些应用程序中可能会很具有挑战性,尤其是因为使用像 Hugging Face 这样的 Python 库的复杂性。C++
    的头像 发表于 10-12 09:36 366次阅读
    使用OpenVINO GenAI API在<b class='flag-5'>C++</b>中构建AI应用程序

    【飞凌嵌入式OK3576-C开发板体验】RKNN神经网络算法开发环境搭建

    的软件包管理系统和环境管理系统,主要用于安装、更新、删除软件包及其依赖关系,并允许用户在不同环境之间轻松切换。用于后续安装Python等插件
    发表于 10-10 09:28

    OpenVINO2024 C++推理使用技巧

    很多人都使用OpenVINO新版的C++ 或者Python的SDK,都觉得非常好用,OpenVINO2022之后的版本C++ SDK做了大量的优化与整理,已经是非常贴近开发的使用习惯与推理方式。与OpenCV的Mat对象对接方式
    的头像 发表于 07-26 09:20 873次阅读

    C++中实现类似instanceof的方法

    C++有多态与继承,但是很多人开始学习C++,有时候会面临常见问题,就是如何向下转型,特别是不知道具体类型的时候,这个时候就希望C++
    的头像 发表于 07-18 10:16 573次阅读
    <b class='flag-5'>C++</b>中实现类似instanceof的方法

    keilc++编译含有rtos模块时的错误问题怎么解决?

    近期看到哥们c++写嵌入式,感觉蛮有趣,如果当初我的程序要是能用类就轻松多了,所以也想尝试下。虽然不会
    发表于 05-09 08:29

    鸿蒙OS开发实例:【Native C++

    使用DevEco Studio创建Native C++应用。应用采用Native C++模板,实现使用NAPI调用C标准库的功能。使用
    的头像 发表于 04-14 11:43 2591次阅读
    鸿蒙OS<b class='flag-5'>开发</b>实例:【Native <b class='flag-5'>C++</b>】

    请问CubeIDE如何支持C++开发

    CubeIDE如何支持C++开发。有没有些例程。
    发表于 03-25 06:22

    鸿蒙实战开发学习:【HiView插件开发

    Hiview是跨平台的终端设备维测服务集,其中是由插件管理平台和插件实现的各自功能构成整套系统。 本文描述了hiview
    的头像 发表于 03-12 11:52 1317次阅读
    鸿蒙实战<b class='flag-5'>开发</b>学习:【HiView<b class='flag-5'>插件</b><b class='flag-5'>开发</b>】

    c语言,c++,java,python区别

    C语言、C++、Java和Python是四种常见的编程语言,各有优点和特点。 C语言: C语言是种面向过程的编程语言。它具有底层的特性,能
    的头像 发表于 02-05 14:11 2361次阅读

    vb语言和c++语言的区别

    Microsoft开发种面向对象的事件驱动编程语言。它的设计目标是简化编程过程,让初学者也能快速上手。与之相比,C++语言是种通用的、面向对象的编程语言,其设计目标是提供高性能的
    的头像 发表于 02-01 10:20 2257次阅读

    C++在Linux内核开发中从争议到成熟

    Linux 内核邮件列表中篇已有六年历史的老帖近日再次引发激烈讨论 —— 主题是建议将 Linux 内核的开发语言从 C 转换为更现代的 C++
    的头像 发表于 01-31 14:11 623次阅读
    <b class='flag-5'>C++</b>在Linux内核<b class='flag-5'>开发</b>中从争议到成熟

    鸿蒙开发什么语言?

    两种开发方向 我们常说鸿蒙开发,但是其实鸿蒙开发分为两方向: 一个是系统级别的开发,比如驱动,
    的头像 发表于 01-30 16:12 1530次阅读
    鸿蒙<b class='flag-5'>开发</b><b class='flag-5'>用</b>什么语言?

    基于QT5+OpenCV+OpenVINO C++的应用打包过程

    QT C++写了YOLOv5模型推理演示应用。
    的头像 发表于 01-26 10:17 1230次阅读
    基于QT5+OpenCV+OpenVINO <b class='flag-5'>C++</b>的应用打包过程

    C++简史:C++是如何开始的

    MISRA C++:2023,MISRA® C++ 标准的下一个版本,来了!为了帮助您做好准备,我们介绍了 Perforce 首席技术支持工程师 Frank van den Beuken 博士撰写
    的头像 发表于 01-11 09:00 581次阅读
    <b class='flag-5'>C++</b>简史:<b class='flag-5'>C++</b>是如何开始的