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 QObject
s, 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).
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.
Qt already has a class called QPluginLoader that does what you're trying to achieve.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With