代码之家  ›  专栏  ›  技术社区  ›  bnaecker

使用带有新信号槽语法的qt插件系统在接口类中声明信号

  •  2
  • bnaecker  · 技术社区  · 7 年前

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

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

    虽然信号可以在接口类中声明,但它们的定义是由qt自动生成的 moc 在编译过程中。因此,我可以在接口类中定义这些信号,但是当创建实现接口的插件时,构建在链接处失败。这是因为信号的定义在 接口 对象文件,而不是 插件 对象文件。

    所以问题是 ,如何确保在 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 从顶层目录。

    事实上, 接口 继承自 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
    

    这对我来说是有意义的。这个 莫克 执行信号 Interface::name(QString) ,因此实现及其相关符号位于 moc_interface.o . 在生成 插件 子目录,因此没有定义符号,链接失败。

    我可以很容易地解决这个问题,或者在 plugin.pro 文件:

    LIBS += ../app/moc_interface.o
    

    或添加:

    #include "moc_interface.cpp"
    

    到最后 插件/插件.h .

    这两个看起来都是一个坏主意,因为这些文件是在 app 我无法保证它们的存在。我宁愿一个新插件的作者只需要担心包括 "interface.h" 头文件,而不是这些自动生成的文件。

    所以问题是,我怎样才能 qmake 包括来自 接口 创建时的类 插件 ?

    相关问题:

    我知道。 this answer 解决一个密切相关的问题。但这使用了老式的连接信号和插槽的“串化”版本。我更喜欢使用新的指向成员的指针语法,它提供编译时检查。而且,该解决方案需要 dynamic_cast 接口,这比 接口 类直接继承自 QObject(质量对象) .

    1 回复  |  直到 6 年前
        1
  •  3
  •   eyllanesc Yonghwan Shin    6 年前

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

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

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

    在其中,我们看到接口是一个独立的库。应用程序和插件是使用它的两个项目。

    完整的项目可以在以下位置找到 link .