设置首页收藏本站
开启左侧

[C++] Qt 插件机制使用及原理

[复制链接]
Criss 发表于 2023-6-5 15:28:07 | 显示全部楼层 |阅读模式
本篇文章主要讲述一下Qt插件机制的使用和原理,以代码示例展现其使用方法,并详细地描述其实现原理,希望给需要的人一点点帮助。

按照Qt给的文档和demo, 实现一个Qt插件还是很容易的,只需要使用三个宏:
  1. Q_DECLARE_INTERFACE
  2. Q_PLUGIN_METADATA
  3. Q_INTERFACES
复制代码


1. 使用方法
1.1 定义一个插件接口
  1. class MyPluginInterface
  2. {
  3.     public:
  4.     virtual void DoSomething() = 0;
  5. }

  6. QT_BEGIN_NAMESPACE
  7. #define MyPluginInterface_IId "MyPluginInterface_id"
  8. Q_DECLARE_INTERFACE(MyPluginInterface, MyPluginInterface_IId);
  9. QT_END_NAMESPACE
复制代码

这里可以定义想要的接口函数,定义完通过Q_DECLARE_INTERFACE来申明一下接口,当然这里需要自定义个插件接口id。

1.2 现实插件接口
  1. class MyPluginImplement1: public QObject, public MyPluginInterface
  2. {
  3.     Q_OBJECT
  4.             Q_PLUGIN_METADATA(IID MyPluginInterface_Id)
  5.             Q_INTERFACES(MyPluginInterface)
  6.     public:
  7.     MyPluginImplement1();
  8.     virtual void DoSomething() override;
  9. }

  10. class MyPluginImplement2: public QObject, public MyPluginInterface
  11. {
  12.     Q_OBJECT
  13.             Q_PLUGIN_METADATA(IID MyPluginInterface_Id)
  14.             Q_INTERFACES(MyPluginInterface)
  15.     public:
  16.     MyPluginImplement2();
  17.     virtual void DoSomething() override;
  18. }
复制代码

接下来就是在自己的插件DLL中实现具体的插件实现类,如上图MyPluginImplement1, 需要继承QObject和MyPluginInterface,继承QObject主要是为了使用QT的元对象数据,当然插件系统是基于元对象实现的。然后实现宏Q_PLUGIN_METADATA 和宏Q_INTERFACES, 来申明这个插件实现是基于哪个插件接口的。

1.3 加载使用插件
  1. class PluginsManager
  2. {
  3. bool loadPlugin(const QString& filepath)
  4. {
  5.     for (int i = 0 ; i < m_DllNameVec.size(); i++)
  6.     {
  7.         if (m_DllNameVec[i] == filepath)
  8.         {
  9.             return false;
  10.         }
  11.     }
  12.     QPluginLoader *loader = new QPluginLoader(filepath);
  13.     if (loader->load())
  14.     {
  15.         MyPluginInterface *plugin = qobject_cast<MyPluginInterface *>(loader->instance());
  16.         if (plugin) {
  17.             /// do everything what you want to do
  18.         }
  19.     }
  20. }
  21. }
复制代码

这样基于QT插件机制就创建了两个插件,然后就通过PluginsManager直接load对应的dll地址就可以获取到该插件直接使用了,插件DLL架构图如下图,是不是很简单? 对就是这么简单。
1.jpg

2 实现原理
接下来我们详细看一下Qt插件的实现原理,首先看一下三个宏对应具体实现:

2.1 Q_DECLARE_INTERFACE
  1. #define Q_DECLARE_INTERFACE(IFace, IId) \
  2.     template <> inline const char *qobject_interface_iid<IFace *>() \
  3.     { return IId; } \
  4.     template <> inline IFace *qobject_cast<IFace *>(QObject *object) \
  5.     { return reinterpret_cast<IFace *>((object ? object->qt_metacast(IId) : nullptr)); } \
  6.     template <> inline IFace *qobject_cast<IFace *>(const QObject *object) \
  7.     { return reinterpret_cast<IFace *>((object ? const_cast<QObject *>(object)->qt_metacast(IId) : nullptr)); }
复制代码

该宏定义在qobject.h中,主要就是定义了3个模板内联函数,展开如上, 通过QObject对象的meta_data中的id获取插件对象。

2.2 Q_INTERFACES
  1. #define Q_INTERFACES(x) QT_ANNOTATE_CLASS(qt_interfaces, x)
  2. # define QT_ANNOTATE_CLASS(type, ...)
复制代码

