...
 
Commits (2)
# 3.8 is the lowest version with C++ standard 17 switch
cmake_minimum_required(VERSION 3.8)
project(plugin_system)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# only valid for GCC / Clang
add_compile_options(-Wall -Wextra)
add_executable(main main.cpp plugin_manager.cpp)
target_link_libraries(main ${CMAKE_DL_LIBS} stdc++fs)
add_subdirectory(plugins)
# Plugin System Example
```
$ mkdir build && cd build
$ cmake -GNinja ..
$ ninja
$ ./main foo bar
Creation of first plugin.
Creation of second plugin.
Running the second plugin.
Running the first plugin.
Destruction of first plugin.
Destruction of second plugin.
```
#include <iostream>
#include <cstdlib>
#include "plugin.hpp"
#include "plugin_manager.hpp"
int main(int argc, char *argv[])
{
if (argc == 1) {
std::cerr << "usage: " << argv[0] << " <plugins>...\n";
return EXIT_FAILURE;
}
PluginManager pluginManager;
for (auto i = 1; i < argc; i++) {
pluginManager.loadPlugin(argv[i]);
}
pluginManager.runAll();
return EXIT_SUCCESS;
}
#ifndef _PLUGIN_HPP
#define _PLUGIN_HPP
#include <memory>
/// Interface for plugins.
class Plugin {
public:
Plugin(){};
virtual void run() = 0;
virtual ~Plugin(){};
};
/// Symbol of the plugin construtor function.
const auto PLUGIN_CONSTRUCTOR_SYMBOL = "create_plugin";
/// Type of the plugin constructor function.
using PLUGIN_CONSTRUCTOR = std::unique_ptr<Plugin> (*)();
#endif
#include "plugin_manager.hpp"
#include <experimental/filesystem>
#include <stdexcept>
namespace fs = std::experimental::filesystem;
static const fs::path PLUGIN_DIR = "./plugins";
void PluginManager::loadPlugin(const std::string &name)
{
if (m_loadedPlugins.find(name) != m_loadedPlugins.end())
return;
const auto filepath = PLUGIN_DIR / (std::string{"lib"} + name + ".so");
// As soon as a void* is acquired, we wrap it. For simplicity an
// std::runtime_error is used rather than dedicated exception types.
LibHandle lib{dlopen(filepath.c_str(), RTLD_NOW)};
if (!lib)
throw std::runtime_error(std::string{"dlopen: "} + dlerror());
// C++ casts are preferred to C-style casts. Depending on the kind of cast,
// we need to select the right one. For void* casts, reinterpret_cast is
// used.
const auto constructor = reinterpret_cast<PLUGIN_CONSTRUCTOR>(dlsym(lib.get(), PLUGIN_CONSTRUCTOR_SYMBOL));
if (!constructor)
throw std::runtime_error(std::string{"dlsym: "} + dlerror());
m_loadedPlugins[name] = constructor();
m_openedLibs[name] = std::move(lib);
}
void PluginManager::runAll() const
{
for (const auto &[name, plugin] : m_loadedPlugins) {
plugin->run();
}
}
#ifndef _PLUGIN_MANAGER_HPP
#define _PLUGIN_MANAGER_HPP
#include <map>
#include <memory>
#include <string>
#include "dlfcn.h"
#include "plugin.hpp"
/// The PluginManager manages all loaded Plugins.
class PluginManager {
public:
void loadPlugin(const std::string &name);
void runAll() const;
private:
/// This custom deleter allows us to wrap the result of dlopen with an
/// std::unique_ptr. The library handle is managed for us automatically.
struct DlCloser {
void operator()(void *handle) { dlclose(handle); }
};
using LibHandle = std::unique_ptr<void, DlCloser>;
// Order is important! m_loadedPlugins needs to be destroyed before
// m_openedLibs.
/// Associated library handle needs to be maintained until the Plugin is destroyed.
std::map<std::string, LibHandle> m_openedLibs;
std::map<std::string, std::unique_ptr<Plugin>> m_loadedPlugins;
};
#endif
include_directories(..)
foreach(plugin foo bar)
add_library(${plugin} SHARED ${plugin}/${plugin}.cpp)
endforeach(plugin)
#include "bar.hpp"
#include <iostream>
Bar::Bar()
{
std::cout << "Creation of second plugin.\n";
}
Bar::~Bar()
{
std::cout << "Destruction of second plugin.\n";
}
void Bar::run()
{
std::cout << "Running the second plugin.\n";
}
#ifndef _BAR_HPP
#define _BAR_HPP
#include "plugin.hpp"
class Bar : public Plugin {
public:
Bar();
~Bar() override;
void run() override;
};
extern "C" std::unique_ptr<Plugin> create_plugin()
{
return std::make_unique<Bar>();
}
#endif
#include "foo.hpp"
// Moved to cpp files, since the header file does not use std::cout.
#include <iostream>
Foo::Foo()
{
// std::endl also flushes the stream which is usually not intended. This can
// cause performance issues, a \n is sufficient here.
std::cout << "Creation of first plugin.\n";
}
Foo::~Foo()
{
std::cout << "Destruction of first plugin.\n";
}
void Foo::run()
{
std::cout << "Running the first plugin.\n";
}
#ifndef _FOO_HPP
#define _FOO_HPP
#include "plugin.hpp"
class Foo : public Plugin {
public:
Foo();
// Using override instead of virtual to indicate that Foo actually
// implements these member functions.
~Foo() override;
void run() override;
};
extern "C" std::unique_ptr<Plugin> create_plugin()
{
// This function originally instantiated Plugin rather than Foo. We need to
// instantiate a non-abstract (i.e. non-interface) class here. Same goes for
// Bar.
return std::make_unique<Foo>();
}
#endif