Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split functionalities of an application into plugins with Qt

Pretty much like the title says, I want to split some parts of my Qt application into plugins, so I can add new functionalities at runtime. Ideally, plugins would be compiled separately and put into a dedicated path for plugins; when the application launches, installed extensions are automatically loaded, or can be reloaded at the user request at any time.

I should mention that the objects I want to put into plugins are not QObjects, but if it can make the solution simpler it's acceptable that they inherit from QObject.

How can I do that? I want the simplest solution that's portable and doesn't require anything else than Qt (no external dependencies).

like image 744
Jarhmander Avatar asked Jul 09 '18 13:07

Jarhmander


2 Answers

Although I answer my own question, I'm more than interested to hear others'!

For a start, you need to have a common interface among your plugins. Here's an example:

class MyPlugin
{
public:
    virtual ~MyPlugin() {}  // Needs to be virtual. Important!

    // Put here your method(s)
    virtual void frobnicate() = 0;
};

Do not name your interface like this, though. If your plugins represent video codecs, name it "VideoCodec", for example. Some prefer to put an "I" before interfaces' name (e.g. IVideoCodec). Also, some people would tell you to have public methods calling protected virtuals, but that's not strictly necessary there.

Why an interface? That's because it's the only way the application can use plugins without knowing the classes themselves beforehand. This means that because the application doesn't know the classes, the plugin must allow creating the plugin component via a factory. In fact, the only required function to declare is a factory function that creates a fresh instance of the "plugin". This factory function could be declared as such:

extern "C" std::unique_ptr<MyPlugin> MyPlugin_new();

(You need extern "C", otherwise you'll get trouble with QLibrary because of C++ name mangling ― see below)

The factory function need not be without parameters, but the parameters must make sense for all types of plugins. This could be a hashtable or a file containing general configuration information, or even better, an interface for a configuration object, for instance.

Now the loading part. The easiest way is to use a QDirIterator initialized to the plugin directory, iterate through all files and try to load them. Something along the lines of...

void load_plugins_from_path(const QString &plugin_dir)
{
    QDirIterator it(plugin_dir, QDir::Files, QDir::Readable);

    while (it.hasNext()) {
        try_load_plugin(it.next());
    }
}

(it's written like it's a function, but it should be a method)

Do not try in any way to filter the files by extension or by using the QDir::Executable flag: this will needlessly reduce the portability of the program―each OSes have their own file extensions, and QDir::Executable only work on unices (probably because there's no exec bit on Windows). Here, the method load_plugins_from_path just loads plugins from one given path; the caller may invoke that method over the elements of a list containing all the paths to search for plugins, for example. try_load_plugin may be defined like this:

void try_load_plugin(const QString &filename)
{
    QLibrary lib(filename);

    auto factory = reinterpret_cast<decltype (MyPlugin_new) *>(lib.resolve("MyPlugin_new"));

    if (factory) {
        std::unique_ptr<MyPlugin> plugin(factory());

        // Do something with "plugin", e.g. store in a std::vector
    }
}

decltype is used on MyPlugin_new so we doesn't have to specify its type (std::unique_ptr<MyPlugin> (*)()) and using it with auto will save you the trouble of changing the code more than it needs to be, should you change the signature of MyPlugin_new.

This method just tries to load a file as a library (whether it's a valid library file or not!) and attempts to resolve the required function, returning nullptr if either we're not dealing with a valid library file or the requested symbol (our function) didn't exist. Note that because we do the search directly in a dynamic library, we must know the exact name of the entity in that library. Because C++ mangles names, and that mangling is dependent on the implementation, the only sensible thing is to use extern "C" functions. Don't worry though: extern "C" will only prevent overloading of that function, but otherwise all C++ can be used inside of that function. Also, even though the factory function is not inside any namespace, it won't collide with other factory functions in other libraries, because we use explicit linking; that way, we can have MyPlugin_new from plugin A and MyPlugin_new from plugin B, and they will live at separate addresses.

Finally, if your set of plugins is too diverse to be expressed by one interface, one solution is to simply define (possibly) multiple factories inside of your plugins, each returning a pointer to a different kind of interface.

like image 104
Jarhmander Avatar answered Oct 18 '22 16:10

Jarhmander


Qt already has a class called QPluginLoader that does what you're trying to achieve.

like image 3
simurg Avatar answered Oct 18 '22 15:10

simurg