这里可以看到只是一个空的定义,在源码中没有实际意义。主要用于MOC的输入,MOC会为Q_INTERFACES动态生成一些代码,确保qobject_cast()能正确进行QObject*到接口指针的转换,如下所示(MyPluginImplement1):
  1. void *MyPluginImplement1::qt_metacast(const char *_clname)
  2. {
  3.     if (!_clname) return nullptr;
  4.     if (!strcmp(_clname, qt_meta_stringdata__MyPluginImplement1.stringdata0))
  5.         return static_cast<void*>(this);
  6.     if (!strcmp(_clname, "MyPluginImplement1"))
  7.         return static_cast< MyPluginInterface*>(this);
  8.     if (!strcmp(_clname, "MyPluginInterface_id"))
  9.         return static_cast< MyPluginInterface*>(this);
  10.     return QObject::qt_metacast(_clname);
  11. }
复制代码


2.3 Q_PLUGIN_METADATA
  1. #define Q_PLUGIN_METADATA(x) QT_ANNOTATE_CLASS(qt_plugin_metadata, x)
  2. # define QT_ANNOTATE_CLASS(type, ...)
复制代码

与Q_INTERFACES类似,在代码层面也是空的,也用于MOC的输入,让MOC来动态生成一些代码。实际上,Q_PLUGIN_METADATA让MOC生成导出函数qt_plugin_instance(),供QPluginLoader()调用,创建接口实例,返回QObject的指针,如下所示(MyPluginImplement1):
  1. QT_PLUGIN_METADATA_SECTION
  2. static constexpr unsigned char qt_pluginMetaData[] = {
  3.     'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',
  4.     // metadata version, Qt version, architectural requirements
  5.     0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(),
  6.     0xbf,
  7.     // "IID"
  8.     0x02,  0x78,  'M',  'y',  'P',  'l',  'u',
  9.     'g',  'i',  'n',  'I',  'n',  't',  'e',  'r',
  10.     'f',  'a',  'c',  'e',  '_',  'i',  'd',
  11.     // "className"
  12.     0x03,  0x73,  'M',  'y',  'P',  'l',  'u',
  13.     'g',  'i',  'n',  'I',  'm',  'p',  'l',  'e',
  14.     'm',  'e',  'n',  't',  '1',
  15.     0xff,
  16. };
  17. QT_MOC_EXPORT_PLUGIN(MyPluginImplement1, MyPluginImplement1)
复制代码


看到这里,想必大家已经对Qt插件有个比较深的了解,但肯定有人会有疑问? 插件类MyPluginImplement1实例对象是什么时候创建的呢? 为什么通过PluginsManager中QPluginLoader就可以直接获取到呢?

对于该问题,笔者仔细搜索一下发现如下:
  1. #  define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME)      \
  2.             Q_EXTERN_C Q_DECL_EXPORT \
  3.             const char *qt_plugin_query_metadata() \
  4.             { return reinterpret_cast<const char *>(qt_pluginMetaData); } \
  5.             Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \
  6.             Q_PLUGIN_INSTANCE(PLUGINCLASS)
复制代码

以上就是moc文件中,Q_PLUGIN_METADATA 宏自动生成的数据中QT_MOC_EXPORT_PLUGIN宏展开的实现,想必有些不太熟的人看起来还是有点别扭,不要急,已经帮你全部展开,如下:
  1. #  define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME)      \
  2.             extern "C" __declspec(dllexport) \
  3.             const char *qt_plugin_query_metadata() \
  4.             { return reinterpret_cast<const char *>(qt_pluginMetaData); } \
  5.             extern "C" __declspec(dllexport) QT_NAMESPACE::QObject *qt_plugin_instance() \
  6.             { \
  7.             static QT_NAMESPACE::QPointer<QT_NAMESPACE::QObject> _instance; \
  8.             if (!_instance) {    \
  9.                 QT_PLUGIN_RESOURCE_INIT \
  10.                 _instance = new IMPLEMENTATION; \
  11.             } \
  12.             return _instance; \
  13.         }
复制代码

看到这里,你是不是想说原来如此。
以上就是Qt插件的使用方法的原理了,是不是很简单。
奖励计划已经开启,本站鼓励作者发布最擅长的技术内容和资源,流量变现就在现在,[点我]加入吧~~~Go
osimida 发表于 2023-6-6 11:13:43 | 显示全部楼层
学习学习,开始学习cpp版qt中
奖励计划已经开启,本站鼓励作者发布最擅长的技术内容和资源,流量变现就在现在,[点我]加入吧~~~Go
asdfjkl0 发表于 2023-6-20 21:03:40 | 显示全部楼层
学习学习,加油,谢谢分享
奖励计划已经开启,本站鼓励作者发布最擅长的技术内容和资源,流量变现就在现在,[点我]加入吧~~~Go
fade_warr 发表于 2023-7-9 18:10:59 | 显示全部楼层
谢谢楼主分享
奖励计划已经开启,本站鼓励作者发布最擅长的技术内容和资源,流量变现就在现在,[点我]加入吧~~~Go
782712080@qq.co 发表于 2023-7-10 09:06:42 | 显示全部楼层
学习学习,加油,谢谢分享
奖励计划已经开启,本站鼓励作者发布最擅长的技术内容和资源,流量变现就在现在,[点我]加入吧~~~Go
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表