当前位置: 首页 > 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;、 占用系统资…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...