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


前面我们提到组件合成的各个语言平台实现时,说到了 C# 中的 MEF 框架,它提供了很好的架构思路,并且核心部分很简洁,所以我们选择将 MEF 框架移植到 Qt 中来。

Managed Extensibility Framework (MEF) 托管扩展框架或MEF是用于创建轻量级和可扩展应用程序的库。它使应用程序开发人员无需配置即可发现和使用扩展。它还使扩展开发人员可以轻松地封装代码并避免脆弱的硬依赖性。MEF不仅允许扩展在应用程序内重用,而且还可以跨应用程序重用。

模型概述

MEF的主要模型包括下列元素,我们在迁移中将基础常用的部分进行了对比实现,舍弃了不常用或者难以在C++中实现的部分。

  • 导入导出基础(Import and Export Basics)
  • 导入类型(Types of Imports)
  • 避免被发现(Avoiding Discovery)
  • 扩展信息(Metadata and Metadata Views)
  • 导入导出继承(Import and Export Inheritance)
  • 自定义导出属性(Custom Export Attributes)
  • 创建策略(Creation Policies)
  • 生命期及释放(Life Cycle and Disposing)

MEF的定义是基于 C# 语言的,映射到 Qt 上应该是这样的:

模型元素 Qt 映射 备注
类型 Type

QObject 及派生类

由 QMetaObject 对象代表

在 MEF 中,可以是任意类型
导出(Exports) 通过全局 QExport 对象声明

在 MEF 中,可作用于:类,字段,属性或方法

在 Qt 中,仅可作用于 QObject 类

导入(Imports) 提供全局 QImport 对象声明

在 MEF 中,可作用于:字段,属性或参数构造

在 Qt 中,仅可作用于 QObject 类的属性 Q_PROPERTY

扩展信息(Metadata)

通过 QObject 类的 Q_CLASSINFO 定义扩展信息

创建策略 通过 Q_INVOKABALE 构造方法创建对象
导出继承(Export Inheritance) 利用 QObject 类的Q_CLASSINFO 以及继承关系 实现一些与继承关系相关的策略

接下来看看如何用 Qt 实现模型的各个部分。

导出、导入(Exports、Imports)

假如我们实现了一个功能(形式上是一个类),然后要告诉别人有这样一个类可以使用,就需要通过一个导出声明来发布我们的功能组件。

又假如我们需要使用一个功能,但是不想操心它的创建、管理,那就发布一个导入声明,让组件合成框架来满足我们的需求。

导出声明本质上是在组件合成框架中注册一个类型,需要执行注册方法。导入声明也是注册一些需求信息,以便将来被满足。

在 C++ 中,要让一块代码没有被调用也能执行,好像只有全局对象的构造方法了。因此我们提供了 QExport、QImport 类,他们都是继承 QPart 类,让他们的构造方法完成相关的注册工作。

这些类的实现,都比较简单。但是在使用这些类定义对象时,需要注意使用规则。

你必须通过在全局作用域定义这些类的对象,以便在模块加载的时候,他们的构造函数就被执行。另外,在某些情况下,整个 C++编译单元(cpp文件)会被链接阶段丢弃,必须保证该编译单元的符号有被引用。

好在 C++ 11 引了的匿名名字空间,它能够保证匿名名字空间的全局对象一定会被创建。

这里也用到了一些 Qt 相关的技术:

首先,注册的类型必须是 QObject 派生类,通过 Q_OBJECT 创建了元数据信息,并且通过 Q_INVOKABLE 在元数据中导出了其构造方法。举个例子:

class RobotPenService: public DeviceService
{Q_OBJECT
public:Q_INVOKABLE RobotPenService();
};

其次,必须通过 Qt 的属性 (Q_PROPERTY)导入外部组件,这个属性的类型是 QObject 继承体系的类,也可以是数组(用来导入一组组件对象)。比如:

class DeviceManager : public QObject
{Q_OBJECTQ_PROPERTY(QList<DeviceService*> services READ services WRITE setServices)
public:QList<DeviceService*> services() const;void setServices(QList<DeviceService*> const & services);
};

从这里,你应该可以猜出来,我们是通过 Qt 元数据(QMetaObject)来创建组件对象的,正如所见:

QObject * QComponentContainer::getExportValue(QMetaObject const & meta, bool share)
{return getExportValue(meta, share, [](auto meta) {return meta.newInstance();});
}

QMetaObject::newInstance 也可以附带参数,建议阅读 Qt 文档了解更多。

而通过属性导入组件时,框架将组件的实例对象,用 setProperty 的方式,注入到使用该组件的对象中,这个过程是这样实现的:

void QImportBase::compose(QObject * obj, QVector<QObject *> const & targets) const
{if (typeRegister_)typeRegister_();obj->setProperty(prop_, QVariant::fromValue(targets));
}

