本篇文章主要讲述一下Qt插件机制的使用和原理,以代码示例展现其使用方法,并详细地描述其实现原理,希望给需要的人一点点帮助。
按照Qt给的文档和demo, 实现一个Qt插件还是很容易的,只需要使用三个宏:
- Q_DECLARE_INTERFACE
- Q_PLUGIN_METADATA
- Q_INTERFACES
复制代码
1. 使用方法
1.1 定义一个插件接口
- class MyPluginInterface
- {
- public:
- virtual void DoSomething() = 0;
- }
- QT_BEGIN_NAMESPACE
- #define MyPluginInterface_IId "MyPluginInterface_id"
- Q_DECLARE_INTERFACE(MyPluginInterface, MyPluginInterface_IId);
- QT_END_NAMESPACE
复制代码
这里可以定义想要的接口函数,定义完通过Q_DECLARE_INTERFACE来申明一下接口,当然这里需要自定义个插件接口id。
1.2 现实插件接口
- class MyPluginImplement1: public QObject, public MyPluginInterface
- {
- Q_OBJECT
- Q_PLUGIN_METADATA(IID MyPluginInterface_Id)
- Q_INTERFACES(MyPluginInterface)
- public:
- MyPluginImplement1();
- virtual void DoSomething() override;
- }
- class MyPluginImplement2: public QObject, public MyPluginInterface
- {
- Q_OBJECT
- Q_PLUGIN_METADATA(IID MyPluginInterface_Id)
- Q_INTERFACES(MyPluginInterface)
- public:
- MyPluginImplement2();
- virtual void DoSomething() override;
- }
复制代码
接下来就是在自己的插件DLL中实现具体的插件实现类,如上图MyPluginImplement1, 需要继承QObject和MyPluginInterface,继承QObject主要是为了使用QT的元对象数据,当然插件系统是基于元对象实现的。然后实现宏Q_PLUGIN_METADATA 和宏Q_INTERFACES, 来申明这个插件实现是基于哪个插件接口的。
1.3 加载使用插件
- class PluginsManager
- {
- bool loadPlugin(const QString& filepath)
- {
- for (int i = 0 ; i < m_DllNameVec.size(); i++)
- {
- if (m_DllNameVec[i] == filepath)
- {
- return false;
- }
- }
- QPluginLoader *loader = new QPluginLoader(filepath);
- if (loader->load())
- {
- MyPluginInterface *plugin = qobject_cast<MyPluginInterface *>(loader->instance());
- if (plugin) {
- /// do everything what you want to do
- }
- }
- }
- }
复制代码
这样基于QT插件机制就创建了两个插件,然后就通过PluginsManager直接load对应的dll地址就可以获取到该插件直接使用了,插件DLL架构图如下图,是不是很简单? 对就是这么简单。
2 实现原理
接下来我们详细看一下Qt插件的实现原理,首先看一下三个宏对应具体实现:
2.1 Q_DECLARE_INTERFACE
- #define Q_DECLARE_INTERFACE(IFace, IId) \
- template <> inline const char *qobject_interface_iid<IFace *>() \
- { return IId; } \
- template <> inline IFace *qobject_cast<IFace *>(QObject *object) \
- { return reinterpret_cast<IFace *>((object ? object->qt_metacast(IId) : nullptr)); } \
- template <> inline IFace *qobject_cast<IFace *>(const QObject *object) \
- { return reinterpret_cast<IFace *>((object ? const_cast<QObject *>(object)->qt_metacast(IId) : nullptr)); }
复制代码
该宏定义在qobject.h中,主要就是定义了3个模板内联函数,展开如上, 通过QObject对象的meta_data中的id获取插件对象。
2.2 Q_INTERFACES
- #define Q_INTERFACES(x) QT_ANNOTATE_CLASS(qt_interfaces, x)
- # define QT_ANNOTATE_CLASS(type, ...)
复制代码
这里可以看到只是一个空的定义,在源码中没有实际意义。主要用于MOC的输入,MOC会为Q_INTERFACES动态生成一些代码,确保qobject_cast()能正确进行QObject*到接口指针的转换,如下所示(MyPluginImplement1):
- void *MyPluginImplement1::qt_metacast(const char *_clname)
- {
- if (!_clname) return nullptr;
- if (!strcmp(_clname, qt_meta_stringdata__MyPluginImplement1.stringdata0))
- return static_cast<void*>(this);
- if (!strcmp(_clname, "MyPluginImplement1"))
- return static_cast< MyPluginInterface*>(this);
- if (!strcmp(_clname, "MyPluginInterface_id"))
- return static_cast< MyPluginInterface*>(this);
- return QObject::qt_metacast(_clname);
- }
复制代码
2.3 Q_PLUGIN_METADATA
- #define Q_PLUGIN_METADATA(x) QT_ANNOTATE_CLASS(qt_plugin_metadata, x)
- # define QT_ANNOTATE_CLASS(type, ...)
复制代码
与Q_INTERFACES类似,在代码层面也是空的,也用于MOC的输入,让MOC来动态生成一些代码。实际上,Q_PLUGIN_METADATA让MOC生成导出函数qt_plugin_instance(),供QPluginLoader()调用,创建接口实例,返回QObject的指针,如下所示(MyPluginImplement1):
- QT_PLUGIN_METADATA_SECTION
- static constexpr unsigned char qt_pluginMetaData[] = {
- 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',
- // metadata version, Qt version, architectural requirements
- 0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(),
- 0xbf,
- // "IID"
- 0x02, 0x78, 'M', 'y', 'P', 'l', 'u',
- 'g', 'i', 'n', 'I', 'n', 't', 'e', 'r',
- 'f', 'a', 'c', 'e', '_', 'i', 'd',
- // "className"
- 0x03, 0x73, 'M', 'y', 'P', 'l', 'u',
- 'g', 'i', 'n', 'I', 'm', 'p', 'l', 'e',
- 'm', 'e', 'n', 't', '1',
- 0xff,
- };
- QT_MOC_EXPORT_PLUGIN(MyPluginImplement1, MyPluginImplement1)
复制代码
看到这里,想必大家已经对Qt插件有个比较深的了解,但肯定有人会有疑问? 插件类MyPluginImplement1实例对象是什么时候创建的呢? 为什么通过PluginsManager中QPluginLoader就可以直接获取到呢?
对于该问题,笔者仔细搜索一下发现如下:
- # define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME) \
- Q_EXTERN_C Q_DECL_EXPORT \
- const char *qt_plugin_query_metadata() \
- { return reinterpret_cast<const char *>(qt_pluginMetaData); } \
- Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \
- Q_PLUGIN_INSTANCE(PLUGINCLASS)
复制代码
以上就是moc文件中,Q_PLUGIN_METADATA 宏自动生成的数据中QT_MOC_EXPORT_PLUGIN宏展开的实现,想必有些不太熟的人看起来还是有点别扭,不要急,已经帮你全部展开,如下:
- # define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME) \
- extern "C" __declspec(dllexport) \
- const char *qt_plugin_query_metadata() \
- { return reinterpret_cast<const char *>(qt_pluginMetaData); } \
- extern "C" __declspec(dllexport) QT_NAMESPACE::QObject *qt_plugin_instance() \
- { \
- static QT_NAMESPACE::QPointer<QT_NAMESPACE::QObject> _instance; \
- if (!_instance) { \
- QT_PLUGIN_RESOURCE_INIT \
- _instance = new IMPLEMENTATION; \
- } \
- return _instance; \
- }
复制代码
看到这里,你是不是想说原来如此。
以上就是Qt插件的使用方法的原理了,是不是很简单。 |