Declaring signals in interface class using Qt plugin system with new signal-slot syntax

2.3k Views Asked by At

I'm building a set of plugins for a Qt application, using the low-level Qt plugin API. A manager object will load these plugins at runtime, and allow client programs access to any that are available. I'd like the manager to communicate with the plugin classes via signals and slots, since these plugins may live in different threads than the manager.

So the interface that each plugin must implement should declare these signals and slots. The slots are no problem, because they're really just abstract member functions that each plugin must implement. The signals are the problem.

While the signals can be declared in the interface class, their definition is auto-generated by Qt's moc during the compilation process. Thus I can define these signals in the interface class, but when creating a plugin which implements the interface, the build fails at link. This is because the definition of the signals is in the interface object file, not the plugin object file.

So the question is, how can I be sure that the auto-generated implementations of the signals defined in the Interface class are generated and/or linked when building the Plugin class?

Here is a minimal, complete example to demonstrate the problem.

Directory structure

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

This can be compiled by calling qmake && make from the top-level directory.

As is, Interface inherits from QObject, so that it can define the signal that all plugins share. But when compiling the plugin subdirectory, we get a linker error:

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

This makes sense to me. The moc implements the signal Interface::name(QString), so the implementation and its related symbols are in moc_interface.o. That object file is neither compiled nor linked when building the plugin sub-directory, so there is no definition of the symbols and the link fails.

I can actually fix this pretty easily, either by including the following line in the plugin.pro file:

LIBS += ../app/moc_interface.o

Or adding:

#include "moc_interface.cpp"

to the end of plugin/plugin.h.

Both of these seem like a bad idea, since these files are automatically generated during the build of app, and there's no real way for me to guarantee that they exist. I'd prefer that the writer of a new plugin only need to worry about including the "interface.h" header, and not these auto-generated files.

So the question is, how can I get qmake to include the definitions of the signals from the Interface class when building the Plugin?

Related questions:

I know that this answer solves a closely related problem. But that uses the old-style "stringified" version of connecting signals and slots. I'd prefer to use the new, pointer-to-member syntax, which offers compile-time checks. Also, that solution requires dynamic_casting the interface, which is more error-prone and less efficient than having the Interface class just inherit directly from QObject.

1

There are 1 best solutions below

5
On BEST ANSWER

Your main mistake is that you are combining projects that have circular dependencies.

I have restructured your project using the following structure:

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

In it we see that the Interface is an independent library. App and Plugin are 2 projects that use it.

The complete project can be found at the following link.