当设置值的实际类型与属性类型不一样时,setProperty 会进行自动转换,值类型转换是 QVariant/QMetaType 提供的功能。其中 QObject 与其派生类指针类型转换是默认支持的,但是数组导入时,需要将 QVector<QObject*> 转换为目标属性的类型(比如可能 QList<U*>>),这就需要在 Qt 类型系统(QMetaType)里面注册相应的转换器(Converter)。上面的 typeRegister_ 是对应该导入声明的相关类型转换器的注册函数,它通常是这么实现的:

template<typename U, typename List>
inline static bool registerImportManyConverter()
{qRegisterMetaType<List>();return QMetaType::registerConverter<QVector<QObject*>, List>([](QVector<QObject*> const & f) {List list;for (auto l : f)list.push_back(qobject_cast<U*>(l));return list;});
}

使用 Qt 元数据的另一个例子是:自定义可选的注入完成后处理。在类中声明一个名称为 onComposition() 的槽方法,那么组件合成框架在完成该对象的属性注入后,会调用该方法,让对象可以做一些自己的后期处理工作。在框架中,通过 QMetaMethod 去调用该方法:

int index = meta.indexOfMethod("onComposition()");
if (index >= 0)meta.method(index).invoke(obj);

上面这种调用方式实现了弱契约模式,是可选的弱依赖性。使用 Qt 元数据可以很方便的实现这种模式。

除了使用 QMetaMethod 外,在 Qt 中,还有一个更直接的方法,可以用来实现上面的调用,它是 QMetaObject::invokeMethod,所以上面的代码可以改为:

QMetaObject::invokeMethod(obj, "onComposition")

但是使用这种方式,如果方法不存在或者调用参数不匹配,会有警告信息。

另外需要注意的是,这两种方式都会有一种线程亲缘性,方法必定在对象关联的线程中执行,如果调用线程不是对象关联的线程,那么就会通过向该对象发送事件来异步调用相关的方法。建议参考 Qt 文档了解如何传递参数和接收返回值。

扩展信息(Metadata)

利用 Qt 的类型信息(Q_CLASSINFO),可以为导出声明添加额外的扩展信息。形式是这样的:

class Control : public QObject
{Q_OBJECTQ_CLASSINFO("version", "1.0")
};

在组件合成框架中,通过 QMetaClassInfo 来读取这些信息:

void QExportBase::collectClassInfo()
{QMap<char const *, char const *> attrs;for (int i = 0; i < meta_->classInfoCount(); ++i) {QMetaClassInfo const ci = meta_->classInfo(i);attrs.insert(ci.name(), ci.value());}
};

上面的方法收集了一个类的所有 Qt 类型信息,包括信息的名称和值。但是有时候我们需要知道一个类型是否自己具有某个名称的类型信息(不包括从基类继承来的),后面就会有这样的例子,这里就不展开了。

导出继承(Export Inheritance)

在导出里面,我们还会提供一些与类型派生关系相关策略。比如,基类可以声明导出继承,那么在声明导出派生类时,也会用基类类型作为导出类型,注册一个新的导出项。

具体方案是,在基类中增加一个特定名称("InheritedExport")的类型信息,举个例子:

// qpart.h
#define Q_INHERITED_EXPORT Q_CLASSINFO("InheritedExport", "true")// deviceservice.h
class INTERACTBASE_EXPORT DeviceService : public QObject
{Q_OBJECTQ_INHERITED_EXPORT
};

在处理导出注册信息时,需要通过 QMetaObject 读取 InheritedExport 信息,并且要确定是哪个类型直接定义了 InheritedExport,而不是继承基类中的定义。

void QComponentRegistry::inheritedExport(QComponentRegistry::Meta &m, QExportBase *e)
{QMetaObject const * type = e->type_->superClass();while (type && type != &QObject::staticMetaObject) {int index = type->indexOfClassInfo("InheritedExport");if (index >= type->superClass()->classInfoCount()) {if (QByteArray("true") == type->classInfo(index).value()) {m.exports.push_back(new QExportBase(*e, type));}}type = type->superClass();}
}

这里,QMetaObject 的 superClass()->classInfoCount(),就是自己的直接 CLASS_INFO 的第一项的索引位置,当 index 不小于改索引值时,说明这个 InheritedExport 信息是属于 type 这个类型的。

当然,更底层的基类还可能定义了 InheritedExport 信息,所以遍历检查所有基类,直到触达最终基类 QObject::staticMetaObject。

通过 QMetaObject::superClass 可以遍历所有基类,在基础框架中,会经常用到这一点。

