我正在使用low-level Qt plugin API为Qt应用程序构建一组插件。一个管理器对象将在运行时加载这些插件,并允许客户端程序访问任何可用的插件。我希望管理器通过信号和插槽与插件类进行通信,因为这些插件可能位于与管理器不同的线程中。

因此,每个插件必须实现的接口(interface)应声明这些信号和插槽。插槽没有问题,因为它们实际上只是每个插件必须实现的抽象成员函数。信号就是问题。

尽管可以在接口(interface)类中声明信号,但是它们的定义是在编译过程中由Qt的moc自动生成的。因此,我可以在接口(interface)类中定义这些信号,但是在创建实现该接口(interface)的插件时,构建在链接处失败。这是因为信号的定义在接口(interface)目标文件中,而不在插件目标文件中。

因此,问题是,在构建Interface类时,如何确保生成和/或链接了Plugin类中定义的信号的自动生成的实现?

这是一个最小的完整示例来说明问题。

目录结构

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

可以通过从顶级目录调用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" header ,而不必担心这些自动生成的文件。

因此,问题是,在构建qmake时,如何获取Interface来包括Plugin类的信号定义?

相关问题:

我知道this answer解决了一个密切相关的问题。但这使用了连接信号和插槽的旧式“字符串化”版本。我更喜欢使用新的,指向成员的语法,该语法提供编译时检查。而且,该解决方案要求对接口(interface)进行dynamic_cast编码,与仅直接从Interface继承QObject类相比,该接口(interface)更容易出错且效率更低。

最佳答案

您的主要错误是您正在组合具有循环依赖项的项目。

我已经使用以下结构重组了您的项目:

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中找到。

10-07 19:07
查看更多