You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏插件场景下,使用C++动态库定义多函数是否有更优方案?

问题描述

现有C++动态库插件代码可正常运行,但作为初学者,希望实现更通用的插件系统,想通过std::map<std::string, functionPtr>结构跟踪插件的可用函数,但由于各函数签名不同,不知道如何实现,询问是否有更优的实现方式。

原代码:

#include <cassert>
#include <iostream>
#include <dlfcn.h>

//
// How could I encapsulate these in a consistent way?
//

typedef bool        (*logfileOpen_type)(const std::string &newFileName);    // logfile_open
typedef void        (*logfileWrite_type)(const std::string &logText);       //logfile_write
typedef void        (*logfileClose_type)();                //logfile_close
typedef std::string (*logfileError_type)();                //logFile_getLastError
typedef bool        (*logfileActive_type)();               //logFile_enabled

int main()
{
    // Load a dynamic plugin.
    auto libHandle = dlopen("./libPluginLogfile.so", RTLD_LAZY);
    assert(libHandle != nullptr);

    if (libHandle != nullptr)
        printf("INFO: Plugin successfully loaded.\n");
    else
    {
        printf("ERROR: Plugin failed to load.\n");
        exit(-1);
    }

    // Get the address of the desired function
    auto openFunction = (logfileOpen_type) dlsym(libHandle, "logFile_open");
    if (openFunction == nullptr)
        printf("Unable to find function [ %s ] [ %s ]\n", "logFile_open", dlerror());

    auto writeFunction = (logfileWrite_type) dlsym(libHandle, "logFile_write");
    if (writeFunction == nullptr)
        printf("Unable to find function [ %s ] [ %s ]\n", "logFile_write", dlerror());

    auto closeFunction = (logfileClose_type) dlsym(libHandle, "logFile_close");
    if (closeFunction == nullptr)
        printf("Unable to find function [ %s ] [ %s ]\n", "logFile_close", dlerror());

    auto getErrorFunction = (logfileError_type) dlsym(libHandle, "logFile_getLastError");
    if (getErrorFunction == nullptr)
        printf("Unable to find function [ %s ] [ %s ]\n", "logFile_getLastError", dlerror());

    auto getEnabledFunction = (logfileActive_type) dlsym(libHandle, "logFile_enabled");
    if (getEnabledFunction == nullptr)
        printf("Unable to find function [ %s ] [ %s ]\n", "logFile_enabled", dlerror());

    openFunction("logfile.log");
    writeFunction("Writing to the logfile.");
    writeFunction(".. and a second line.");
    closeFunction();

    dlclose(libHandle);
    std::cout << "INFO: Plugin Unloaded." << std::endl;

    return 0;
}

优化方案一:用std::variant统一存储不同函数指针

C++17引入的std::variant是类型安全的联合体,可存储多种不同类型的值,刚好解决不同签名函数指针的统一存储问题。

实现代码

#include <cassert>
#include <iostream>
#include <dlfcn.h>
#include <map>
#include <variant>
#include <string>

// 定义所有函数指针类型别名
using LogfileOpenFn = bool(*)(const std::string&);
using LogfileWriteFn = void(*)(const std::string&);
using LogfileCloseFn = void(*)();
using LogfileErrorFn = std::string(*)();
using LogfileActiveFn = bool(*)();

// 用variant统一所有函数指针类型
using PluginFn = std::variant<
    LogfileOpenFn,
    LogfileWriteFn,
    LogfileCloseFn,
    LogfileErrorFn,
    LogfileActiveFn
>;

// 模板工具函数:加载指定类型的函数到map
template<typename FnType>
void load_function(void* lib_handle, std::map<std::string, PluginFn>& fn_map, const std::string& fn_name) {
    void* ptr = dlsym(lib_handle, fn_name.c_str());
    if (!ptr) {
        printf("Unable to find function [ %s ] [ %s ]\n", fn_name.c_str(), dlerror());
        return;
    }
    fn_map[fn_name] = reinterpret_cast<FnType>(ptr);
}