1.1 组件合成与 Qt 元数据相关推荐

  1. QT+高德地图Web服务API开发—静态地图开发Demo。QT组件提升、QT鼠标事件处理、Qt图片显示与移动、QT网络操作

    说明 在本次项目中,我们使用QT开发框架,高德地图Web服务地图API,完成一个项目Demo,进行一次对QT.对编程的学习. 本系类文章所包含内容包括: 1.QT组件提升 2.QT鼠标事件处理 3.Q ...

  2. Qt Creator创建组件

    Qt Creator创建组件 Qt Creator创建组件 QML进口 资产 在设计中添加组件 组件类型 使用Qt Quick 动画类型 为MCU创建UI 样式控制 Qt Quick Controls ...

  3. 在QT中CXDVA视频组件的例子

    在QT中CXDVA视频组件的例子 在windows 平台下,cxdva视频组件可以在Qt.MFC等等框架中嵌入视频播放画面.cxdva视频组件使用起来很简单,只用简单的的调用三四个函数就可以实现播放视 ...

  4. QT安装、添加或删除、更新组件

    参考和致谢: 1 http://blog.sina.com.cn/s/blog_8564b95d0102ybpz.html 2 https://www.cnblogs.com/SaveDictator ...

  5. 嵌入式跨平台UI开发必备组件!Qt系列控件2020年重磅首发,全新升级!

    Developer Machines公司是一家专门为程序员开发高效的.快速可靠的工具和类库的公司.其主要为开源UI开发框架QT提供商业组件,包括Chart.DataGrid.Ribbon控件.日前Qt ...

  6. 嵌入式UI开发必备组件——Qt系列全新升级,更多新功能等你来体验

    Developer Machines公司是一家专门为程序员开发高效的.快速可靠的工具和类库的公司.其主要为开源UI开发框架QT提供商业组件,包括Chart.DataGrid.Ribbon控件.日前Qt ...

  7. QT界面中实现视频帧显示的多种方法及应用

    QT界面中实现视频帧显示的多种方法及应用 (一) 引言 1.1 视频帧在QT界面中的应用场景 1.2 不同方法的性能和适用性分析 1.2.1 使用QLabel和QPixmap 1.2.2 使用QPai ...

  8. qt编写activex_Qt中使用ActiveX(一)

    由于最近需要使用ActiveX,一般来说可以使用微软提供的MFC或者ATL框架来开发,由于我个人对这部分内容不是很熟悉,好在Qt也提供对于ActiveX的支持.本文主要记录个人学习ActiveX的一些 ...

  9. Qt 框架性开发实践——基础框架篇

    基于 Qt 的组件合成框架https://blog.csdn.net/luansxx/article/details/120668676 基于 Qt 的消息总线https://blog.csdn.ne ...

最新文章

  1. 皮一皮:可怜的西瓜...
  2. 【强化学习】从强化学习基础概念开始
  3. POJ 1182 食物链 (并查集解法)(详细注释)
  4. VMware:为中国中小企业建立“外部云计算”
  5. WPF 制作便携小空调
  6. go操作mysql创建多对多_Django 数据库表多对多的创建和增删改查
  7. 火狐对ajax的onreadystatechange与IE的不同。
  8. AcWing 102. 最佳牛围栏
  9. 【mysql】关于IO/内存方面的一些优化
  10. 基于Windows字库的点阵数据提取方法
  11. FluentValidation:一个非常受欢迎的,用于构建强类型验证规则的.NET 库
  12. EP100含有.S文件的bootloader
  13. 自己用qt编写的图片查看器
  14. ndwi是什么意思_NDWI是什么意思
  15. 途家2019校招笔试 1 求最大公约数和最小公倍数
  16. httpd配置三种虚拟主机带访问控制
  17. 突变!微软将结束对Office Android应用的支持
  18. 06-golang布尔类型
  19. 计算机应用类专业综合知识模拟卷(七),计算机应用专业综合知识模拟试卷5
  20. VFB组件:ListBox控件(列表框)

热门文章

  1. insert()方法
  2. CSS pink老师教学笔记详解
  3. 用了两年的电脑,它哭着对我说:“我不行了”QAQ
  4. QQ能上但网页打不开的解决方法
  5. 华为鸿蒙六月更新机型,华为鸿蒙OS带来好消息,这8款机型6月2日升级,有你的手机吗?...
  6. CRM是什么意思?CRM管理软件选型必知的3大“要点”
  7. c语言字符就地逆置,高手看看我的C语言代码单链表实现就地逆置
  8. 【面试题】一个环,有n个点, 问从0点出发,经过k步回到原点有多少种方法
  9. 《汇编语言》第5章 [BX]和loop指令
  10. 抖音康辉机器人_抖音讯飞智声AI黑科技是什么 与明星互动就是这么简单