C++开发的插件系统:为许多“源插件”制作插件系统 我正在研究一个由许多插件组成的C++库,它们可以相互独立地包含在一起。插件集仅取决于编译时的用户需求。 这些插件只是源代码,不是独立的二进制文件。 为此,库的main和惟一的CMakeLists.txt有一个预定义的插件列表,在插件目录中找到的每个插件都被添加到二进制目标中。 此外,还设置了一个使用插件名称定义的预处理器: set (plugins plugin1 plugin2 plugin3 ...) #optional plugins foreach(library ${plugins}) file(TO_CMAKE_PATH "plugins/${library}" librarypath) get_filename_component(librarypath ${librarypath} ABSOLUTE) if(EXISTS ${librarypath}) message("--> found ${library}") include_directories(${librarypath}/include) file(GLOB libsources "${librarypath}/src/*.cpp" "${librarypath}/src/*.f90") set(sources ${sources} ${libsources}) string(TOUPPER ${library} LIBRARY) add_definitions(-D${LIBRARY}) endif() endforeach(library)

C++开发的插件系统:为许多“源插件”制作插件系统 我正在研究一个由许多插件组成的C++库,它们可以相互独立地包含在一起。插件集仅取决于编译时的用户需求。 这些插件只是源代码,不是独立的二进制文件。 为此,库的main和惟一的CMakeLists.txt有一个预定义的插件列表,在插件目录中找到的每个插件都被添加到二进制目标中。 此外,还设置了一个使用插件名称定义的预处理器: set (plugins plugin1 plugin2 plugin3 ...) #optional plugins foreach(library ${plugins}) file(TO_CMAKE_PATH "plugins/${library}" librarypath) get_filename_component(librarypath ${librarypath} ABSOLUTE) if(EXISTS ${librarypath}) message("--> found ${library}") include_directories(${librarypath}/include) file(GLOB libsources "${librarypath}/src/*.cpp" "${librarypath}/src/*.f90") set(sources ${sources} ${libsources}) string(TOUPPER ${library} LIBRARY) add_definitions(-D${LIBRARY}) endif() endforeach(library),c++,plugins,cmake,C++,Plugins,Cmake,现在在我的主库中,我主要做以下工作: #ifdef PLUGIN1 # include "plugin1.h" #endif #ifdef PLUGIN2 # include "plugin2.h" #endif #ifdef PLUGIN3 # include "plugin3.h" #endif ... // each plugin has a unique id: enum PluginID : int { Plugin1

现在在我的主库中,我主要做以下工作:

#ifdef PLUGIN1
#    include "plugin1.h"
#endif
#ifdef PLUGIN2
#    include "plugin2.h"
#endif
#ifdef PLUGIN3
#    include "plugin3.h"
#endif

...

// each plugin has a unique id:
enum PluginID : int {
    Plugin1                          = 1,
    Plugin2                          = 2,
    Plugin3                          = 3,
};

// the name of each plugin is associated with its ID, 

PluginID getPluginIDFromName( const std::string& PluginName )
{
    static std::map<std::string, PluginID> PluginIDMap = {
        {"PLUGIN1", Plugin1},
        {"PLUGIN2", Plugin2},
        {"PLUGIN3", Plugin3},
    };

    return PluginIDMap[PluginName];
}

// Load a plugin by its ID
PluginBaseClass* pluginFactory( PluginID  pluginID)
{
    switch ( pluginID ) {
        #ifdef PLUGIN1 
        case Plugin1: { return new class Plugin1();}
        #endif
       #ifdef PLUGIN2 
        case Plugin2: { return new class Plugin2();}
        #endif
       #ifdef PLUGIN3 
        case Plugin3: { return new class Plugin3();}
       #endif
}}
一切都按预期进行,但我觉得我所做的是滥用cmake和预处理器宏。有没有更好的方法来实现我的目标? 此外,为每个可能的插件手动更新地图和开关相当麻烦。 我的要求是用户不需要手动修改CMakeLists.txt。提前谢谢你

