Объявление сигналов в классе интерфейса с использованием системы плагинов Qt с новым синтаксисом сигнальных слотов - PullRequest
0 голосов
/ 24 мая 2018

Я создаю набор плагинов для приложения 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.

1 Ответ

0 голосов
/ 25 мая 2018

Ваша основная ошибка в том, что вы объединяете проекты с круговыми зависимостями.

Я реструктурировал ваш проект, используя следующую структуру:

test
├── test.pro
├── App
│   ├── App.pro
│   └── main.cpp
├── InterfacePlugin
│   ├── interfaceplugin_global.h
│   ├── interfaceplugin.h
│   └── InterfacePlugin.pro
└──Plugin
    ├── plugin_global.h
    ├── plugin.h
    └── Plugin.pro

В нем мы видим, что интерфейснезависимая библиотека.App и Plugin - это 2 проекта, которые его используют.

Полный проект можно найти по следующей ссылке .

...