Qt使用插件QPluginLoader 机制开发
简介:
插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。
Qt 提供了2种APIs来创建插件:
一种高级API,用于为Qt本身编写插件:自定义数据库驱动程序,图像格式,文本编解码器,自定义样式等。
一种用于扩展Qt应用程序的低级API。也就是说扩展我们自己使用Qt编写的程序。这要求应用程序使用 QPluginLoader 检测和加载插件。在这种情况下,插件可以提供任意功能,不限于数据库驱动程序、图像格式、文本编解码器、样式以及扩展Qt功能的其他类型的插件。本文主要通过示例来展示第二种方式创建插件。
原理:
重载了虚函数的dll,这跟抽象工厂类类似,这便是插件的原理。qt的插件可以说是一种动态库
Qt插件特点:
核心技术:QPluginLoader
Qt插件存储在共享库(DLL)中(即以动态库的方式存在),与使用QLibrary访问的共享库相比,它具有以下优势:
面向Interface编程,内部封装,模块和整体流程开发分离,提高开发效率。
QPluginLoader 检查插件是否与应用程序相同版本的 Qt 链接。
QPluginLoader 提供对根组件对象 (instance()) 的直接访问,而不是强制您手动解析 C 函数
插件
插件主要面向接口编程,无需访问.lib文件,热插拔、利于团队开发。即使在程序运行时.dll不存在,也可以正常启动,只是相应插件功能无法正常使用而已;
动态库
动态库需要访问.lib文件,而且在程序运行时必须保证.lib存在,否则无法正常启动
开发测试环境:QT5.15.2 + MSVC 2019
在容器端:
- 定义一组接口(只有纯虚函数的类)。
- 在QT_BEGIN_NAMESPACE和QT_END_NAMESPACE之间使用Q_DECLARE_INTERFACE()宏告诉Qt的元对象系统有关接口的信息。
- 在程序中使用 QPluginLoader加载插件。
- 使用qobject_cast()测试插件是否实现给定的接口。
Q_DECLARE_INTERFACE
Q_DECLARE_INTERFACE(类名,标识符)
此宏用于把标识符与类名接口关联起来。这个标识符是唯一的,这个宏通常在被放到一个类被定后的位置。
这个是Qt实现插件必须要有的一个宏, 把标识符与类名接口关联起来。
在插件端:
声明一个插件类,该类继承自QObject和插件要提供的接口类。
使用Q_INTERFACES()宏告诉Qt的元对象系统有关接口的信息。
使用Q_PLUGIN_METADATA()宏导出插件。
Q_PLUGIN_METADATA
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json")
这个宏第一次参数定义了一个uuid,保证唯一即可,第二个json是必须要有的,当无法找到指定的文件时,moc 会出现错误,即使是空的文件也行
SubPlugin.json文件存放在源代码目录下,可以记录一下插件的信息(如插件名称、插件版本、插件依赖库),如下
{
"name": "testSubPlugin",
"version": "1.0",
"dependencies": []
}
这个宏被用于声明元数据,这个元数据是被实例化插件的一部分。
这个宏需要通过对象声明被实例化接口的IID,并且要引用包含元数据内容的文件。
需要获取这些元数据(即上面的json数据),可在在通过下面的办法。
QPluginLoader *loader = new QPluginLoader(this);
loader->setFileName(plugin.absoluteFilePath());
qDebug()<<loader->metaData().keys();
QJsonObject json = loader->metaData().value("MetaData").toObject();
QVariant var = json.value("name").toVariant();
Q_INTERFACES
Q_INTERFACES(AppInterface) //声明这个插件实现是基于哪个插件接口的,使qobject_cast()能正确进行QObject*到接口指针的转换
QPluginLoader 类
~QPluginLoader()
销毁 QPluginLoader 对象。
除非显式调用 unload(),否则插件会一直保留在内存中,直到应用程序终止。
Load
加载插件,如果插件加载成功则返回true; 否则返回false。
unload
卸载插件,如果插件可以卸载则返回true,否则返回false。
这在应用程序终止时自动发生,因此通常不需要调用此函数。
如果 QPluginLoader 的其他实例正在使用相同的插件,则调用将失败,并且只有在每个实例都调用了 unload() 时才会发生卸载。
不要尝试删除根组件。 而是依靠 unload() 会在需要时自动删除它
Instance
返回插件的根组件对象,组件对象是一个 QObject。 使用 qobject_cast() 可将其转成所需的对象。如果根组件对象被销毁,则调用此函数会创建一个新实例。
该函数返回的根组件在 QPluginLoader 销毁时不会被删除。 如果要确保删除根组件,则应在不再需要访问核心组件时立即调用 unload()。 当库最终卸载时,根组件将自动删除。
metaData
返回此插件的元数据。 元数据是在编译插件时使用 Q_PLUGIN_METADATA() 宏以 json 格式指定的数据。
例子
首先创建一个Qt Subdirs Project工程PluginDemo,并添加一个app应用程序端。并在app.pro添加输出目录DESTDIR = $$PWD/../bin
- 然后添加一个appinterface.h的头文件,该类作为插件接口类。
#ifndef APPINTERFACE_H#define APPINTERFACE_H#include <QList>#include <QAction>class AppInterface{public:virtual ~AppInterface() {}// 插件的名字virtual QString name() const = 0;// 插件返回的QAction列表virtual QList<QAction *> actions() const = 0;};QT_BEGIN_NAMESPACEQ_DECLARE_INTERFACE(AppInterface, "plugindemo_app_interface")QT_END_NAMESPACE#endif // APPINTERFACE_H
- 在工程上右键,选择“新子项目”,选择Library->C++ Library,类型选择“shared Library”,qt module 选择“widgets”.插件名称为subplugin.
- 修改subplugin.pro
添加CONFIG+=plugin //目的就是为是该dll作为插件
target.path = $$PWD/../bin/plugins、INSTALL+=target //把插件dll拷贝到容器的bin目录下。需构建步骤添加 make install
- 实现接口类SubPlugin.h,注意添加Q_INTERFACES和Q_PLUGIN_METADATA宏
#ifndef SUBPLUGIN_H#define SUBPLUGIN_H#include "subPlugin_global.h"#include "../app/appinterface.h"#include <QObject>class SUBPLUGIN_EXPORT SubPlugin : public QObject, public AppInterface{public:SubPlugin();Q_OBJECTQ_INTERFACES(AppInterface) //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json") //使用Q_PLUGIN_METADATA宏导出插件// AppInterface interfacepublic:virtual QString name() const override;virtual QList<QAction *> actions() const override;};#endif // SUBPLUGIN_H#include "subplugin.h"SubPlugin::SubPlugin(){}QString SubPlugin::name() const{return "SubPlugin";}QList<QAction *> SubPlugin::actions() const{QAction *aboutQt = new QAction;aboutQt->setText(tr("About Qt"));QList<QAction *> result;result << aboutQt;return result;}
- 在容器的Dialog.cpp里面添加加载插件的代码。
#include "dialog.h"#include "ui_dialog.h"#include "appinterface.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){loadPlugins();ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::loadPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i) {QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result) {QObject *pluginInstance = loader->instance();AppInterface *appInterface = qobject_cast<AppInterface *>(pluginInstance);if (appInterface != nullptr) {qDebug() << "add actions from plugin:" << appInterface->name();QList<QAction *> actions = appInterface->actions();for (int k = 0; k < actions.count(); ++k) {QAction *ac = actions.at(k);QString strtext = ac->text();}}} else {qDebug() << loader->errorString();}}}
插件管理
自定义pluginmanager类,封装成dll
管理所有插件,包括加载、卸载等。并暴露方法给主程序。
插件管理器代码
插件接口
#ifndef PLUGININTERFACE_H#define PLUGININTERFACE_H#include <QtWidgets/qwidget.h>#include <QString>class PluginInterface{public:virtual ~PluginInterface() {}// 插件返回的QAction列表virtual QString datawrite() = 0;};Q_DECLARE_INTERFACE(PluginInterface, "PluginManager.PluginInterface")#endif // PLUGININTERFACE_H
插件管理器
#ifndef PLUGINMANAGER_H#define PLUGINMANAGER_H#include "PluginManager_global.h"#include <QObject>#include <QPluginLoader>class PLUGINMANAGER_EXPORT PluginManager:public QObject{Q_OBJECTprivate:PluginManager();public:static PluginManager *getinstance();//加载所有插件void loadAllPlugins();//卸载所有插件void unloadAllPlugins();QString datawrite();private:static PluginManager *m_instance;QHash<QString, QPluginLoader *>m_loaders; //插件路径--QPluginLoader实例};#endif // PLUGINMANAGER_H#include "pluginmanager.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QCoreApplication>#include"PluginInterface.h"PluginManager* PluginManager::m_instance=nullptr;PluginManager::PluginManager(){}PluginManager *PluginManager::getinstance(){if(m_instance==nullptr)m_instance= new PluginManager();return m_instance;}void PluginManager::loadAllPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i){QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result){m_loaders.insert(plugin.absoluteFilePath(), loader);QObject *pluginInstance = loader->instance();PluginInterface *appInterface = qobject_cast<PluginInterface *>(pluginInstance);if (appInterface != nullptr){}}else{qDebug() << loader->errorString();}}}void PluginManager::unloadAllPlugins(){for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);//卸载插件,并从内部数据结构中移除if(loader->unload()){m_loaders.remove(filepath);delete loader;loader = nullptr;}}}QString PluginManager::datawrite(){QString str;for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);PluginInterface *Plugin = dynamic_cast<PluginInterface*>(loader->instance());if(Plugin)str = Plugin->datawrite();}return str;}
插件代码
#ifndef LOGPLUGIN_H#define LOGPLUGIN_H#include "logPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class LOGPLUGIN_EXPORT LogPlugin: public QObject,public PluginInterface{Q_OBJECTQ_INTERFACES(PluginInterface) //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-LogPlugin" FILE "LogPlugin.json") //使用Q_PLUGIN_METADATA宏导出插件public:LogPlugin();virtual QString datawrite() override;};#endif // LOGPLUGIN_H#ifndef DBPLUGIN_H#define DBPLUGIN_H#include "DbPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class DBPLUGIN_EXPORT DbPlugin : public QObject,public PluginInterface{public: Q_OBJECTQ_INTERFACES(PluginInterface) //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-DbPlugin" FILE "DbPlugin.json") //使用Q_PLUGIN_METADATA宏导出插件public:DbPlugin();virtual QString datawrite() override;};#endif // DBPLUGIN_H
App代码
#include "dialog.h"#include "ui_dialog.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>#include "../PluginManager/pluginmanager.h"Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::on_pushButton_clicked(){PluginManager::getinstance()->loadAllPlugins();PluginManager::getinstance()->datawrite();PluginManager::getinstance()->unloadAllPlugins();}
插件通信
插件的通信通过插件管理器来管理,插件管理器转发插件的消息
- 在插件管理器的接口头文件增加一个插件间通信的结构体,增加插件发送消息和接收消息的纯虚函数。
Q_DECLARE_METATYPE(Type)
作用:向Qt元系统注册一些非基本类型
这个宏能使Qt元系统的QMetaType知道Type类型,但前提是Type类型必须提供一个公有的默认构造函数、公有的默认拷贝构造函数和公有的默认析构函数。
- 在插件重写插件管理器声明的接收虚函数,把插件管理器声明的发送虚函数声明为信号。
- 在插件管理器的加载插件处加上connect函数,是信号槽关联起来。其中把插件发送信号关联插件管理器的接收槽函数。
同时让插件管理器的接收槽函数转发给对应的插件
- 在app程序中调用调用插件的信号从而发送消息
完整代码参见:
链接:https://pan.baidu.com/s/1fWK_oSYQTRNip4FLnTOLwg
提取码:qc0t
相关文章:

