【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_HPP

class 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值