1.2 组件合成与插件化(Qt)

        本文是 《基于 Qt 的组件合成框架》的其中一节,建议全章阅读。


        Qt 插件在形式上是一个多态链接模块,在运行时,按需加载。同一个类型(或者一组)插件,存放在 plugins 目录的一个子目录中。在程序要使用某种插件功能时,需要搜索相关子目录,读取插件的描述信息,并据此找到该功能的一个合适的具体实现插件(模块)。然后加载这个模块,使用它里面的组件功能。

        在组件合成框架中,我们将自定义一种插件类型,并且将组件的插件化模块存放在自己的子目录(plugins/components)中。我们的插件描述其实就是一系列类型以及导出、导入描述。使用时,根据组件合成的规则(目标是使用某个具体的类型),去判断需要加载哪个插件。

实现 Qt 插件 

       实现 Qt 插件,首先需要为自定义的插件种类声明一个接口类,定义一个插件类型 ID。

class QComponentFactoryInterface
{
public:
};

#define ComponentFactory_iid \
    "org.qt-project.Qt.ComponentFactoryInterface/1.0"

Q_DECLARE_INTERFACE(QComponentFactoryInterface, ComponentFactory_iid)

        宏 Q_DECLARE_INTERFACE 用于实现运行时类型转换 qobject_cast,转换是通过匹配插件类型 ID 实现的。

        具体的插件模块需要实现一个上面接口类的派生类,并通过 Q_PLUGIN_METADATA、Q_INTERFACES 关联插件类型ID、插件描述以及接口类。使用 Q_INTERFACES 一次性可以关联多个接口类。

class MultiMediaPlugin : public QGenericPlugin
        , public QComponentFactoryInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID ComponentFactory_iid FILE "MultiMedia.json")
    Q_INTERFACES(QComponentFactoryInterface)

public:
    explicit MultiMediaPlugin(QObject *parent = nullptr);
private:
    QObject *create(const QString &name, const QString &spec) override;
};

        Moc 编译器根据上述信息生成代码,代码将 json 描述文件的内容以及其他信息导出到动态链接库的 TEXT SECTION 中。

        同时,Moc 生成的代码也会导出两个动态链接符号(函数) qt_plugin_query_metadata 和 qt_plugin_instance。其中 qt_plugin_query_metadata 用于返回插件信息,qt_plugin_instance 用于创建具体的插件实例。

        插件的 create 方法负责创建实现了具体功能的组件,怎么解释参数 name 和 spec 是由自定义插件自行规定。但是在组件合成框架中,插件的 create 方法没有什么作用,因为组件合成框架通过 Qt 的元数据信息来创建组件实例的。

加载插件描述信息

        我们并不希望提前将动态链接库加载到内存中,但是又要提前收集插件的描述信息(主要是组件导入导出的描述信息),才能构建所有导出、导入的关联性。

        Qt 的插件实现已经可以满足这样的需求,它将插件文件打开后,读出插件信息,然后又关闭的文件。一种实现方案是加载插件模块(在 Win32 中是调用 LoadLibrary),然后调用 qt_plugin_query_metadata 函数,来获取插件信息。还有一种方法是直接分析动态链接库的 TEXT SECTION,解析其中的数据,来构造插件信息。显然第二种方法更高效,因为 LoadLibrary 是需要完成所有符号动态链接过程的。另外 LoadLibrary 再 FreeLibrary 还是有副作用的,比如会执行插件模块的内部全局初始化。

        使用 Qt 获取插件描述的代码是这样的:

void QComponentRegistry::importPlugin(const QString &file)
{
    Loader * l = new Loader;
    l->setFileName(file);
    QJsonObject meta = l->metaData();
    if (meta.value("IID").toString() != ComponentFactory_iid) {
        delete l;
        return;
    }
    meta = meta.value("MetaData").toObject();
}

        最后,meta 中包括我们自定义的组件导出、导入描述信息结构。

        这里的导出、导入描述信息,与实际的通过全局变量定义的导出、导入声明,还是有区别的。除了信息不完整之外,通过插件描述构建的组件信息没有引用 QMetaObject 元数据对象,也不可能引用 QMetaObject 对象,因为插件还没有加载进来呢。

        我们通过虚拟的 QMetaObject 对象来作为临时替代,构建虚拟 QMetaObject 的方法是(这里忽略了填充 int_data、string_data 的细节,有兴趣可以查看源代码):

void MetaObjectBuilder::buildMetaData(QMetaObject * meta)
{
    ......
    // put the metaobject together
    meta->d.data = int_data;
    meta->d.extradata = nullptr;
    meta->d.stringdata = reinterpret_cast<const QByteArrayData *>(string_data);
    meta->d.static_metacall = nullptr;
    meta->d.relatedMetaObjects = nullptr;
    meta->d.superdata = &QObject::staticMetaObject;
}

        当加载插件后,插件的全局初始化过程会注册真实的导出、导入声明,我们再替换为真实的 QMetaObject 对象。

加载、卸载插件模块

        当真正需要插件某个具体组件时,才需要加载插件模块。获取 QPluginLoader 的 instance 时,就会加载插件。

bool QComponentRegistry::loadPlugin(QComponentRegistry::Loader *loader)
{
    if (!loader->isLoaded()) {
        if (!loader->instance()) {
            return false;
        }
        ......
    }
    return true;
}

        之所以使用 instance() 方法,而不是 load() 方法,是因为两个原因:第一个是能够做更多的检查,防止加载了错误的插件;另一个原因是可以让插件开发者在其自定义 Plugin 类中做一些初始化工作。

        当我们不再使用一个插件它的任何组件时,可以将插件卸载,以节省内存空间。

bool QComponentRegistry::unloadPlugin(QComponentRegistry::Loader *loader)
{
    if (!loader->unload())
        return false;
    ......
    return true;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值