编辑:我想通过插件的ID或名称使其可用,因此有两个功能。此外,首选静态链接;我看不出动态加载的理由

从外部文件读取插件列表。阅读基本移动 设置插件 插件1 插件2 插件3

放入plugins.cmake文件,并使用

在主cmake文件中包含plugins.cmake

为了避免手动修改映射和开关的需要:如果可以假定最大插件数为64,则可以按以下方式使用位掩码:

        auto plugin_mask = PLUGINS;
auto id = 1;

while (plugin_mask)
{
    if (plugin_mask & 1)
    {
        // found plugin

        // add to map
        std::pair<std::string, PluginID> p;
        p.first = "PLUGIN" + std::to_string(id);
        p.second = static_cast<PluginID>(id++);
        PluginIDMap.insert(p);
    } plugin_mask >> 1;
}
从外部文件读取插件列表。阅读基本移动 设置插件 插件1 插件2 插件3

放入plugins.cmake文件,并使用

在主cmake文件中包含plugins.cmake

为了避免手动修改映射和开关的需要:如果可以假定最大插件数为64,则可以按以下方式使用位掩码:

        auto plugin_mask = PLUGINS;
auto id = 1;

while (plugin_mask)
{
    if (plugin_mask & 1)
    {
        // found plugin

        // add to map
        std::pair<std::string, PluginID> p;
        p.first = "PLUGIN" + std::to_string(id);
        p.second = static_cast<PluginID>(id++);
        PluginIDMap.insert(p);
    } plugin_mask >> 1;
}

您可以使用所谓的自注册,让编译器为您完成大部分工作,而不是手动创建从id到插件名称和工厂函数的映射

静态插件工厂 首先,我们需要一个工厂类,各个插件可以在其中注册自己。声明可能如下所示:

class PluginFactory
{
public:
  using PluginCreationFunctionT = PluginBaseClass(*)();

  PluginFactory() = delete;

  static bool Register(std::string_view plugin name,
                       PluginID id,
                       PluginCreationFunctionT creation_function);

  static PluginBaseClass* Create(std::string_view name);
  static PluginBaseClass* Create(PluginID id);

private:
  static std::map<std::string_view, PluginCreationFunctionT> s_CreationFunctionByName;
  static std::map<PluginID, PluginCreationFunctionT> s_CreationFunctionById;
};
或通过

PluginBaseClass* thePlugin1 = PluginFactory::Create(PluginID::Plugin1);
修改插件 插件本身现在需要修改,以便它们自己注册。从理论上讲,任何全局变量都可以,但为了避免不同插件之间的名称冲突,只需在每个插件类中添加一个静态数据成员即可

class Plugin1 : public PluginBaseClass {
public:
  ...

private:
  static bool s_IsRegistered;
};
然后将以下内容添加到插件的源文件中:

namespace {
  PluginBaseClass* create_plugin1()
  {
    return new Plugin1{};
  }
}

bool Plugin1::s_IsRegistered 
  = PluginFactory::Register("PLUGIN1", PluginID::Plugin1, create_plugin1);
简化CMake代码 既然编译器生成了映射,您就不再需要预处理器定义了。您的CMake代码现在需要做的就是添加正确的include目录和源代码。但这不需要是主CMake文件的一部分。相反,您可以将一个CMakeLists.txt放入每个插件文件夹,然后通过add_子目录或include将其包含到主CMakeLists文件中:

