本文介绍了如何通过 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. dlopen
和 dlsym
-
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
- plugin.hpp
// plugin.hpp
#ifndef PLUGIN_HPP
#define PLUGIN_HPPclass PluginBase {
public:virtual void run() = 0;virtual ~PluginBase() {}
};#endif
- 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();
}
- 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();
}
- 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;
}
- 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