当前位置: 首页 > news >正文

C++动态加载 插件

动态加载(Dynamic Loading)是指在程序运行时,根据需要动态地加载和链接代码或资源。

动态加载的主要目的是实现程序的灵活性和可扩展性,以及减少内存消耗和启动时间。通过动态加载,程序可以根据运行时的需求加载特定的模块、库或插件,而不需要将所有代码和资源都打包在一起。这样可以实现模块化设计,减少程序的体积,以及在需要时进行动态升级和扩展。

在C++中,常见的动态加载方式是使用动态链接库(Dynamic Link Library,DLL)或共享对象(Shared Object,SO)文件。通过使用操作系统提供的动态链接库机制,程序可以在运行时加载、链接和调用动态链接库中的函数和数据。

动态加载通常包括以下步骤:

加载库:程序使用操作系统提供的函数加载指定的动态链接库或共享对象文件,并获得一个句柄或句柄指针。
解析符号:程序使用操作系统提供的函数根据函数名或符号名称从动态链接库中获取对应的函数指针或变量地址。
使用功能:程序通过获取到的函数指针或变量地址,可以调用动态链接库中的函数或访问其中的变量。
卸载库:当不再需要使用动态链接库时,程序使用操作系统提供的函数(如dlclose())关闭句柄,并释放相关资源。

动态加载可以在运行时根据需求选择加载特定的模块或插件,从而实现更灵活和可扩展的软件架构。它常用于插件系统、模块化设计、动态升级和动态扩展等场景。同时,需要注意合理管理和使用动态加载,在加载和卸载过程中保证正确性和安全性,避免内存泄漏或资源冲突等问题。

常用的动态加载库的方式

1. 使用dlopen()和dlsym()函数(UNIX-like系统):

UNIX-like系统是指类似于UNIX操作系统的系统家族
Linux:
Linux是一种基于UNIX设计原则的开源操作系统。它以自由、开放源代码和高度可定制性而闻名,并且广泛用于服务器、嵌入式系统和个人计算机等领域。
macOS:
macOS是苹果公司开发的基于UNIX的操作系统,主要用于苹果的Mac电脑和服务器设备。它具有直观的用户界面和强大的开发者工具,支持多种应用程序和开发环境。
FreeBSD:
FreeBSD是一个自由、开源的UNIX-like操作系统,它是从BSD项目分支出来的一个分支。FreeBSD具有高度可靠性、稳定性和安全性,广泛应用于服务器和网络设备。
Solaris:
Solaris是由甲骨文公司(Oracle)开发和支持的UNIX操作系统。它具有强大的网络功能、可扩展性和可靠性,广泛用于企业级服务器和高性能计算领域。
包括但不限于以上几种常见的操作系统。

使用dlopen()和dlsym()函数的常见限制:

  1. 平台限制:
    dlopen()和dlsym()函数是针对UNIX-like系统的动态链接库机制,因此在不同平台上的可用性和行为可能会有所不同。比如,Windows系统使用的是LoadLibrary()和GetProcAddress()函数。
  2. 符号查找限制:
    dlsym()函数是通过符号名称来查找并返回对应的函数指针或变量地址。因此,对于不可重定位的代码或使用了隐藏符号的库,dlsym()可能无法直接获取到这些符号。可以通过在编译时使用-fvisibility=hidden选项来隐藏符号,但需要提供额外的导出接口。
  3. 符号命名限制:
    当使用dlsym()查找函数时,函数名需要与库中定义的符号名称完全匹配。这包括符号名称的大小写、命名空间和重载等因素。如果符号名称没有正确匹配,dlsym()将无法获取到函数指针。
  4. 依赖关系处理:
    dlopen()和dlsym()函数不会自动处理库之间的依赖关系。如果一个库依赖于其他库,需要手动调用dlopen()加载所有依赖库,并确保它们按照正确的顺序加载和链接。
  5. 版本兼容性:
    动态加载的库可能会在不同的版本之间发生变化,导致函数签名、结构体布局或符号名称的变化。在使用dlsym()获取函数指针时,需要确保库的版本兼容性,以免出现函数调用错误或内存访问错误。
  6. 安全性问题:
    动态加载库的方式可能存在一些安全性问题,如潜在的代码注入、恶意库替换等。因此,在使用dlopen()和dlsym()时需要谨慎处理,确保加载和使用的库来自可信源,并进行适当的安全检查。
