【C/C++】插件机制:基于工厂函数的动态插件加载

本文介绍了如何通过 C++ 的 工厂函数动态库(.so 文件)和 dlopen / dlsym 实现插件机制。这个机制允许程序在运行时动态加载和调用插件,而无需在编译时知道插件的具体类型。


一、 动态插件机制

在现代 C++ 中,插件机制广泛应用于需要扩展或灵活配置的场景,如:

  • 策略模式:根据需求动态选择不同策略。

  • 插件化系统:如游戏引擎、服务器系统等,通过动态加载不同功能模块。

通过使用 动态库(.so 文件)和 工厂函数,可以实现插件的动态创建和管理。


二、插件机制核心流程

1. 创建插件接口(基类)

定义一个基类 PluginBase,所有插件都继承自它,实现自己的功能。

// plugin.hpp
class PluginBase {
public:virtual void run() = 0;  // 纯虚函数virtual ~PluginBase() {}
};

2. 实现具体插件

每个插件类实现 PluginBase 接口,并提供自己的功能。

#include "plugin.hpp"class PluginA : public PluginBase {
public:void run() override {std::cout << "PluginA is running!" << std::endl;}
};// 工厂函数
extern "C" PluginBase* create_plugin_0() {return new PluginA();
}

编译命令:

g++ -fPIC -shared plugin_0.cpp plugin_1.cpp -o libplugin.so

将plugin_*.cpp编译为动态库:libplugin.so

3. 主程序加载插件

主程序通过 dlopen 加载动态库,通过 dlsym 查找工厂函数,调用工厂函数创建插件对象,并执行其方法。

// main.cpp
#include <iostream>
#include <dlfcn.h>
#include "plugin.hpp"int main() {void* handle = dlopen("./libplugin.so", RTLD_LAZY);  // 打开动态库if (!handle) {std::cerr << "❌ Failed to open plugin: " << dlerror() << std::endl;return -1;}typedef PluginBase* (*CreateFunc)();  // 定义工厂函数指针类型// 查找两个插件的工厂函数CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");if (!create_plugin_0) {std::cerr << "❌ Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}CreateFunc create_plugin_1 = (CreateFunc)dlsym(handle, "create_plugin_1");if (!create_plugin_1) {std::cerr << "❌ Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}// 调用工厂函数创建插件对象PluginBase* plugin_0 = create_plugin_0();plugin_0->run();  // 执行插件功能PluginBase* plugin_1 = create_plugin_1();plugin_1->run();  // 执行插件功能// 释放资源delete plugin_0;delete plugin_1;dlclose(handle);  // 关闭动态库return 0;
}

编译命令:

g++ main.cpp -o main -ldl

链接libdl.so动态库,dlopen、dlsym 这类函数属于 Linux 系统的动态链接库(libdl)


三、核心概念解析

1. typedef 和 函数指针

通过 typedef 为函数指针起别名,使得函数指针的声明更加简洁易读。

typedef PluginBase* (*CreateFunc)();
  • CreateFunc 现在是指向无参、返回 PluginBase* 的函数指针类型。

  • 它允许我们用简单的名字表示工厂函数类型。

2. dlopendlsym

  • dlopen:打开动态库并返回一个句柄,程序可以通过该句柄加载库中的函数。

  • dlsym:根据符号名称在动态库中查找对应的函数地址。

这些函数属于 POSIX 标准,提供了 运行时加载和调用动态库的能力

3. 工厂函数

工厂函数是动态库中暴露给主程序的接口,负责创建插件对象实例。通过 extern "C" 来确保该函数不进行 C++ 名字修饰,从而避免不同编译器或链接时产生不同的符号名称。

extern "C" PluginBase* create_plugin_0() {return new PluginA();
}

5. dlsym 返回值和强制类型转换

CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");
  • dlsym 返回 void* 类型,表示它是一个通用的指针。void* 是一个 不带类型信息的指针,它可以指向任何类型的对象或函数。

  • 通过 强制转换 (CreateFunc),将 void* 转换为我们预定义的 函数指针类型 CreateFunc
    这样,通过 create_plugin_0() 等工厂函数返回的插件对象指针,可以调用其定义的 run 等方法。


四、 完整的工作流程

步骤描述
1. 插件开发定义基类接口 PluginBase,实现具体插件类,编写工厂函数。
2. 编译插件使用 g++ 编译源文件为动态库 .so 文件。
3. 主程序主程序使用 dlopen 加载动态库,dlsym 查找工厂函数,创建插件对象。
4. 执行功能通过插件对象调用具体功能(如 run())。
5. 释放资源删除插件对象,关闭动态库。

五、完整demo

  1. plugin.hpp
// plugin.hpp
#ifndef PLUGIN_HPP
#define PLUGIN_HPPclass PluginBase {
public:virtual void run() = 0;virtual ~PluginBase() {}
};#endif
  1. plugin_0.cpp
// plugin.cpp
#include <iostream>
#include "plugin.hpp"// 派生类
class MyPlugin_0 : public PluginBase {
public:void run() override {std::cout << "🚀 MyPlugin_0 is running!" << std::endl;}
};// 工厂函数,必须用 C 接口导出,防止 C++ 名字修饰
extern "C" PluginBase* create_plugin_0() {return new MyPlugin_0();
}
  1. plugin_1.cpp
// plugin_1.cpp
#include <iostream>
#include "plugin.hpp"// 派生类
class MyPlugin_1 : public PluginBase {
public:void run() override {std::cout << "🚀 MyPlugin_1 is running!" << std::endl;}
};// 工厂函数,必须用 C 接口导出,防止 C++ 名字修饰
extern "C" PluginBase* create_plugin_1() {return new MyPlugin_1();
}
  1. main.cpp
// main.cpp
#include <iostream>
#include <dlfcn.h>
#include "plugin.hpp"int main() {// 1. 打开动态库void* handle = dlopen("./libplugin.so", RTLD_LAZY);if (!handle) {std::cerr << "❌ Failed to open plugin: " << dlerror() << std::endl;return -1;}// 2. 查找工厂函数,先拿到目标函数指针,再基于这个指针创建对象。typedef PluginBase* (*CreateFunc)();  /*  函数指针语法结构:返回类型 (*指针名)(参数列表);typedef 原类型 新类型名;CreateFunc定义了一个指向“PluginBase* (*CreateFunc)()”此类函数的函数指针的别名*/CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");// dlsym(handle, "create_plugin_0")返回的是一个 void* 也就是一个空句柄,将这个句柄强制转换为CreateFunc// create_plugin_0即为目标函数(工厂函数)句柄,通过create_plugin_0()即可完成调用。if (!create_plugin_0) {std::cerr << "❌ Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}CreateFunc create_plugin_1 = (CreateFunc)dlsym(handle, "create_plugin_1");if (!create_plugin_1) {std::cerr << "❌ Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}// 3. 创建插件对象并调用PluginBase* plugin_0 = create_plugin_0(); // 将函数指针PluginBase*指向具体的create_plugin_0()plugin_0->run();PluginBase* plugin_1 = create_plugin_1();plugin_1->run();// 4. 释放资源delete plugin_0;delete plugin_1;dlclose(handle);return 0;
}
  1. build.sh
g++ -fPIC -shared plugin_0.cpp plugin_1.cpp -o libplugin.so # 将plugin_*.cpp编译为动态库:libplugin.so 
g++ main.cpp -o main -ldl # 链接libdl.so动态库,dlopen、dlsym 这类函数属于 Linux 系统的动态链接库(libdl)
echo "Build complete!"# 执行:
# 赋予脚本文件执行权限:chmod +x build.sh 
# 执行编译脚本./build.sh 
# 运行 ./main

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80216.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【音视频】AAC-ADTS分析

