A small interface-driven toolkit for adding plugins to C++ applications.
The following code snippet shows how to use the PluginFactory.
PluginFactory::PluginFactory<MyPluginInterface, MyAppInterface> factory("plugins/");
factory.load();
// store for instantiated plugin pointers,
// the ownership of the pointers resides with
// the shared library, not the parent process.
using MyPlugin = MyPluginInterface*;
std::deque<MyPlugin> plugins;
// list all available plugins
auto availablePlugins = factory.availablePlugins();
// instantiate all available plugins
for(const auto& pluginName : availablePlugins)
{
plugins.push_back(factory.instance(pluginName));
}
// invoke MyPluginInterface::foo() on all loaded plugins
for(auto& plugin : plugins)
{
plugin->foo();
}
PluginFactory plugins are concrete implementatinos of a C++ abstract interface. Interactions with the plugin from the main program happen through this interface. Let's define a simple plugin interface to use with our example plugin.
namespace plugin_example {
class PluginInterface
{
public:
virtual ~PluginInterface(){}
virtual void do_stuff() = 0;
};
}
When creating concrete plugins, we can provide an interface to them so they can affect change in the main process. This interface is refered to as the PluginServiceInterface
.
namespace plugin_example {
class PluginService
{
public:
virtual ~PluginService(){}
virtual pluginCalled(char const * const name) = 0;
};
}
Providing this interface is not required, the default is a null interface that does nothing. Whether or not the service interface is desired, because plugins reside in shared libraries, we have to take the service interface as a void*
in the constructor.
Let's implement a concrete plugin implementing the plugin_example::PluginInterface example.
#include <PluginFactory/MakePluginMethods.hpp>
namespace MyPlugin {
class MyPlugin : public plugin_example::PluginInterface
{
public:
MyPlugin(void* pluginService)
: pluginService_(static_cast<plugin_example::PluginService*>(pluginService))
{
}
void do_stuff() override
{
std::cout << "I'm a plugin!!" << std::endl;
pluginService_->pluginCalled("MyPlugin");
}
private:
plugin_example::PluginService* pluginService_;
};
}
// use a helper macro to define required creation methods in our shared
// library.
//
// NOTE: we expect this to reside in the global namespace. Failure
// to put this in the global namespace will mean your plugin won't
// be able to be instanced correctly. This macro creates a function
// called createPlugin().
//
// NOTE: We don't have to worry about namespace pollution on POSIX
// systems as we're loading library symbols using RTLD_LOCAL. Every
// plugin loaded can contain the same symbol name as every other
// plugin, or can contain symbols found in the main process.
// Using RTLD_LOCAL shields us from many problems faced when using
// the default RTLD_GLOBAL.
MAKE_PLUGIN_METHODS(plugin_example::PluginInterface, MyPlugin::MyPlugin)
Then somewhere in the main process, we would use the PluginFactory to load the plugin and interact with it. In this case, the plugin factory declaration would look like PluginFactory::PluginFactory<plugin_example::MyPlugin, plugin_example::PluginService>
.
See PluginFactory/tests/acceptance_test/load_plugin for example code.
On POSIX systems, you'll most likely have to supply linker arguments to get the symbols in your shared library exported. (e.g. GCC on Linux and clang on Mac support the --export-dynamic to export symbols.)
On Windows, you'll need to use a compiler intrinsic attribute to indicate which symbols you want exported.
__declspec(dllexport) MyPlugin* createPlugin(PluginServiceInterface&);
- c++11
- boost. Uses boost::filesystem::path.
- build_traits
Used for unit testing on all platforms:
- UnitTest++. Unit test framework.
Windows Platforms:
- Visual Leak Detector (VLD). Used on windows to detect memory leaks in unit tests)
Austin Gilbert ceretullis@gmail.com
4-Clause BSD license, see LICENSE.md for details. Other licensing available upon request.