4步实现C++插件化编程,轻松实现功能定制与扩展

2024-09-27  本文已影响0人  开源519

4步实现C++插件化编程,轻松实现功能定制与扩展

[TOC]

引言

  在项目开发中,我们经常面临为适应不同市场或产品层级而需调整功能的需求。从软件工程的角度来看,这意味着使用同一套代码,通过配置来实现产品的功能差异化。实现这一目标的方法多种多样,本文将探讨如何通过 插件化编程 优雅地满足这一需求。

概述

插件化编程 是一种通过动态加载功能模块(即插件)来增强主程序功能的软件设计策略。通过制定标准化接口,确保插件与主程序之间的兼容性与独立性。此方法能显著提高软件的灵活性、可扩展性和易维护性,同时支持快速定制及对市场变化的迅速响应。

需求分析

  通过上述描述,可以将功能需求概括为:使用同一套代码基础,实现不同产品的功能差异化。

  从软件设计的角度来看,主要功能需求包括:

  1. 实现不同产品客制化配置
  1. 实现依据配置集成指定模块

设计方案

  基于上述分析,以下是设计方案的大致流程:

  1. 配置文件构建
  1. 依据配置加载指定模块

详细设计

主要是通过CMake配置化编译和插件化编程实现动态加载,详细实现如下:

  1. 配置文件 modules_configs.cmake
# 业务模块 Components/Business
set(MODULE_CONFIG_VERSION "DEFAULT_MCONFIG_1001")

set(BUSINESS_MODULES "")
list(APPEND BUSINESS_MODULES OneNetMqtt)
  1. 编译BUSINESS_MODULES指定模块
## Business

# 动态加载, 配置文件modules_configs.cmake
foreach(module IN LISTS BUSINESS_MODULES)
    message(STATUS "Add Business Module: ${module}")
    add_subdirectory(${module})
endforeach()
  1. 动态库入口实现
// The entry of OneNet business plugin
extern "C" void PluginEntry(std::map<int, SprObserver*>& observers, SprContext& ctx)
{
    auto pOneDrv = OneNetDriver::GetInstance(MODULE_ONENET_DRIVER, "OneDrv");
    auto pOneMgr = OneNetManager::GetInstance(MODULE_ONENET_MANAGER, "OneMgr");

    observers[MODULE_ONENET_DRIVER] = pOneDrv;
    observers[MODULE_ONENET_MANAGER] = pOneMgr;
    SPR_LOGD("Load plug-in OneNet modules\n");
}
  1. 主程序加载指定动态库
void SprSystem::LoadPlugins()
{
    std::string path = DEFAULT_PLUGIN_LIBRARY_PATH;
    if (access(DEFAULT_PLUGIN_LIBRARY_PATH, F_OK) == -1) {
        GetDefaultLibraryPath(path);
        SPR_LOGW("%s not exist, changed path %s\n", DEFAULT_PLUGIN_LIBRARY_PATH, path.c_str());
    }

    DIR* dir = opendir(path.c_str());
    if (dir == nullptr) {
        SPR_LOGE("Open %s fail! (%s)\n", path, strerror(errno));
        return;
    }

    // loop: find all plugins library files in path
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strncmp(entry->d_name, DEFAULT_PLUGIN_LIBRARY_FILE_PREFIX, strlen(DEFAULT_PLUGIN_LIBRARY_FILE_PREFIX)) != 0) {
            continue;
        }

        void* pDlHandler = dlopen(entry->d_name, RTLD_NOW);
        if (!pDlHandler) {
            SPR_LOGE("Load plugin %s fail! (%s)\n", entry->d_name, dlerror() ? dlerror() : "unknown error");
            continue;
        }

        auto pEntry = (PluginEntryFunc)dlsym(pDlHandler, DEFAULT_PLUGIN_LIBRARY_ENTRY_FUNC);
        if (!pEntry) {
            SPR_LOGE("Find %s fail in %s! (%s)\n", DEFAULT_PLUGIN_LIBRARY_ENTRY_FUNC, entry->d_name, dlerror() ? dlerror() : "unknown error");
            dlclose(pDlHandler);
            continue;
        }

        mPluginHandles.push_back(pDlHandler);
        mPluginEntries.push_back(pEntry);
        SPR_LOGD("Load plugin %s success!\n", entry->d_name);
    }

    closedir(dir);
}

void SprSystem::Init()
{
    ...
    LoadPlugins();  // load plugin libraries

    // excute plugin entry function
    SprContext ctx;
    for (auto& mPluginEntry : mPluginEntries) {
        mPluginEntry(mModules, ctx);
    }

    // excute plug module initialize function
    for (auto& module : mModules) {
        module.second->Initialize();
    }

    ...
}

验证

09-28 17:02:23.049 146938 SprSystem    D:  173 Load plugin libpluginonenet.so success!
09-28 17:02:23.052 146938 EntryOneNet  D:   41 Load plug-in OneNet modules 

日志上看,动态库已经加载成功,动态库入口日志正常打印,OneNetMqtt模块启动正常。

$ cat /tmp/sparrow_version
System Version : Sparrow 1.0.1
C++ Standard   : 11
G++ Version    : 11.4.0
Gcc Version    : 11.4.0
Running Env    : Default
Build Time     : 2024-09-28 16:50:58
Build Type     : Release
Build Host     : Beckett
Build Platform : Linux 5.15.153.1-microsoft-standard-WSL2
Module Config  : DEFAULT_MCONFIG_1001

系统环境中模块配置版本号为DEFAULT_MCONFIG_1001与配置文件中一致

总结

上一篇 下一篇

猜你喜欢

热点阅读