AAC-ADTS 格式分析 AAC⾳频格式&#xff1a;Advanced Audio Coding(⾼级⾳频解码)&#xff0c;是⼀种由MPEG-4标准定义的有损⾳频压缩格式&#xff0c;由Fraunhofer发展&#xff0c;Dolby, Sony和AT&T是主 要的贡献者。 ADIF&#xff1a;Audio Data Interchange Format ⾳…

机器学习 Day12 集成学习简单介绍

1.集成学习概述 1.1. 什么是集成学习 集成学习是一种通过组合多个模型来提高预测性能的机器学习方法。它类似于&#xff1a; 超级个体 vs 弱者联盟 单个复杂模型(如9次多项式函数)可能能力过强但容易过拟合 组合多个简单模型(如一堆1次函数)可以增强能力而不易过拟合 集成…

通过爬虫方式实现头条号发布视频(2025年4月)

1、将真实的cookie贴到代码目录中toutiaohao_cookie.txt文件里,修改python代码里的user_agent和video_path, cover_path等变量的值,最后运行python脚本即可; 2、运行之前根据import提示安装一些常见依赖,比如requests等; 3、2025年4月份最新版; 代码如下: import js…

Linux ssh免密登陆设置

使用 ssh-copy-id 命令来设置 SSH 免密登录&#xff0c;并确保所有相关文件和目录权限正确设置&#xff0c;可以按照以下步骤进行&#xff1a; 步骤 1&#xff1a;在源服务器&#xff08;198.120.1.109&#xff09;生成 SSH 密钥对 如果还没有生成 SSH 密钥对&#xff0c;首先…

《让机器人读懂你的心:情感分析技术融合奥秘》

机器人早已不再局限于执行简单机械的任务&#xff0c;人们期望它们能像人类伙伴一样&#xff0c;理解我们的喜怒哀乐&#xff0c;实现更自然、温暖的互动。情感分析技术&#xff0c;正是赋予机器人这种“理解人类情绪”能力的关键钥匙&#xff0c;它的融入将彻底革新机器人与人…

Linux笔记---进程间通信:匿名管道

1. 管道通信 1.1 管道的概念与分类 管道&#xff08;Pipe&#xff09; 是进程间通信&#xff08;IPC&#xff09;的一种基础机制&#xff0c;主要用于在具有亲缘关系的进程&#xff08;如父子进程、兄弟进程&#xff09;之间传递数据&#xff0c;其核心特性是通过内核缓冲区实…

Ollama API 应用指南

1. 基础信息 默认地址: http://localhost:11434/api数据格式: application/json支持方法: POST&#xff08;主要&#xff09;、GET&#xff08;部分接口&#xff09; 2. 模型管理 API (1) 列出本地模型 端点: GET /api/tags功能: 获取已下载的模型列表。示例:curl http://lo…

【OSCP-vulnhub】Raven-2

目录 端口扫描 本地/etc/hosts文件解析 目录扫描&#xff1a; 第一个flag 利用msf下载exp flag2 flag3 Mysql登录 查看mysql的运行权限 MySql提权&#xff1a;UDF 查看数据库写入条件 查看插件目录 查看是否可以远程登录 gcc编译.o文件 创建so文件 创建临时监听…

Podman Desktop:现代轻量容器管理利器(Podman与Docker)

前言 什么是 Podman Desktop&#xff1f; Podman Desktop 是基于 Podman CLI 的图形化开源容器管理工具&#xff0c;运行在 Windows&#xff08;或 macOS&#xff09;上&#xff0c;默认集成 Fedora Linux&#xff08;WSL 2 环境&#xff09;。它提供与 Docker 类似的使用体验…

极狐GitLab 权限和角色如何设置?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 权限和角色 (BASIC ALL) 将用户添加到项目或群组时&#xff0c;您可以为他们分配角色。该角色决定他们在极狐GitLab 中可以执…

