当前位置: 首页 > 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

相关文章:

  • 2025年渗透测试面试题总结-拷打题库13(题目+回答)
  • 【redis】主从复制
  • 程序员学英文之Shipment Claim 运输和索赔
  • Node.js学习
  • Vite/Rollup 模块热更新
  • Python内置函数---bytes()
  • MySQL基础增删改
  • CDN加速http请求
  • 百万点数组下memset、memcpy与for循环效率对比及原理分析
  • 【大模型与AIGC深度解析】从核心概念到行业应用
  • Python实现孔填充与坐标转换
  • 网络编程——通信三要素
  • GitLab_密钥生成(SSH-key)
  • 第4天:Linux开发环境搭建
  • 【JavaScript】详讲运算符--算术运算符
  • 时间自动填写——电子表格公式的遗憾(DeepSeek)
  • 品融电商:领航食品类目全域代运营,打造品牌增长新引擎
  • EasySearch 服务昨天还好好的,为什么今天突然访问不了了?
  • Java面试题汇总
  • FI固定资产折旧码的功能用途及其配置介绍
  • 我国成年国民综合阅读率82.1%,数字化阅读接触率首超80%
  • 新东方:2025财年前三季度净利增29%,第四财季海外业务将承压
  • 消费补贴政策力度最大的一届!第六届上海“五五购物节” 4月底启幕
  • 看正背面月壤、听火星上的声音,记者探营“中国航天日”科普展
  • 瞭望:高校大门要向公众打开,不能让“一关了之”成为常态
  • 讲武谈兵|英国公布六代机最新渲染图,但研发面临多重难题