#include <dlfcn.h> // 动态链接库相关头文件
#include <iostream>
int main() {// 加载库void* libraryHandle = dlopen("./libexample.so", RTLD_LAZY);if (!libraryHandle) {std::cerr << "Failed to load library: " << dlerror() << std::endl;return 1;}// 获取库中的函数指针typedef void (*FunctionPtr)();FunctionPtr function = reinterpret_cast<FunctionPtr>(dlsym(libraryHandle, "exampleFunction"));if (!function) {std::cerr << "Failed to get function: " << dlerror() << std::endl;dlclose(libraryHandle);return 1;}// 使用函数function();// 关闭库dlclose(libraryHandle);return 0;
}

2. 使用LoadLibrary()和GetProcAddress()函数(Windows平台):

使用LoadLibrary()和GetProcAddress()函数的常见限制:

  1. 平台限制:
    LoadLibrary()和GetProcAddress()函数是Windows操作系统特有的函数,因此只能在Windows平台上使用。在其他操作系统上,使用dlopen()和dlsym()函数进行动态加载库。
  2. 文件路径限制:
    LoadLibrary()函数需要指定库文件的完整路径,或者是根据Windows系统的搜索规则进行查找。如果库文件不存在或无法找到,LoadLibrary()将失败。因此,需要确保提供正确的库文件路径。
  3. 符号查找限制:
    GetProcAddress()函数是通过函数名来查找并返回对应的函数指针。与dlsym()类似,对于不可重定位的代码或使用了隐藏符号的库,GetProcAddress()可能无法直接获取到这些符号。可以通过在编译时使用__declspec(dllexport)来导出符号,但需要提供额外的导出接口。
  4. 符号命名限制:
    当使用GetProcAddress()查找函数时,函数名需要与库中定义的符号名称完全匹配。这包括符号名称的大小写、命名空间和重载等因素。如果符号名称没有正确匹配,GetProcAddress()将无法获取到函数指针。
  5. 依赖关系处理:
    LoadLibrary()和GetProcAddress()函数不会自动处理库之间的依赖关系。如果一个库依赖于其他库,需要手动调用LoadLibrary()加载所有依赖库,并确保它们按照正确的顺序加载和链接。
  6. 版本兼容性:
    动态加载的库可能会在不同的版本之间发生变化,导致函数签名、结构体布局或符号名称的变化。在使用GetProcAddress()获取函数指针时,需要确保库的版本兼容性,以免出现函数调用错误或内存访问错误。
  7. 安全性问题:
    动态加载库的方式可能存在一些安全性问题,如潜在的代码注入、恶意库替换等。因此,在使用LoadLibrary()和GetProcAddress()时需要谨慎处理,确保加载和使用的库来自可信源,并进行适当的安全检查。
#include <windows.h> // Windows平台下的头文件
#include <iostream>
int main() {// 加载库HINSTANCE libraryHandle = LoadLibrary(TEXT("example.dll"));if (!libraryHandle) {std::cerr << "Failed to load library: " << GetLastError() << std::endl;return 1;}// 获取库中的函数指针typedef void (*FunctionPtr)();FunctionPtr function = reinterpret_cast<FunctionPtr>(GetProcAddress(libraryHandle, "exampleFunction"));if (!function) {std::cerr << "Failed to get function: " << GetLastError() << std::endl;FreeLibrary(libraryHandle);return 1;}// 使用函数function();// 关闭库FreeLibrary(libraryHandle);return 0;
}

完整示例

创建插件接口:

// PluginInterface.h
class PluginInterface {
public:virtual void doSomething() = 0;
};

创建插件实现:

// PluginImplementation.cpp
#include "PluginInterface.h"
#include <iostream>
class PluginImplementation : public PluginInterface {
public:void doSomething() override {std::cout << "Plugin is doing something!" << std::endl;}
};
// 导出插件实现的函数
extern "C" PluginInterface* createPlugin() {return new PluginImplementation();
}extern "C" void destroyPlugin(PluginInterface* plugin) {delete plugin;
}

编译插件为动态链接库或共享对象文件:

$ g++ -shared -fPIC PluginImplementation.cpp -o plugin.so

在主程序中加载插件并使用功能:

