Plugins management in application plugin system

Started by
7 comments, last by Tribad 9 years, 10 months ago

Hi,

I am working on an application which uses plugin system. There are many different kinds of plugins: data readers, plot creators, computing kernels and so on. For simplicity let us assume that only two kinds are available. My question is how to manage those plugins in the sense of registering them. Should I use some flag (read by the app at register event) inside DLLs indicating if it is data reader plugin or plotting plugin? Or maybe I should place each kind of plugins in a different folder, etc. Of course plugin management system has different interfaces and managers for every kind. How do you handle such cases?

Regards,

Misery

Advertisement

I use different directories. Subdirs in lib.

But maybe it would be possible to use a plugin base class with an ID to identify the type of plugin-interface to expect.

I tend to turn this entire thing around for flexibility reasons. The problem I've had with plugins which attempt to expose data to the system is that the structure/flags/whatever you wish to expose ends up constantly changing as you add more features and it makes keeping all the plugins up to date difficult. Instead of doing this, I let the plugins do the work themselves. Basically I load up the plugin and call a register function in the plugin. Now it is up to the plugin to find factories to hook into, tell systems about themselves etc. In this way, if I add a new factory I don't need to go through all the existing plugins and update the registration structures, existing plugins simply continue to work since they don't know about the new thing or need to change how they register. When I add a plugin for the new item, it just "works" because it is querying the system for the new factory to plug into instead of the system querying the plugin to figure out where it should be hooked.

Just a quick outline of this. (Note that I use a custom variation of the COM pattern, it is not MS COM though, just the pattern.)


for (auto plugin : foundPlugins)
  plugin->Load(registry);
 
void Plugin::Load(Registry& registry) // plugins don't cause load failures, they just don't register and that is logged.
{
  m_SharedObject.Load (m_Filename);
  if (m_SharedObject.Loaded())
  {
    InstallFunction install = m_SharedObject.GetFunction<InstallFunction>("Install");
    if (install)
    {
       if ((install)(registry))
         return true;
    }
  }
  // TODO: Clean up everything from above on failure...
}
 
// From a plugin.
bool stdcall Install(Registry& registry)
{
  // I'm a file format plugin...
  iFileFormatFactory* factory = nullptr;
  if (registry.Create<iFileFormatFactory>(&factory)) // Note: the factory is probably a singleton internally, it deals with that behind the scenes.
  {
    if (factory->Install(MyPlugin::kId, &MyPlugin::Creator))
    {
      // Note: you can pass in descriptive flags above in a "real" system.
      factory->Release();
      return true;
    }
  }
  return false;
}

There are a whole slew of benefits to this solution. For instance, if this plugin absolutely needs another plugin to exist before it can load, it could soft fail and add itself to a deferred load (i.e. try again) list in the registry to be called again after other things get a chance to load. Or, if this was itself the factory mentioned, it could register the factory, then implement a subdirectory scan to load plugins for itself.

Overall, this pattern has served me very well and allows for very intricate plugin systems without the primary "Registry" getting all bloated with options and parsing systems.

It is one possible solution.

For my personal feeling I dont think it is a good thing that the plugin accesses information in the application. In your solution the factory class.

I would setup a factory class in the application and make the application responsible to load and register the plugin.

For a C++ solution I would define a generic Interface class that is used within the factory. And like your install function someone will need a create-interface function that creates a new Interface. There is not more needed to handle the plugin.

The difference is that the plugin accesses no data in the application. So its a question of what "feels" better.

I may come around later with some pictures.

I hope this is visible.

As you see I defined a configuration object that has no attributes at all. This way I can sub-class the application specific configuration data and pass a pointer to the Base-Class through the CreateIfc() function into the plugin.

I defined that an object-type has 32-bits that makes a lot different object to create from such a factory. Each plugin delivers a list of ids the plugin is responsible for and the factory class can easy ask the right factory for a new object.

An application can extend the interface into the plugin by sub-classing from CSimFactoryIfc. I think that is easy.

SimFactory.png

