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

【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.xdnf.cn/news/100099.html

相关文章:

  • 【多线程】二、pthread库 线程控制 线程分离 __thread关键字 线程库封装
  • skynet.cluster 库函数应用
  • update方法
  • Kafka 保证多分区的全局顺序性的设计方案和具体实现
  • 接口访问数据库报错问题记录
  • Java多线程的暗号密码:5分钟掌握wait/notify
  • 大模型框架技术演进与全栈实践指南
  • 57、Spring Boot 最佳实践
  • 模板方法模式:定义算法骨架的设计模式
  • 图文结合 - 光伏系统产品设计PRD文档 -(慧哥)慧知开源充电桩平台
  • docker学习笔记5-docker中启动Mysql的最佳实践
  • SQL技术终极指南:从内核原理到超大规模应用
  • 4.23刷题记录(栈与队列专题)
  • devops自动化容器化部署
  • 【人工智能】解锁 AI 潜能:DeepSeek 大模型迁移学习与特定领域微调的实践
  • MCP 协议:AI 时代的 “USB-C” 革命——从接口统一到生态重构的技术哲学
  • 硬核解析:整车行驶阻力系数插值计算与滑行阻力分解方法论
  • vue项目打包后点击dist下面index.html(无法访问您的文件该文件可能已被移至别处、修改或删除。ERR_FILE_NOT_FOUND)比如若依
  • 金仓读写分离集群修改IP
  • 从性能到安全:大型网站系统架构演化的 13 个核心维度
  • Qt案例 使用QFtpServerLib开源库实现Qt软件搭建FTP服务器,使用QFTP模块访问FTP服务器
  • C语言中小写字母转大写字母
  • 数据通信学习笔记之OSPF的基础术语
  • 有哪些信誉良好的脂多糖供应商推荐?
  • 16.第二阶段x64游戏实战-分析二叉树结构
  • 前端js需要连接后端c#的wss服务
  • python自动化测试1——鼠标移动偏移与移动偏移时间
  • Redis 服务自动开启
  • Linux——进程优先级/切换/调度
  • Elasticsearch 堆内存使用情况和 JVM 垃圾回收