// Main.cpp
#include "PluginInterface.h"
#include <dlfcn.h> // 动态链接库相关头文件
#include <iostream>
int main() {// 加载插件void* pluginHandle = dlopen("./plugin.so", RTLD_LAZY);if (!pluginHandle) {std::cerr << "Failed to load plugin: " << dlerror() << std::endl;return 1;}// 获取插件中的函数指针typedef PluginInterface* (*CreatePluginFunc)();CreatePluginFunc createPlugin = reinterpret_cast<CreatePluginFunc>(dlsym(pluginHandle, "createPlugin"));if (!createPlugin) {std::cerr << "Failed to get createPlugin function: " << dlerror() << std::endl;dlclose(pluginHandle);return 1;}// 创建插件实例PluginInterface* plugin = createPlugin();// 使用插件功能plugin->doSomething();// 销毁插件实例typedef void (*DestroyPluginFunc)(PluginInterface*);DestroyPluginFunc destroyPlugin = reinterpret_cast<DestroyPluginFunc>(dlsym(pluginHandle, "destroyPlugin"));if (!destroyPlugin) {std::cerr << "Failed to get destroyPlugin function: " << dlerror() << std::endl;dlclose(pluginHandle);return 1;}destroyPlugin(plugin);// 关闭插件dlclose(pluginHandle);return 0;
}

在上述示例中,使用了extern "C"来修饰createPlugin()函数。这是因为该函数是作为插件接口中的一个导出函数,它将被主程序动态加载并调用。通过使用extern "C"修饰,确保了函数名在生成的符号表中按照C语言的规则进行命名,以便主程序能够正确地找到并调用该函数。

需要注意的是,在使用extern "C"修饰函数时,需要确保该函数的声明和定义都位于extern "C"块内或者使用extern "C"修饰。这样才能保证函数名的一致性,避免因为名称修饰导致的链接错误。

相关文章:

C++动态加载 插件

动态加载&#xff08;Dynamic Loading&#xff09;是指在程序运行时&#xff0c;根据需要动态地加载和链接代码或资源。 动态加载的主要目的是实现程序的灵活性和可扩展性&#xff0c;以及减少内存消耗和启动时间。通过动态加载&#xff0c;程序可以根据运行时的需求加载特定的…...

redis的缓存更新策略

目录 三种缓存更新策略 业务场景&#xff1a; 主动更新的三种实现 操作缓存和数据库时有三个问题 1.删除缓存还是更新缓存&#xff1f; 2.如何保证缓存与数据库的操作的同时成功或失败&#xff1f; 3.先操作缓存还是先操作数据库&#xff1f; 缓存更新策略的最佳实践方案&am…...

Android应用开发(6)TextView进阶用法

Android应用开发学习笔记——目录索引 本章介绍文本视图&#xff08;TextView&#xff09;的显示&#xff0c;包括&#xff1a;设置文本内容、设置文本大小、设置文本显示颜色。 一、设置TextView显示内容 Layout XML文件中设置 如&#xff1a;res/layout/activity_main.xm…...

Matlab滤波、频谱分析

Matlab滤波、频谱分析 滤波&#xff1a; 某目标信号是由5、15、30Hz正弦波混合而成的混合信号&#xff0c;现需要设计一个滤波器滤掉5、30Hz两种频率。 分析&#xff1a;显然我们应该设计一个带通滤波器&#xff0c;通带频率落在15Hz附近。 % 滤波 % 某目标信号是由5、15、3…...

车载软件架构 —— 车载软件入侵检测系统

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 没有人关注你。也无需有人关注你。你必须承认自己的价值&#xff0c;你不能站在他人的角度来反对自己。人…...

“深入解析JVM内部机制:理解Java虚拟机的工作原理“

标题&#xff1a;深入解析JVM内部机制&#xff1a;理解Java虚拟机的工作原理 摘要&#xff1a;本文将深入探讨Java虚拟机&#xff08;JVM&#xff09;的内部机制&#xff0c;解释其工作原理。我们将讨论JVM的组成部分、类加载过程、运行时数据区域以及垃圾回收机制。此外&…...

FPGA初步学习之串口发送模块【单字节和字符串的发送】

串口相关简介 UART 在发送或接收过程中的一帧数据由4部分组成&#xff0c;起始位、数据位、奇偶校验位和停止位&#xff0c;如图所示。其中&#xff0c;起始位标志着一帧数据的开始&#xff0c;停止位标志着一帧数据的结束&#xff0c;数据位是一帧数据中的有效数据。 通常用…...

