使用具有新 signal-slot 语法的 Qt 插件系统在接口 class 中声明信号
Declaring signals in interface class using Qt plugin system with new signal-slot syntax
我正在使用 low-level Qt plugin API 为 Qt 应用程序构建一组插件。管理器 object 将在运行时加载这些插件,并允许客户端程序访问任何可用的插件。我希望管理器通过信号和插槽与插件 classes 通信,因为这些插件可能与管理器位于不同的线程中。
所以每个插件必须实现的接口应该声明这些信号和槽。插槽没有问题,因为它们实际上只是每个插件必须实现的抽象成员函数。信号是问题所在。
虽然可以在接口 class 中声明信号,但在编译过程中 Qt 的 moc
定义了它们 auto-generated。因此,我可以在接口 class 中定义这些信号,但是在创建实现该接口的插件时,构建在 link 处失败。这是因为信号的定义在 interface object 文件中,而不是 plugin object 文件中。
所以问题是,我如何确定 auto-generated 中定义的信号的实现=23=] class 在构建 Plugin
class?
时生成 and/or linked
这是一个演示问题的最小的完整示例。
目录结构
test
|_ test.pro
|_ app
|_ app.pro
|_ interface.h
|_ main.cc
|_ plugin
|_ plugin.pro
|_ plugin.h
在test.pro
中:
TEMPLATE = subdirs
SUBDIRS = app plugin
在app/app.pro
中:
TEMPLATE = app
QT += testlib
HEADERS = interface.h
SOURCES = main.cc
TARGET = test-app
DESTDIR = ../
在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
在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;
}
在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
在plugin/plugin.pro
中:
TEMPLATE = lib
CONFIG += plugin
INCLUDEPATH += ../app
HEADERS = plugin.h
TARGET = $$qtLibraryTarget(plugin)
DESTDIR = ../plugins
这可以通过从 top-level 目录调用 qmake && make
来编译。
照原样,Interface
继承自QObject
,因此它可以定义所有插件共享的信号。但是在编译 plugin
子目录时,我们得到一个 linker 错误:
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
sub-directory 时,那个 object 文件既没有编译也没有 linked,所以没有符号的定义并且 link 失败。
我实际上可以很容易地解决这个问题,方法是在 plugin.pro
文件中包含以下行:
LIBS += ../app/moc_interface.o
或添加:
#include "moc_interface.cpp"
到 plugin/plugin.h
结束。
这两个似乎都不是个好主意,因为这些文件是在构建 app
期间自动生成的,我无法保证它们存在。我希望新插件的编写者只需要担心包含 "interface.h"
header,而不是这些 auto-generated 文件。
所以问题是,如何让 qmake
在构建 Plugin
时包含来自 Interface
class 的信号定义?
相关问题:
我知道 this answer 解决了一个密切相关的问题。但这使用 old-style "stringified" 版本的连接信号和槽。我更愿意使用新的 pointer-to-member 语法,它提供 compile-time 检查。此外,该解决方案需要 dynamic_cast
ing 接口,这比 Interface
class 直接继承自 QObject
更 error-prone 且效率更低。
您的主要错误是您合并的项目具有循环依赖性。
我已经使用以下结构重组了您的项目:
test
├── test.pro
├── App
│ ├── App.pro
│ └── main.cpp
├── InterfacePlugin
│ ├── interfaceplugin_global.h
│ ├── interfaceplugin.h
│ └── InterfacePlugin.pro
└──Plugin
├── plugin_global.h
├── plugin.h
└── Plugin.pro
在里面我们看到Interface是一个独立的库。 App和Plugin是2个使用它的项目。
完整的工程可以在下面link.
找到
我正在使用 low-level Qt plugin API 为 Qt 应用程序构建一组插件。管理器 object 将在运行时加载这些插件,并允许客户端程序访问任何可用的插件。我希望管理器通过信号和插槽与插件 classes 通信,因为这些插件可能与管理器位于不同的线程中。
所以每个插件必须实现的接口应该声明这些信号和槽。插槽没有问题,因为它们实际上只是每个插件必须实现的抽象成员函数。信号是问题所在。
虽然可以在接口 class 中声明信号,但在编译过程中 Qt 的 moc
定义了它们 auto-generated。因此,我可以在接口 class 中定义这些信号,但是在创建实现该接口的插件时,构建在 link 处失败。这是因为信号的定义在 interface object 文件中,而不是 plugin object 文件中。
所以问题是,我如何确定 auto-generated 中定义的信号的实现=23=] class 在构建 Plugin
class?
这是一个演示问题的最小的完整示例。
目录结构
test
|_ test.pro
|_ app
|_ app.pro
|_ interface.h
|_ main.cc
|_ plugin
|_ plugin.pro
|_ plugin.h
在test.pro
中:
TEMPLATE = subdirs
SUBDIRS = app plugin
在app/app.pro
中:
TEMPLATE = app
QT += testlib
HEADERS = interface.h
SOURCES = main.cc
TARGET = test-app
DESTDIR = ../
在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
在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;
}
在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
在plugin/plugin.pro
中:
TEMPLATE = lib
CONFIG += plugin
INCLUDEPATH += ../app
HEADERS = plugin.h
TARGET = $$qtLibraryTarget(plugin)
DESTDIR = ../plugins
这可以通过从 top-level 目录调用 qmake && make
来编译。
照原样,Interface
继承自QObject
,因此它可以定义所有插件共享的信号。但是在编译 plugin
子目录时,我们得到一个 linker 错误:
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
sub-directory 时,那个 object 文件既没有编译也没有 linked,所以没有符号的定义并且 link 失败。
我实际上可以很容易地解决这个问题,方法是在 plugin.pro
文件中包含以下行:
LIBS += ../app/moc_interface.o
或添加:
#include "moc_interface.cpp"
到 plugin/plugin.h
结束。
这两个似乎都不是个好主意,因为这些文件是在构建 app
期间自动生成的,我无法保证它们存在。我希望新插件的编写者只需要担心包含 "interface.h"
header,而不是这些 auto-generated 文件。
所以问题是,如何让 qmake
在构建 Plugin
时包含来自 Interface
class 的信号定义?
相关问题:
我知道 this answer 解决了一个密切相关的问题。但这使用 old-style "stringified" 版本的连接信号和槽。我更愿意使用新的 pointer-to-member 语法,它提供 compile-time 检查。此外,该解决方案需要 dynamic_cast
ing 接口,这比 Interface
class 直接继承自 QObject
更 error-prone 且效率更低。
您的主要错误是您合并的项目具有循环依赖性。
我已经使用以下结构重组了您的项目:
test
├── test.pro
├── App
│ ├── App.pro
│ └── main.cpp
├── InterfacePlugin
│ ├── interfaceplugin_global.h
│ ├── interfaceplugin.h
│ └── InterfacePlugin.pro
└──Plugin
├── plugin_global.h
├── plugin.h
└── Plugin.pro
在里面我们看到Interface是一个独立的库。 App和Plugin是2个使用它的项目。
完整的工程可以在下面link.
找到