游戏插件场景下,使用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
相关产品推荐
相关产品推荐