Kotlin重点理解安全性

目录 一 Kotlin安全性1.1 可空类型1.2 安全调用运算符1.3 Elvis 运算符1.4 非空断言运算符1.5 安全类型转换1.6 延迟初始化 一 Kotlin安全性 Kotlin 在设计时采用了一系列策略&#xff0c;旨在尽可能地减少空指针异常&#xff08;NullPointerException&#xff09;的出现。空指…...

基于Java+SpringBoot+SpringCloud+Vue的智慧养老平台设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…...

Spring中的全局异常处理

在项目中通常会定义各种异常&#xff0c;比如断言异常、业务异常&#xff0c;甚至调用外部接口报的异常需要转成自己系统中的异常类型&#xff0c;这时就需要将各种异常类型进行统一的处理&#xff0c;返回统一的数据结构。在spring中提供了这样全局统一处理异常的注解Controll…...

【安全测试】Web应用安全之XSS跨站脚本攻击漏洞

目录 前言 XSS概念及分类 反射型XSS(非持久性XSS) 存储型XSS(持久型XSS) 如何测试XSS漏洞 方法一&#xff1a; 方法二&#xff1a; XSS漏洞修复 原则&#xff1a;不相信客户输入的数据 处理建议 资料获取方法 前言 以前都只是在各类文档中见到过XSS&#xff0c;也进…...

LeNet卷积神经网络-笔记

LeNet卷积神经网络-笔记 手写分析LeNet网三卷积运算和两池化加两全连接层计算分析 修正上图中H,W的计算公式为下面格式 基于paddle飞桨框架构建测试代码 #输出结果为&#xff1a; #[validation] accuracy/loss: 0.9530/0.1516 #这里准确率为95.3% #通过运行结果可以看出&am…...

使用XMLHttpRequest实现文件异步下载

1、问题描述 我想通过异步的方式实现下载文化&#xff0c;请求为post请求。一开始我打算用ajax。 $.ajax({type:post,contentType:application/json,url:http://xxx/downloadExcel,data:{data:JSON.stringify(<%oJsonResponse.JSONoutput()%>)},}).success(function(dat…...

Lombok 的安装与使用

文章目录 一、什么是 Lombok1.1 Lombok 的概念1.2 为什么使用 Lombok1.3 Lombok 的相关注解 二、Lombok 的安装2.1 引入依赖2.2 安装插件 三、Lombok 的使用案例四、Lombok 的原理 一、什么是 Lombok 1.1 Lombok 的概念 Lombok&#xff08;“Project Lombok”&#xff09;是一…...

springBean生命周期解析

本文基于Spring5.3.7 参考&#xff1a; kykangyuky Spring中bean的生命周期 阿斌Java之路 SpringBean的生命周期&#xff0c; 杨开振 JavaEE互联网轻量级框架整合开发 黑马程序员 JavaEE企业级应用开发教程 马士兵 Spring源码讲解 一. SpringBean生命周期流程图 二. 示例代码 …...

人工智能轨道交通行业周刊-第54期(2023.7.31-8.6)

本期关键词&#xff1a;BIM智能运维、铁水联运、编组站美容、鸿蒙4.0、LK-99完全悬浮 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…...

Docker Compose 使用方法

目录 前言 安装 Docker Compose Ubuntu 安装与更新 Red Hat 安装与更新 验证是否安装 Docker Compose 创建 docker-compose.yml 文件 创建一个MySQL 与 tomcat 示例 使用Docker Compose启动服务 前言 Docker Compose 是一个工具&#xff0c;旨在帮助定义和 共享多容器…...

HTML 初

前言 HTML的基本骨架 HTML基本骨架是构建网页的最基本的结果。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…...

IPv6地址分类,EUI-64转换规则

1、可聚合的单全球单播地址Global Unique Address&#xff1a; Aggregate global unicast address&#xff0c;前3位是001&#xff0c;即2000::/3&#xff0c;目前IANA已经将一部分可聚合全球单播进行了专门使用&#xff0c;如&#xff1a;2001::/16用于IPV6互联网&#xff0c;…...

Nginx安装部署

什么是Nginx? Nginx&#xff08;发音同engine x&#xff09;是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代 理服务器以及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。 Nginx 因具有高并发&#xff08;特別是静态资源&#xff09;、 占用系统资…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...