├── .gitignore ├── LICENSE ├── README.md ├── Sample ├── App │ ├── App.pro │ ├── iplugin.h │ ├── main.cpp │ ├── main.qml │ ├── pluginhelper.cpp │ ├── pluginhelper.h │ └── qml.qrc ├── Plugin1 │ ├── Plugin1.json │ ├── Plugin1.pro │ ├── plugin1.cpp │ └── plugin1.h └── Sample.pro ├── qpluginfactory.cpp ├── qpluginfactory.h └── qpluginfactory.pri /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QPluginFactory 2 | A factory class to easily load generic plugins 3 | 4 | ## Features 5 | - Load plugins based on their IID and static keys 6 | - Supports both dynamic and static plugins 7 | - Respects the QT_PLUGIN_PATH and other plugin-related settings 8 | - Supports factory-like generic plugin factory for such plugins 9 | 10 | ## Installation 11 | The package is provided via qdep, as `Skycoder42/QPluginFactory`. To use it simply: 12 | 13 | 1. Install and enable qdep (See [qdep - Installing](https://github.com/Skycoder42/qdep#installation)) 14 | 2. Add the following to your pro file: 15 | ```qmake 16 | QDEP_DEPENDS += Skycoder42/QPluginFactory 17 | !load(qdep):error("Failed to load qdep feature! Run 'qdep.py prfgen --qmake $$QMAKE_QMAKE' to create it.") 18 | ``` 19 | 20 | ## Example 21 | A full example with a demo plugin can be found in the `Sample` folder. The following code snippet illustrates how to use the factory: 22 | 23 | ### Interface 24 | First, assume we have a plugin definition as follows: 25 | 26 | ```cpp 27 | class MyClass; 28 | class IMyPlugin 29 | { 30 | public: 31 | virtual inline ~IMyPlugin() = default; 32 | 33 | virtual MyClass *createInstance(const QString &key) = 0; 34 | }; 35 | 36 | #define IMyPluginIID "de.skycoder42.qpluginloader.sample.IMyPlugin" 37 | Q_DECLARE_INTERFACE(IMyPlugin, IMyPluginIID) 38 | ``` 39 | 40 | ### Plugin 41 | Next, create a plugin that implements this interface. The important parts here is to make shure that: 42 | 43 | 1. The plugin extends `QObect` 44 | 2. The plugin implements your interface with a JSON-Manifest 45 | 3. You declare the interface via `Q_INTERFACES` 46 | 4. The JSON-Manifest contains an element called `Keys` that holds an array of keys that identify the plugin 47 | 48 | The plugin header could look like this: 49 | 50 | ```cpp 51 | class MyPluginImplementation : public QObject, public IMyPlugin 52 | { 53 | Q_OBJECT 54 | Q_INTERFACES(IMyPlugin) 55 | Q_PLUGIN_METADATA(IID IMyPluginIID FILE "MyPluginImplementation.json") 56 | 57 | public: 58 | //... 59 | }; 60 | ``` 61 | 62 | And the corresponding JSON-Manifest as follows. For the example, we want the plugin to be identified by the key `"stuff"`, but can can of course specify anything here, whatever fits your semantics best. Multiple keys are allowed as well. 63 | 64 | ```json 65 | { 66 | "Keys" : [ "stuff" ] 67 | } 68 | ``` 69 | 70 | ### Application 71 | We can use the factory to create instances of this plugin by simply creating a generic instance. There are prepared macros that do so by creating a `Q_GLOBAL_STATIC` instance of the factory. But you can of course also create the instance yourself. The following code creates a `QPluginFactory` instanced named `loader` that searches for plugins in a subfolder to the default Qt plugin folder (or a folder advertised via `QT_PLUGIN_PATH`) named `myPlugin`: 72 | ```cpp 73 | Q_GLOBAL_PLUGIN_FACTORY(IMyPlugin, "myPlugin", loader) 74 | 75 | // ... 76 | 77 | IMyPlugin *instance = loader->plugin("stuff"); 78 | ``` 79 | 80 | If you want to use the factory style instead, use the following: 81 | ```cpp 82 | Q_GLOBAL_PLUGIN_OBJECT_FACTORY(IMyPlugin, MyClass, "myPlugin", loader) 83 | 84 | // ... 85 | 86 | MyClass *instance = loader->createInstance("stuff"); 87 | ``` 88 | -------------------------------------------------------------------------------- /Sample/App/App.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | 3 | CONFIG += c++14 4 | 5 | HEADERS += \ 6 | pluginhelper.h \ 7 | iplugin.h 8 | 9 | SOURCES += main.cpp \ 10 | pluginhelper.cpp 11 | 12 | RESOURCES += qml.qrc 13 | 14 | ANDROID_EXTRA_PLUGINS = $$OUT_PWD/../plugins 15 | 16 | include(../../qpluginfactory.pri) 17 | !load(qdep):error("qdep required") 18 | -------------------------------------------------------------------------------- /Sample/App/iplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef IPLUGIN_H 2 | #define IPLUGIN_H 3 | 4 | #include 5 | #include 6 | 7 | class IPlugin 8 | { 9 | public: 10 | virtual inline ~IPlugin() = default; 11 | 12 | virtual QString printText() const = 0; 13 | 14 | public Q_SLOTS: 15 | virtual void ping(const QString &base) = 0; 16 | 17 | Q_SIGNALS: 18 | virtual void pong(const QString &result) = 0; 19 | }; 20 | 21 | #define IPluginIid "de.skycoder42.qpluginloader.sample.IPlugin" 22 | Q_DECLARE_INTERFACE(IPlugin, IPluginIid) 23 | 24 | #endif // IPLUGIN_H 25 | -------------------------------------------------------------------------------- /Sample/App/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pluginhelper.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 9 | qputenv("QT_PLUGIN_PATH", (QCoreApplication::applicationDirPath() + "../plugins").toUtf8()); 10 | 11 | QGuiApplication app(argc, argv); 12 | 13 | QQmlApplicationEngine engine; 14 | engine.rootContext()->setContextProperty("plugin", new PluginHelper(qApp)); 15 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 16 | if (engine.rootObjects().isEmpty()) 17 | return -1; 18 | 19 | return app.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /Sample/App/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Window 2.2 3 | import QtQuick.Controls 2.2 4 | import QtQuick.Layouts 1.3 5 | 6 | ApplicationWindow { 7 | visible: true 8 | width: 640 9 | height: 480 10 | title: qsTr("Plugin loader test") 11 | 12 | ListView { 13 | anchors.fill: parent 14 | model: plugin.keys 15 | 16 | delegate: ItemDelegate { 17 | width: parent.width 18 | text: modelData 19 | 20 | onPressAndHold: plugin.info(modelData) 21 | onClicked: plugin.load(modelData) 22 | } 23 | 24 | footer: GridLayout { 25 | width: parent.width 26 | columns: 2 27 | 28 | Label { 29 | text: "Title" 30 | Layout.fillWidth: true 31 | } 32 | 33 | TextField { 34 | text: plugin.title 35 | readOnly: true 36 | Layout.fillWidth: true 37 | } 38 | 39 | Label { 40 | text: "Ping" 41 | Layout.fillWidth: true 42 | } 43 | 44 | TextField { 45 | id: pingText 46 | placeholderText: "Enter a ping text" 47 | onTextChanged: plugin.ping(text) 48 | Layout.fillWidth: true 49 | 50 | Connections { 51 | target: plugin 52 | onUpdated: pingText.clear() 53 | } 54 | } 55 | 56 | Label { 57 | text: "Pong" 58 | Layout.fillWidth: true 59 | } 60 | 61 | TextField { 62 | id: pongText 63 | placeholderText: "wait for the pong" 64 | onTextChanged: plugin.ping(text) 65 | readOnly: true 66 | Layout.fillWidth: true 67 | 68 | Connections { 69 | target: plugin 70 | onPong: pongText.text = text 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sample/App/pluginhelper.cpp: -------------------------------------------------------------------------------- 1 | #include "pluginhelper.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Q_GLOBAL_PLUGIN_FACTORY(IPlugin, "plugin", loader) 9 | 10 | PluginHelper::PluginHelper(QObject *parent) : 11 | QObject(parent), 12 | _current(nullptr) 13 | {} 14 | 15 | QStringList PluginHelper::keys() const 16 | { 17 | return loader->allKeys(); 18 | } 19 | 20 | QString PluginHelper::title() const 21 | { 22 | return _current ? _current->printText() : QString(); 23 | } 24 | 25 | void PluginHelper::info(const QString &name) 26 | { 27 | qInfo() << loader->metaData(name); 28 | } 29 | 30 | void PluginHelper::load(const QString &name) 31 | { 32 | if(_current) 33 | dynamic_cast(_current)->disconnect(this); 34 | _current = loader->plugin(name); 35 | connect(dynamic_cast(_current), SIGNAL(pong(QString)), 36 | this, SIGNAL(pong(QString))); 37 | emit updated(); 38 | } 39 | 40 | void PluginHelper::ping(const QString &text) 41 | { 42 | if(_current) 43 | _current->ping(text); 44 | } 45 | -------------------------------------------------------------------------------- /Sample/App/pluginhelper.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGINHELPER_H 2 | #define PLUGINHELPER_H 3 | 4 | #include 5 | #include 6 | #include "iplugin.h" 7 | 8 | class PluginHelper : public QObject 9 | { 10 | Q_OBJECT 11 | 12 | Q_PROPERTY(QStringList keys READ keys CONSTANT) 13 | 14 | Q_PROPERTY(QString title READ title NOTIFY updated) 15 | 16 | public: 17 | explicit PluginHelper(QObject *parent = nullptr); 18 | 19 | QStringList keys() const; 20 | QString title() const; 21 | 22 | public slots: 23 | void info(const QString &name); 24 | void load(const QString &name); 25 | 26 | void ping(const QString &text); 27 | 28 | signals: 29 | void updated(); 30 | 31 | void pong(const QString &text); 32 | 33 | private: 34 | IPlugin *_current; 35 | }; 36 | 37 | #endif // PLUGINHELPER_H 38 | -------------------------------------------------------------------------------- /Sample/App/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/Plugin1/Plugin1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "plugin1", "pluginA" ] 3 | } 4 | -------------------------------------------------------------------------------- /Sample/Plugin1/Plugin1.pro: -------------------------------------------------------------------------------- 1 | QT += core 2 | 3 | TARGET = Plugin1 4 | DESTDIR = ../plugins/plugin/ 5 | 6 | TEMPLATE = lib 7 | CONFIG += plugin 8 | 9 | HEADERS += \ 10 | plugin1.h 11 | 12 | SOURCES += \ 13 | plugin1.cpp 14 | 15 | DISTFILES += Plugin1.json 16 | -------------------------------------------------------------------------------- /Sample/Plugin1/plugin1.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin1.h" 2 | 3 | Plugin1::Plugin1(QObject *parent) : 4 | QObject(parent), 5 | IPlugin() 6 | {} 7 | 8 | QString Plugin1::printText() const 9 | { 10 | return QStringLiteral("plugin 1"); 11 | } 12 | 13 | void Plugin1::ping(const QString &base) 14 | { 15 | emit pong(base.simplified()); 16 | } 17 | -------------------------------------------------------------------------------- /Sample/Plugin1/plugin1.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGIN1_H 2 | #define PLUGIN1_H 3 | 4 | #include "../App/iplugin.h" 5 | 6 | class Plugin1 : public QObject, public IPlugin 7 | { 8 | Q_OBJECT 9 | Q_INTERFACES(IPlugin) 10 | Q_PLUGIN_METADATA(IID IPluginIid FILE "Plugin1.json") 11 | 12 | public: 13 | explicit Plugin1(QObject *parent = nullptr); 14 | 15 | QString printText() const override; 16 | 17 | public slots: 18 | void ping(const QString &base) override; 19 | 20 | signals: 21 | void pong(const QString &result) final; 22 | }; 23 | 24 | #endif // PLUGIN1_H 25 | -------------------------------------------------------------------------------- /Sample/Sample.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | App \ 5 | Plugin1 6 | -------------------------------------------------------------------------------- /qpluginfactory.cpp: -------------------------------------------------------------------------------- 1 | #include "qpluginfactory.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | class StaticPluginInfo : public QPluginFactoryBase::PluginInfo 12 | { 13 | public: 14 | StaticPluginInfo(QStaticPlugin plugin); 15 | 16 | QJsonObject metaData() const override; 17 | QObject *instance() override; 18 | bool isLoaded() const override; 19 | void unload(const QString &key) override; 20 | 21 | private: 22 | QStaticPlugin _plugin; 23 | }; 24 | 25 | #if QT_CONFIG(library) 26 | 27 | class DynamicPluginInfo : public QPluginFactoryBase::PluginInfo 28 | { 29 | public: 30 | DynamicPluginInfo(QScopedPointer &loader); 31 | 32 | QJsonObject metaData() const override; 33 | QObject *instance() override; 34 | bool isLoaded() const override; 35 | void unload(const QString &key) override; 36 | 37 | private: 38 | QScopedPointer _loader; 39 | }; 40 | 41 | #endif 42 | 43 | } 44 | 45 | 46 | 47 | QPluginFactoryBase::QPluginFactoryBase(QString pluginType, QObject *parent, bool isDebugBuild) : 48 | QPluginFactoryBase(std::move(pluginType), QByteArray(), parent, isDebugBuild) 49 | {} 50 | 51 | QPluginFactoryBase::QPluginFactoryBase(QString pluginType, QByteArray pluginIid, QObject *parent, bool isDebugBuild) : 52 | QObject{parent}, 53 | _isDebugBuild{isDebugBuild}, 54 | _pluginType{std::move(pluginType)}, 55 | _pluginIid{std::move(pluginIid)} 56 | { 57 | //setup dynamic plugins 58 | reloadPlugins(); 59 | } 60 | 61 | void QPluginFactoryBase::addSearchDir(const QDir &dir, bool isTopLevel) 62 | { 63 | if(isTopLevel) { 64 | auto mDir = dir; 65 | if(mDir.cd(_pluginType)) 66 | _extraDirs.append(mDir); 67 | } else 68 | _extraDirs.append(dir); 69 | } 70 | 71 | QStringList QPluginFactoryBase::allKeys() const 72 | { 73 | QMutexLocker _{&_loaderMutex}; 74 | return _plugins.keys(); 75 | } 76 | 77 | QJsonObject QPluginFactoryBase::metaData(const QString &key) const 78 | { 79 | QMutexLocker _{&_loaderMutex}; 80 | auto info = _plugins.value(key); 81 | if(info) 82 | return info->metaData()[QStringLiteral("MetaData")].toObject(); 83 | else 84 | return {}; 85 | } 86 | 87 | QObject *QPluginFactoryBase::plugin(const QString &key) const 88 | { 89 | QMutexLocker _{&_loaderMutex}; 90 | auto info = _plugins.value(key); 91 | if(info) 92 | return info->instance(); 93 | else 94 | return nullptr; 95 | } 96 | 97 | QString QPluginFactoryBase::pluginType() const 98 | { 99 | return _pluginType; 100 | } 101 | 102 | QByteArray QPluginFactoryBase::pluginIid() const 103 | { 104 | return _pluginIid; 105 | } 106 | 107 | void QPluginFactoryBase::setPluginIid(const QByteArray &pluginIid) 108 | { 109 | _pluginIid = pluginIid; 110 | reloadPlugins(); 111 | } 112 | 113 | void QPluginFactoryBase::reloadPlugins() 114 | { 115 | QMutexLocker _{&_loaderMutex}; 116 | 117 | //find the plugin dir 118 | auto oldKeys = _plugins.keys(); 119 | 120 | #if QT_CONFIG(library) 121 | 122 | QList allDirs; 123 | //first: dirs in path 124 | //MAJOR remove 125 | auto envVar = QStringLiteral("PLUGIN_%1_PATH").arg(_pluginType.toUpper()).toUtf8(); 126 | #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) 127 | auto path = qEnvironmentVariable(envVar.constData()); 128 | #else 129 | auto path = QString::fromUtf8(qgetenv(envVar.constData())); 130 | #endif 131 | for(const auto &p : path.split(QDir::listSeparator(), QString::SkipEmptyParts)) { 132 | QDir plgDir{p}; 133 | if(plgDir.exists()) 134 | allDirs.append(plgDir); 135 | } 136 | 137 | //second: extra dirs 138 | allDirs.append(_extraDirs); 139 | 140 | //third: original plugin dirs 141 | for(const auto &plgPath : QCoreApplication::libraryPaths()) { 142 | QDir plgDir{plgPath}; 143 | if(plgDir.cd(_pluginType)) 144 | allDirs.append(plgDir); 145 | } 146 | 147 | //setup dynamic plugins 148 | for(const auto &pluginDir : allDirs) { 149 | #ifdef Q_OS_WIN 150 | for(const auto &info : pluginDir.entryInfoList({QStringLiteral("*.dll")}, QDir::Files | QDir::Readable)) { 151 | #else 152 | for(const auto &info : pluginDir.entryInfoList(QDir::Files | QDir::Readable)) { 153 | #endif 154 | QScopedPointer loader{new QPluginLoader{info.absoluteFilePath()}}; 155 | auto metaData = loader->metaData(); 156 | auto keys = checkMeta(metaData, loader->fileName()); 157 | if(keys.isEmpty()) 158 | continue; 159 | 160 | auto dynInfo = QSharedPointer::create(loader); 161 | for(const auto key : keys) { 162 | auto k = key.toString(); 163 | if(!_plugins.contains(k)) 164 | _plugins.insert(k, dynInfo); 165 | oldKeys.removeOne(k); 166 | } 167 | } 168 | } 169 | 170 | #endif 171 | 172 | //setup static plugins 173 | for(const auto &info : QPluginLoader::staticPlugins()) { 174 | auto keys = checkMeta(info.metaData(), QString()); 175 | for(const auto key : keys) { 176 | auto k = key.toString(); 177 | if(!_plugins.contains(k)) 178 | _plugins.insert(k, QSharedPointer::create(info)); 179 | oldKeys.removeOne(k); 180 | } 181 | } 182 | 183 | //remove old, now unused plugins 184 | for(const auto &key : oldKeys) 185 | _plugins.remove(key); 186 | } 187 | 188 | bool QPluginFactoryBase::isLoaded(const QString &key) const 189 | { 190 | QMutexLocker _{&_loaderMutex}; 191 | auto info = _plugins.value(key); 192 | if(info) 193 | return info->isLoaded(); 194 | else 195 | return false; 196 | } 197 | 198 | void QPluginFactoryBase::unload(const QString &key) 199 | { 200 | QMutexLocker _{&_loaderMutex}; 201 | auto info = _plugins.value(key); 202 | if(info) 203 | info->unload(key); 204 | } 205 | 206 | QJsonArray QPluginFactoryBase::checkMeta(const QJsonObject &metaData, const QString &filename) const 207 | { 208 | if(metaData[QStringLiteral("debug")].toBool() != _isDebugBuild) 209 | return {}; 210 | 211 | auto iid = metaData.value(QStringLiteral("IID")).toString().toUtf8(); 212 | if(!_pluginIid.isNull() && iid != _pluginIid) { 213 | qWarning().noquote() << "File" << filename << "is no plugin of type" << _pluginIid; 214 | return {}; 215 | } 216 | 217 | auto data = metaData[QStringLiteral("MetaData")].toObject(); 218 | auto keys = data[QStringLiteral("Keys")].toArray(); 219 | if(keys.isEmpty()) { 220 | qWarning().noquote() << "Plugin" << filename << "is does not provide any Keys"; 221 | return {}; 222 | } 223 | 224 | return keys; 225 | } 226 | 227 | 228 | 229 | #if QT_CONFIG(library) 230 | 231 | QPluginLoadException::QPluginLoadException(QPluginLoader *loader) : 232 | _what{QStringLiteral("Failed to load plugin \"%1\" with error: %2") 233 | .arg(loader->fileName(), loader->errorString()) 234 | .toUtf8()} 235 | {} 236 | 237 | const char *QPluginLoadException::what() const noexcept 238 | { 239 | return _what.constData(); 240 | } 241 | 242 | void QPluginLoadException::raise() const 243 | { 244 | throw *this; 245 | } 246 | 247 | QExceptionBase::Base *QPluginLoadException::clone() const 248 | { 249 | return new QPluginLoadException{*this}; 250 | } 251 | 252 | #endif 253 | 254 | 255 | 256 | StaticPluginInfo::StaticPluginInfo(QStaticPlugin plugin) : 257 | _plugin{plugin} 258 | {} 259 | 260 | QJsonObject StaticPluginInfo::metaData() const 261 | { 262 | return _plugin.metaData(); 263 | } 264 | 265 | QObject *StaticPluginInfo::instance() 266 | { 267 | return _plugin.instance(); 268 | } 269 | 270 | bool StaticPluginInfo::isLoaded() const 271 | { 272 | return true; 273 | } 274 | 275 | void StaticPluginInfo::unload(const QString &key) 276 | { 277 | Q_UNUSED(key) 278 | } 279 | 280 | 281 | 282 | #if QT_CONFIG(library) 283 | 284 | DynamicPluginInfo::DynamicPluginInfo(QScopedPointer &loader) 285 | { 286 | _loader.swap(loader); 287 | } 288 | 289 | QJsonObject DynamicPluginInfo::metaData() const 290 | { 291 | return _loader->metaData(); 292 | } 293 | 294 | QObject *DynamicPluginInfo::instance() 295 | { 296 | if(!_loader->isLoaded() && !_loader->load()) 297 | throw QPluginLoadException{_loader.data()}; 298 | return _loader->instance(); 299 | } 300 | 301 | bool DynamicPluginInfo::isLoaded() const 302 | { 303 | return _loader->isLoaded(); 304 | } 305 | 306 | void DynamicPluginInfo::unload(const QString &key) 307 | { 308 | if(_loader->isLoaded()){ 309 | if(!_loader->unload()) 310 | qWarning().noquote() << "Failed to unload plugin for key" << key << "with error:" << _loader->errorString(); 311 | } 312 | } 313 | 314 | #endif 315 | -------------------------------------------------------------------------------- /qpluginfactory.h: -------------------------------------------------------------------------------- 1 | #ifndef QPLUGINFACTORY_H 2 | #define QPLUGINFACTORY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #if QT_CONFIG(library) 12 | class Q_PLUGIN_FACTORY_EXPORT QPluginLoadException : public QExceptionBase 13 | { 14 | public: 15 | QPluginLoadException(QPluginLoader *loader); 16 | 17 | const char *what() const noexcept override; 18 | void raise() const override; 19 | Base *clone() const override; 20 | 21 | private: 22 | const QByteArray _what; 23 | }; 24 | #else 25 | //dummy class 26 | class Q_PLUGIN_FACTORY_EXPORT QPluginLoadException : public QExceptionBase {}; 27 | #endif 28 | 29 | #ifdef QT_NO_DEBUG 30 | #define Q_PLUGIN_FACTORY_IS_DEBUG false 31 | #else 32 | #define Q_PLUGIN_FACTORY_IS_DEBUG true 33 | #endif 34 | 35 | class Q_PLUGIN_FACTORY_EXPORT QPluginFactoryBase : public QObject 36 | { 37 | Q_OBJECT 38 | 39 | Q_PROPERTY(QString pluginType READ pluginType CONSTANT) 40 | Q_PROPERTY(QByteArray pluginIid READ pluginIid WRITE setPluginIid) 41 | 42 | public: 43 | class Q_PLUGIN_FACTORY_EXPORT PluginInfo { 44 | Q_DISABLE_COPY(PluginInfo) 45 | public: 46 | inline PluginInfo() = default; 47 | virtual inline ~PluginInfo() = default; 48 | virtual QJsonObject metaData() const = 0; 49 | virtual QObject *instance() = 0; 50 | 51 | virtual bool isLoaded() const = 0; 52 | virtual void unload(const QString &key) = 0; 53 | }; 54 | 55 | QPluginFactoryBase(QString pluginType, QObject *parent = nullptr, bool isDebugBuild = Q_PLUGIN_FACTORY_IS_DEBUG); 56 | QPluginFactoryBase(QString pluginType, QByteArray pluginIid, QObject *parent = nullptr, bool isDebugBuild = Q_PLUGIN_FACTORY_IS_DEBUG); 57 | 58 | void addSearchDir(const QDir &dir, bool isTopLevel = false); 59 | 60 | QStringList allKeys() const; 61 | QJsonObject metaData(const QString &key) const; 62 | QObject *plugin(const QString &key) const; 63 | 64 | QString pluginType() const; 65 | QByteArray pluginIid() const; 66 | 67 | public Q_SLOTS: 68 | virtual void setPluginIid(const QByteArray &pluginIid); 69 | 70 | void reloadPlugins(); 71 | 72 | protected: 73 | bool isLoaded(const QString &key) const; 74 | void unload(const QString &key); 75 | 76 | private: 77 | const bool _isDebugBuild; 78 | const QString _pluginType; 79 | QByteArray _pluginIid; 80 | QList _extraDirs; 81 | 82 | mutable QMutex _loaderMutex; 83 | QHash> _plugins; 84 | 85 | QJsonArray checkMeta(const QJsonObject &metaData, const QString &filename) const; 86 | }; 87 | 88 | template 89 | class QPluginFactory : public QPluginFactoryBase 90 | { 91 | public: 92 | QPluginFactory(const QString &pluginType, QObject *parent = nullptr); 93 | 94 | TPlugin *plugin(const QString &key) const; 95 | QObject *pluginObj(const QString &key) const; 96 | 97 | void setPluginIid(const QByteArray &) override; 98 | }; 99 | 100 | template 101 | class QPluginObjectFactory : public QPluginFactory 102 | { 103 | public: 104 | QPluginObjectFactory(const QString &pluginType, QObject *parent = nullptr); 105 | 106 | template 107 | TObject *createInstance(const QString &key, Args... args) const; 108 | }; 109 | 110 | //single-line loader method 111 | 112 | 113 | // ------------- Template Implementations ------------- 114 | 115 | template 116 | QPluginFactory::QPluginFactory(const QString &pluginType, QObject *parent) : 117 | QPluginFactoryBase(pluginType, qobject_interface_iid(), parent) 118 | {} 119 | 120 | template 121 | TPlugin *QPluginFactory::plugin(const QString &key) const 122 | { 123 | return qobject_cast(QPluginFactoryBase::plugin(key)); 124 | } 125 | 126 | template 127 | QObject *QPluginFactory::pluginObj(const QString &key) const 128 | { 129 | return QPluginFactoryBase::plugin(key); 130 | } 131 | 132 | template 133 | void QPluginFactory::setPluginIid(const QByteArray &) {} 134 | 135 | template 136 | QPluginObjectFactory::QPluginObjectFactory(const QString &pluginType, QObject *parent) : 137 | QPluginFactory(pluginType, parent) 138 | {} 139 | 140 | template 141 | template 142 | TObject *QPluginObjectFactory::createInstance(const QString &key, Args... args) const 143 | { 144 | auto plg = this->plugin(key); 145 | if(plg) 146 | return plg->createInstance(key, args...); 147 | else 148 | return nullptr; 149 | } 150 | 151 | #define Q_GLOBAL_PLUGIN_FACTORY(PluginType, pluginKey, instName) namespace { \ 152 | typedef QPluginFactory __QGPF_ ## PluginType ## _Factory; \ 153 | Q_GLOBAL_STATIC_WITH_ARGS(__QGPF_ ## PluginType ## _Factory, instName, (QString::fromUtf8(pluginKey))) \ 154 | } 155 | 156 | #define Q_GLOBAL_PLUGIN_OBJECT_FACTORY(PluginType, ObjectType, pluginKey, instName) namespace { \ 157 | typedef QPluginObjectFactory __QGPF_ ## PluginType ## _Factory; \ 158 | Q_GLOBAL_STATIC_WITH_ARGS(__QGPF_ ## PluginType ## _Factory, instName, (QString::fromUtf8(pluginKey))) \ 159 | } 160 | 161 | #endif // QPLUGINFACTORY_H 162 | -------------------------------------------------------------------------------- /qpluginfactory.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | 3 | HEADERS += \ 4 | $$PWD/qpluginfactory.h 5 | 6 | SOURCES += \ 7 | $$PWD/qpluginfactory.cpp 8 | 9 | QDEP_DEPENDS += Skycoder42/QExceptionBase 10 | 11 | QDEP_PACKAGE_EXPORTS += Q_PLUGIN_FACTORY_EXPORT 12 | !qdep_build: DEFINES += "Q_PLUGIN_FACTORY_EXPORT=" 13 | --------------------------------------------------------------------------------