int main() {
    auto libHandle = dlopen("./libPluginLogfile.so", RTLD_LAZY);
    assert(libHandle != nullptr);

    if (libHandle) {
        printf("INFO: Plugin successfully loaded.\n");
    } else {
        printf("ERROR: Plugin failed to load.\n");
        exit(-1);
    }

    std::map<std::string, PluginFn> plugin_functions;

    // 批量加载函数
    load_function<LogfileOpenFn>(libHandle, plugin_functions, "logFile_open");
    load_function<LogfileWriteFn>(libHandle, plugin_functions, "logFile_write");
    load_function<LogfileCloseFn>(libHandle, plugin_functions, "logFile_close");
    load_function<LogfileErrorFn>(libHandle, plugin_functions, "logFile_getLastError");
    load_function<LogfileActiveFn>(libHandle, plugin_functions, "logFile_enabled");

    // 调用函数:通过std::get_if获取对应类型的指针
    if (auto open_fn = std::get_if<LogfileOpenFn>(&plugin_functions["logFile_open"])) {
        (*open_fn)("logfile.log");
    }
    if (auto write_fn = std::get_if<LogfileWriteFn>(&plugin_functions["logFile_write"])) {
        (*write_fn)("Writing to the logfile.");
        (*write_fn)(".. and a second line.");
    }
    if (auto close_fn = std::get_if<LogfileCloseFn>(&plugin_functions["logFile_close"])) {
        (*close_fn)();
    }

    dlclose(libHandle);
    std::cout << "INFO: Plugin Unloaded." << std::endl;

    return 0;
}

优化方案二:用抽象接口封装插件(面向对象风格)

如果插件的功能是一组相关操作(如日志的打开/写入/关闭),可以定义抽象基类,让插件导出创建接口实例的函数,主程序通过接口调用功能,无需单独管理每个函数指针。

主程序代码

#include <cassert>
#include <iostream>
#include <dlfcn.h>
#include <memory>
#include <string>

// 抽象日志插件接口
class ILogPlugin {
public:
    virtual ~ILogPlugin() = default;
    virtual bool open(const std::string& filename) = 0;
    virtual void write(const std::string& text) = 0;
    virtual void close() = 0;
    virtual std::string get_last_error() = 0;
    virtual bool is_active() = 0;
};

// 插件导出的实例创建函数类型
using CreateLogPluginFn = ILogPlugin*(*)();

int main() {
    auto libHandle = dlopen("./libPluginLogfile.so", RTLD_LAZY);
    assert(libHandle != nullptr);

    if (libHandle) {
        printf("INFO: Plugin successfully loaded.\n");
    } else {
        printf("ERROR: Plugin failed to load.\n");
        exit(-1);
    }

    // 获取创建插件实例的函数
    CreateLogPluginFn create_fn = reinterpret_cast<CreateLogPluginFn>(dlsym(libHandle, "create_log_plugin"));
    if (!create_fn) {
        printf("Unable to find function [ create_log_plugin ] [ %s ]\n", dlerror());
        dlclose(libHandle);
        exit(-1);
    }

    // 用智能指针自动管理插件实例生命周期
    std::unique_ptr<ILogPlugin> log_plugin(create_fn());

    // 通过接口调用功能
    log_plugin->open("logfile.log");
    log_plugin->write("Writing to the logfile.");
    log_plugin->write(".. and a second line.");
    log_plugin->close();

    dlclose(libHandle);
    std::cout << "INFO: Plugin Unloaded." << std::endl;

    return 0;
}

插件端实现示例(libPluginLogfile.so)

#include <string>
#include "log_plugin_interface.h" // 包含上述ILogPlugin定义

class LogfilePlugin : public ILogPlugin {
private:
    std::string filename_;
    bool is_active_ = false;
    std::string last_error_;

public:
    bool open(const std::string& filename) override {
        // 实际文件打开逻辑
        filename_ = filename;
        is_active_ = true;
        last_error_.clear();
        return true;
    }

    void write(const std::string& text) override {
        if (is_active_) {
            // 实际文件写入逻辑
            std::cout << "[LOG] " << text << std::endl;
        }
    }

    void close() override {
        is_active_ = false;
        // 实际文件关闭逻辑
    }

    std::string get_last_error() override {
        return last_error_;
    }

    bool is_active() override {
        return is_active_;
    }
};

// 导出创建函数(extern "C"避免C++名称修饰)
extern "C" ILogPlugin* create_log_plugin() {
    return new LogfilePlugin();
}

方案对比

  • 方案一:灵活性高,适合函数分散、无统一逻辑的插件场景,通过map直接管理单个函数,但调用时需明确函数类型。
  • 方案二:封装性强,符合面向对象设计,主程序无需关心插件内部实现,只需通过接口调用,适合功能模块化的插件系统。

内容的提问来源于stack exchange,提问作者Droid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:47:32