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_cast
ing the interface, which is more error-prone and less efficient than having the Interface
class just inherit directly from QObject
.
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.