Я создаю набор плагинов для приложения Qt, используя низкоуровневый API плагинов Qt .Объект менеджера загрузит эти плагины во время выполнения и предоставит клиентским программам доступ ко всем доступным.Я бы хотел, чтобы менеджер общался с классами плагинов через сигналы и слоты, поскольку эти плагины могут жить в разных потоках, чем менеджер.
Поэтому интерфейс, который должен реализовывать каждый плагин, должен объявлять эти сигналы и слоты.Слоты не являются проблемой, потому что они на самом деле просто абстрактные функции-члены, которые должен реализовывать каждый плагин.Сигналы являются проблемой.
Хотя сигналы могут быть объявлены в классе интерфейса, их определение автоматически генерируется Qt moc
в процессе компиляции.Таким образом, я могу определить эти сигналы в классе интерфейса, но при создании плагина, который реализует интерфейс, сборка завершается неудачно по ссылке.Это связано с тем, что определение сигналов содержится в объектном файле interface , а не в объектном файле plugin .
Итак, вопрос в том , как я могу быть уверен, что автоматически сгенерированные реализации сигналов, определенных в классе Interface
, генерируются и / или связаны при построении класса Plugin
?
Вотминимальный, полный пример для демонстрации проблемы.
Структура каталогов
test
|_ test.pro
|_ app
|_ app.pro
|_ interface.h
|_ main.cc
|_ plugin
|_ plugin.pro
|_ plugin.h
In test.pro
:
TEMPLATE = subdirs
SUBDIRS = app plugin
In app/app.pro
:
TEMPLATE = app
QT += testlib
HEADERS = interface.h
SOURCES = main.cc
TARGET = test-app
DESTDIR = ../
In app/interface.h
:
#ifndef _INTERFACE_H_
#define _INTERFACE_H_
#include <QObject>
#include <QString>
class Interface : public QObject
{
Q_OBJECT
public:
virtual ~Interface() {}
// Slot which should cause emission of `name` signal.
virtual void getName() = 0;
signals:
// Signal to be emitted in getName()
void name(QString);
};
#define InterfaceIID "interface"
Q_DECLARE_INTERFACE(Interface, InterfaceIID)
#endif
In app/main.cc
:
#include "interface.h"
#include <QtCore>
#include <QSignalSpy>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Find plugin which implements the interface
Interface* interface;
QDir dir(qApp->applicationDirPath());
dir.cd("plugins");
for (auto& filename : dir.entryList(QDir::Files)) {
QPluginLoader loader(dir.absoluteFilePath(filename));
auto* plugin = loader.instance();
if (plugin) {
interface = qobject_cast<Interface*>(plugin);
break;
}
}
if (!interface) {
qDebug() << "Couldn't load interface!";
return 0;
}
// Verify plugin emits its `name` with `QSignalSpy`.
QSignalSpy spy(interface, &Interface::name);
QTimer::singleShot(100, interface, &Interface::getName);
spy.wait();
if (spy.count() == 1) {
auto name = spy.takeFirst().at(0).toString();
qDebug() << "Plugin emitted name:" << name;
} else {
qDebug() << "Not emitted!";
}
return 0;
}
In plugin/plugin.h
:
#ifndef _PLUGIN_H_
#define _PLUGIN_H_
#include "interface.h"
class Plugin : public Interface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "interface")
Q_INTERFACES(Interface)
public:
// Override abstract function to emit the `name` signal
void getName() override { emit name("plugin"); }
};
#endif
In plugin/plugin.pro
:
TEMPLATE = lib
CONFIG += plugin
INCLUDEPATH += ../app
HEADERS = plugin.h
TARGET = $$qtLibraryTarget(plugin)
DESTDIR = ../plugins
Это можно скомпилировать, вызвав qmake && make
из каталога верхнего уровня.
Как есть, Interface
наследуется от QObject
, так что он может определять сигнал, который разделяют все плагины.Но при компиляции подкаталога plugin
мы получаем ошибку компоновщика:
Undefined symbols for architecture x86_64:
"Interface::qt_metacall(QMetaObject::Call, int, void**)", referenced from:
Plugin::qt_metacall(QMetaObject::Call, int, void**) in moc_plugin.o
"Interface::qt_metacast(char const*)", referenced from:
Plugin::qt_metacast(char const*) in moc_plugin.o
"Interface::staticMetaObject", referenced from:
Plugin::staticMetaObject in moc_plugin.o
"Interface::name(QString)", referenced from:
Plugin::getName() in moc_plugin.o
"typeinfo for Interface", referenced from:
typeinfo for Plugin in moc_plugin.o
ld: symbol(s) not found for architecture x86_64
Это имеет смысл для меня.moc
реализует сигнал Interface::name(QString)
, поэтому реализация и связанные с ней символы находятся в moc_interface.o
.Этот объектный файл не компилируется и не связывается при построении подкаталога plugin
, поэтому определение символов отсутствует, а ссылка не срабатывает.
На самом деле это довольно легко исправить, добавив следующеестрока в файле plugin.pro
:
LIBS += ../app/moc_interface.o
Или добавление:
#include "moc_interface.cpp"
в конец plugin/plugin.h
.
Оба эти параметра кажутся плохимиИдея, поскольку эти файлы автоматически генерируются во время сборки app
, и у меня нет реального способа гарантировать, что они существуют.Я бы предпочел, чтобы создателю нового плагина нужно было беспокоиться только о включении заголовка "interface.h"
, а не об этих автоматически сгенерированных файлах.
Так что вопрос в том, как мне получить qmake
длявключить определения сигналов из класса Interface
при построении Plugin
?
Смежные вопросы:
Я знаю, что этот ответ решает тесно связанную проблему.Но здесь используется «строковая» версия старого стиля подключения сигналов и слотов.Я бы предпочел использовать новый синтаксис указатель на член, который предлагает проверки во время компиляции.Кроме того, это решение требует dynamic_cast
интерфейса, который более подвержен ошибкам и менее эффективен, чем класс Interface
, который просто наследуется от QObject
.