For my personal feeling I dont think it is a good thing that the plugin accesses information in the application. In your solution the factory class.


I'll have to back up AllEightUp. You only need to export a small handful of things to the DLL. You can even use a dependency-injection approach where the app calls the Register function in the DLL and passes in an abstract PluginInterface that contains the AddFooFactory/AddBarFactory/etc. methods. Just be sure to use consistent offsets in you class (don't ever change the order of virtual methods, just add new ones to the end) and old plugins will just keep on truckin'.

e.g.

class PluginInterface {
  PluginInterface(const PluginInterface&) = delete;
  PluginInterface& operator=(const PluginInterface&) = delete;

public:
  virtual bool RegisterLoader(ILoader* loader) = 0;
  virtual bool RegisterPlotter(IPlotter* plotter) = 0;
};
The other decent option is to have each plugin export a registration table that the app uses to figure out how to register the plugin. Old plugins keep working because you never change the format of this table; new plugins just put different data in it. e.g

struct PluginInfo {
  const char* name;
  const char* version;
  (void*)(*initialize)();
};

...

__declspec(dllexport) PluginInfo _plugin_info = {
  "my_plugin",
  "1.0-rc3",
  &CreateFooLoader
};
The later approach is not brittle, but it's incredibly inflexible. What if you want a single DLL to supply two different things? What if a DLL needs to change the thing it registers based on runtime choices? Use AllEightUp's advice.

Sean Middleditch – Game Systems Engineer – Join my team!

It is one possible solution.

For my personal feeling I dont think it is a good thing that the plugin accesses information in the application. In your solution the factory class.

I would setup a factory class in the application and make the application responsible to load and register the plugin.

For a C++ solution I would define a generic Interface class that is used within the factory. And like your install function someone will need a create-interface function that creates a new Interface. There is not more needed to handle the plugin.

The difference is that the plugin accesses no data in the application. So its a question of what "feels" better.

I may come around later with some pictures.

For the purpose of plugins, I find that letting them figure out what to do is generally the best approach since the application side classes are less likely to need to be changed for special requirements. This is a matter of opinion of course and everyone has preferences but I tend to think of it as an extension of SRP, the factory stays self contained and knows nothing about the plugins, their requirements etc. Each plugin remains self contained in terms that it knows it's requirements, how to install and instantiate itself and as such it tells others how to use the plugin contents.

Just an example of one of the cases where this style makes things considerably easier. Say you have a preference which selects OpenGL versus D3D rendering. If you make the factories responsible for figuring out the content of the plugins you need to either filter the plugins by name, check flags in the plugins or still rely on the plugin to fail to register based on the preference. In the case of the plugins doing the work, each plugin can simply asks for the prefs object and decides to install or not. The factory remains simple in this case. As you add more items to the system, say Bullet physics versus Havok, versus whatever, having the factories do the work gets more and more complicated while the plugin side can do the work exceptionally easily.

The other decent option is to have each plugin export a registration table that the app uses to figure out how to register the plugin. Old plugins keep working because you never change the format of this table; new plugins just put different data in it. e.g


struct PluginInfo {
  const char* name;
  const char* version;
  (void*)(*initialize)();
};

...

__declspec(dllexport) PluginInfo _plugin_info = {
  "my_plugin",
  "1.0-rc3",
  &CreateFooLoader
};
The later approach is not brittle, but it's incredibly inflexible. What if you want a single DLL to supply two different things? What if a DLL needs to change the thing it registers based on runtime choices? Use AllEightUp's advice.

This is exactly the style I was using several years ago which drove me to turn things around. Eventually I had to put in so many different flags, id's of dependencies and such that these structures where getting out of hand. And worse, I stopped wanting to change them for new features and exceptions because having to go through all the plugins to update a field became a serious chore. Reversing things as suggested prevented needing to touch 90% of the code just because one plugin had a unique requirement to it's loading.

I would not make the factory responsible for the decission whether a plugin is used or not. This should be done by the application.

So. Loading is part of the factory.

What to use is a part of the application. But anyways. Both will work.

This topic is closed to new replies.

Advertisement