解锁现代生活健康密码,开启养生新方式

在科技飞速发展的当下&#xff0c;我们享受着便捷生活&#xff0c;却也面临诸多健康隐患。想要维持良好状态&#xff0c;不妨从这些细节入手&#xff0c;解锁科学养生之道。​ 肠道是人体重要的消化器官&#xff0c;也是最大的免疫器官&#xff0c;养护肠道至关重要。日常可多…

Kafka 主题设计与数据接入机制

一、前言&#xff1a;万物皆流&#xff0c;Kafka 是入口 在构建实时数仓时&#xff0c;Kafka 既是 数据流动的起点&#xff0c;也是后续流处理系统&#xff08;如 Flink&#xff09;赖以为生的数据源。 但“消息进来了” ≠ “你就能处理好了”——不合理的 Topic 设计、接入方…

【绘制图像轮廓|凸包特征检测】图像处理(OpenCV) -part7

15 绘制图像轮廓 15.1 什么是轮廓 轮廓是一系列相连的点组成的曲线&#xff0c;代表了物体的基本外形。相对于边缘&#xff0c;轮廓是连续的&#xff0c;边缘不一定连续&#xff0c;如下图所示。轮廓是一个闭合的、封闭的形状。 轮廓的作用&#xff1a; 形状分析 目标识别 …

uniapp中使用<cover-view>标签

文章背景&#xff1a; uniapp中遇到了原生组件(canvas)优先级过高覆盖vant组件 解决办法&#xff1a; 使用<cover-view>标签 踩坑&#xff1a; 我想实现的是一个vant组件库中动作面板的效果&#xff0c;能够从底部弹出框&#xff0c;让用户进行选择&#xff0c;我直…

Kafka常见问题及解决方案

Kafka 是一个强大的分布式流处理平台&#xff0c;广泛用于高吞吐量的数据流处理&#xff0c;但在实际使用过程中&#xff0c;也会遇到一些常见问题。以下是一些常见的 Kafka 问题及其对应的解决办法的详细解答&#xff1a; 消息丢失 一、原因 1.生产端 网络故障、生产者超时…

leetcode 二分查找应用

34. Find First and Last Position of Element in Sorted Array 代码&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int target) {int low lowwer_bound(nums,target);int high upper_bound(nums,target);if(low high…

【Docker】在容器中使用 NVIDIA GPU

解决容器 GPU 设备映射问题&#xff0c;实现 AI 应用加速 &#x1f517; 官方文档&#xff1a;NVIDIA Container Toolkit GitHub 常见错误排查 若在运行测试容器时遇到以下错误&#xff1a; docker: Error response from daemon: could not select device driver ""…

通过Quartus II实现Nios II编程

目录 一、认识Nios II二、使用Quartus II 18.0Lite搭建Nios II硬件部分三、软件部分四、运行项目 一、认识Nios II Nios II软核处理器简介 Nios II是Altera公司推出的一款32位RISC嵌入式处理器&#xff0c;专门设计用于在FPGA上运行。作为软核处理器&#xff0c;Nios II可以通…

JAVA设计模式——(三)桥接模式

JAVA设计模式——&#xff08;三&#xff09;桥接模式&#xff08;Bridge Pattern&#xff09; 介绍理解实现武器抽象类武器实现类涂装颜色的行为接口具体颜色的行为实现让行为影响武器修改武器抽象类修改实现类 测试 适用性 介绍 将抽象和实现解耦&#xff0c;使两者可以独立…

k8s 证书相关问题

1.重新生成新证书 kubeadm init phase certs apiserver-etcd-client --config ~/kubeadm.yaml这个命令表示生成 kube-apiserver 连接 etcd 使用的证书,生成后如下 -rw------- 1 root root 1.7K Apr 23 16:35 apiserver-etcd-client.key -rw-r--r-- 1 root root 1.2K Apr 23 …