foreach(library ${plugins})
    if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
      message(STATUS "--> found ${library}"
      include(${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
    else()
      message(FATAL "Unknown plugin ${library} requested!")
    endif()
endforeach()
plugins/plugin1文件夹中plugin1的CMakeLists.txt只包含

include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
file(GLOB sources_plugin1 "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/*.f90")
list(APPEND sources ${sources_plugin1})
在这种情况下,它看起来可能没有多大的改进,但是现在拥有这些单独的CMakeLists.txt文件也允许有条件地添加依赖项


例如,假设Plugin2是唯一使用boost的插件。使用单独的CMakeLists.txt,您可以将查找和使用boost所需的所有内容添加到Plugin2的CMakeLists.txt中,而不会污染主CMakeLists.txt文件。

您可以使用所谓的自注册,让编译器执行大部分操作,而不是手动创建从id到插件名称和工厂函数的映射这是你的工作

静态插件工厂 首先,我们需要一个工厂类,各个插件可以在其中注册自己。声明可能如下所示:

class PluginFactory
{
public:
  using PluginCreationFunctionT = PluginBaseClass(*)();

  PluginFactory() = delete;

  static bool Register(std::string_view plugin name,
                       PluginID id,
                       PluginCreationFunctionT creation_function);

  static PluginBaseClass* Create(std::string_view name);
  static PluginBaseClass* Create(PluginID id);

private:
  static std::map<std::string_view, PluginCreationFunctionT> s_CreationFunctionByName;
  static std::map<PluginID, PluginCreationFunctionT> s_CreationFunctionById;
};
或通过

PluginBaseClass* thePlugin1 = PluginFactory::Create(PluginID::Plugin1);
修改插件 插件本身现在需要修改,以便它们自己注册。从理论上讲,任何全局变量都可以,但为了避免不同插件之间的名称冲突,只需在每个插件类中添加一个静态数据成员即可

class Plugin1 : public PluginBaseClass {
public:
  ...

private:
  static bool s_IsRegistered;
};
然后将以下内容添加到插件的源文件中:

namespace {
  PluginBaseClass* create_plugin1()
  {
    return new Plugin1{};
  }
}

bool Plugin1::s_IsRegistered 
  = PluginFactory::Register("PLUGIN1", PluginID::Plugin1, create_plugin1);
简化CMake代码 既然编译器生成了映射,您就不再需要预处理器定义了。您的CMake代码现在需要做的就是添加正确的include目录和源代码。但这不需要是主CMake文件的一部分。相反,您可以将一个CMakeLists.txt放入每个插件文件夹,然后通过add_子目录或include将其包含到主CMakeLists文件中:

foreach(library ${plugins})
    if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
      message(STATUS "--> found ${library}"
      include(${CMAKE_CURRENT_LIST_DIR}/plugins/${library}/CMakeLists.txt)
    else()
      message(FATAL "Unknown plugin ${library} requested!")
    endif()
endforeach()
plugins/plugin1文件夹中plugin1的CMakeLists.txt只包含

include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
file(GLOB sources_plugin1 "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/*.f90")
list(APPEND sources ${sources_plugin1})
在这种特殊情况下,它看起来可能没有多大的改进,但是 现在,拥有这些单独的CMakeLists.txt文件还允许有条件地添加依赖项


例如,假设Plugin2是唯一使用boost的插件。使用单独的CMakeLists.txt,您可以将查找和使用boost所需的所有内容添加到Plugin2的CMakeLists.txt中,而不会污染主CMakeLists.txt文件。

将插件系统作为动态模块实现。通过这种方式,你可以单独构建它们,例如,甚至作为一个完全独立的项目。谢谢,但我更喜欢静态链接;不需要动态加载。为什么不需要动态加载?例如,在Linux上,dlopen是加载插件的标准方式,否则,不要称它为插件,而是软件组件。事实是,并非所有的“插件”都是完全独立的。一些插件导入或重用其他插件的函数和类。这意味着至少必须导出/导入Windows下的符号等。将插件系统实现为动态模块。通过这种方式,你可以单独构建它们,例如,甚至作为一个完全独立的项目。谢谢,但我更喜欢静态链接;不需要动态加载。为什么不需要动态加载?例如,在Linux上,dlopen是加载插件的标准方式,否则,不要称它为插件,而是软件组件。事实是,并非所有的“插件”都是完全独立的。一些插件导入或重用其他插件的函数和类。这意味着至少在Windows下必须导出/导入符号,依此类推。是的,你也不需要假设插件的最大数量,你可以使用std::bitsetYup,你也不需要假设插件的最大数量,你可以使用std::bitset