Qt使用插件QPluginLoader 机制开发
简介: 插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。 Qt 提供了2种APIs来创建插件: 一种高级API,用于为Qt本身编写插件:自定义数据库驱动程序,图像格…...

双子座 Gemini1.5和谷歌的本质
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

二百三十、MySQL——MySQL表的索引
1 目的 梳理一下目前MySQL维度表的索引情况,当然网上也有其他博客专门讲MySQL索引的,我这边只是梳理一下目前的索引状况而已 2单列索引 2.1 索引截图 2.2 建表语句 3 联合索引 3.1 索引截图 3.2 建表语句 4 参考的优秀博客 http://t.csdnimg.cn/ZF7…...

并发编程之ThreadLocal使用及原理
ThreadLocal主要是为了解决线程安全性问题的 非线程安全举例 public class ThreadLocalDemo {// 非线程安全的private static final SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static Date parse(String strDate) throws ParseExc…...

软件测试 测试开发丨Pytest结合数据驱动-yaml,熬夜整理蚂蚁金服软件测试高级笔试题
编程语言 languages: PHPJavaPython book: Python入门: # 书籍名称 price: 55.5 author: Lily available: True repertory: 20 date: 2018-02-17 Java入门: price: 60 author: Lily available: False repertory: Null date: 2018-05-11 yaml 文件使用 查看 yaml 文件 pych…...

软考数据库---2.SQL语言
主要记忆:表、索引、视图操作语句;数据操作;通配符、转义符;授权;存储过程;触发器 这部分等等整理一下: “”" 1、 数据定义语言。 SQL DDL提供定义关系模式和视图、 删除关系和视图、 修改关系模式的…...

基于顺序表实现通讯录
上篇我们讲了顺序表是什么,和如何实现顺序表。这篇文章我们将基于顺序表来实现通讯录。 文章目录 前言一、基于顺序表是如何实现的二、通讯录的头文件和实现文件三、通讯录的实现3.1 定义通讯录结构3.2 初始化通讯录3.3 销毁通讯录3.4 通讯录添加数据3.5 查找联系人…...

咸鱼之王_手游_开服搭建架设_内购修复无bug运营版
视频演示 咸鱼之王_手游_开服 游戏管理后台界面 源码获取在文章末尾 源码获取在文章末尾 源码获取在文章末尾 或者直接下面 https://githubs.xyz/y28.html 1.安装宝塔 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &…...

【JSON2WEB】14 基于Amis的CRUD开发30分钟速成
【JSON2WEB】系列目录 【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSO…...

Java入门教程||Java 变量
Java 变量 Java教程 - Java变量 变量由标识符,类型和可选的初始化程序定义。变量还具有范围(可见性/生存期)。 Java变量类型 在Java中,必须先声明所有变量,然后才能使用它们。变量声明的基本形式如下所示࿱…...

基于Java的校园快递一站式服务系统 (源码+文档+包运行)
一.系统概述 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本校园快递一站式服务系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞…...

通讯录的实现(顺序表版本)
我们知道通讯录是基于顺序表的前提下,要写好通讯录我们就要深入了解好顺序表。我们先来看看什么是顺序表。(注意今天代码量有点多,坚持一下)。冲啊!兄弟们! 顺序表的简单理解 对于顺序表,我们首…...

利用Sentinel解决雪崩问题(一)流量控制
1、解决雪崩问题的常见方式有四种: 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待;舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离;熔断降级:由断路器统计业务…...
二叉树总结
递归返回值 1、如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。 2、如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 3、如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,…...

接口优化技巧
一、背景 针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案 二、接口优化方案总结 1.批处理 批量思想:批量操作数据库&a…...

【工具】NPS 内网穿透搭建
背景 在日常开发中经常会涉及到使用公网某个端口进行开发调试的情况,但我们日常开发的机器IP是非公网IP,所以需要使用内网穿透的手段,使我们的服务在公网上能被访问到。 常用的内网穿透工具分两大类,一类是付费/免费服务…...

【数学】主成分分析(PCA)的详细深度推导过程
本文基于Deep Learning (2017, MIT),推导过程补全了所涉及的知识及书中推导过程中跳跃和省略的部分。 blog 1 概述 现代数据集,如网络索引、高分辨率图像、气象学、实验测量等,通常包含高维特征,高纬度的数据可能不清晰、冗余&am…...

微信跳转页面时发生报错
报错如下图所示: 解决方法:(从下面四种跳转方式中任选一种,哪种能实现效果就用哪个) 带历史回退 wx.navigateTo() //不能跳转到tabbar页面 不带历史回退 wx.redirectTo() //跳转到另一个页面wx.switchTab() //只能…...

8:系统开发基础--8.1:软件工程概述、8.2:软件开发方法 、8.3:软件开发模型、8.4:系统分析
转上一节: http://t.csdnimg.cn/G7lfmhttp://t.csdnimg.cn/G7lfm 课程内容提要: 8:知识点考点详解 8.1:软件工程概述 1.软件的生存周期 2.软件过程改进—CMM Capability Maturity Model能力成熟度模型 3.软件过程改进—CMMI—…...

【简单讲解下Symfony框架】
🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...

【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...