今天简单地了解了一下QT的插件机制,其实也没有什么可多说的,理论上说到底无非就是库的加载和函数地址的查找。但是QT对于插件机制的支持是建立在它独有的元对象系统基础之上,自然也有自己的一套插件实现规则,或者说是语法。
一般来讲,照着QT给的文档和DEMO,很容易就能实现一个插件,这里面最主要的就是三个宏:
Q_DECLARE_INTERFACE
Q_PLUGIN_METADATA
Q_INTERFACES
那么,这三个宏到底在整个插件机制中到底起到一个以什么作用呢?
Q_DECLARE_INTERFACE宏好理解,就是定义了接口ID查找函数和几个QObject到接口的转换函数:
#if !defined(Q_MOC_RUN) && !defined(Q_CLANG_QDOC)
# 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)); }
#endif // Q_MOC_RUN
Q_PLUGIN_METADATA和Q_INTERFACES定义如下:
#define Q_PLUGIN_METADATA(x) QT_ANNOTATE_CLASS(qt_plugin_metadata, x)
#define Q_INTERFACES(x) QT_ANNOTATE_CLASS(qt_interfaces, x)
QT_ANNOTATE_CLASS啥也没定义:
#ifndef QT_ANNOTATE_CLASS
# ifndef Q_COMPILER_VARIADIC_MACROS
# define QT_ANNOTATE_CLASS(type, x)
# else
# define QT_ANNOTATE_CLASS(type, ...)
# endif
#endif
所以,Q_PLUGIN_METADATA和Q_INTERFACES应该是Moc.exe自己做了解释处理. 我在本地的一个插件的头文件的Moc文件中发现了一下内容:
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, 0x77, 'o', 'r', 'g', '.', 'k', 'o',
'm', 'm', 'a', 'n', 'd', 'e', 'r', '.',
'I', 'n', 't', 'e', 'r', 'f', 'a', 'c',
'e',
// "className"
0x03, 0x70, 'M', 'o', 'u', 's', 'e', 'C',
'l', 'i', 'c', 'k', 'P', 'l', 'u', 'g',
'i', 'n',
0xff,
};
QT_MOC_EXPORT_PLUGIN(MouseClickPlugin, MouseClickPlugin)
QT_WARNING_POP
QT_END_MOC_NAMESPACE
这里重要的是倒数第三行的那个宏:QT_MOC_EXPORT_PLUGIN,看一下定义:
#if defined(QT_STATICPLUGIN)
# define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME) \
static QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance_##PLUGINCLASSNAME() \
Q_PLUGIN_INSTANCE(PLUGINCLASS) \
static const char *qt_plugin_query_metadata_##PLUGINCLASSNAME() { return reinterpret_cast<const char *>(qt_pluginMetaData); } \
const QT_PREPEND_NAMESPACE(QStaticPlugin) qt_static_plugin_##PLUGINCLASSNAME() { \
QT_PREPEND_NAMESPACE(QStaticPlugin) plugin = { qt_plugin_instance_##PLUGINCLASSNAME, qt_plugin_query_metadata_##PLUGINCLASSNAME}; \
return plugin; \
}
#else
# 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)
看#else部分,定义了两个导出函数,qt_plugin_query_metadata()和qt_plugin_instance(),调试QPluginLoader的源码,发现qt_plugin_instance()正是QPluginLoader实例化插件对象实例时调用的函数.
所以,Q_PLUGIN_METADATA的作用就是生成导出函数qt_plugin_instance()供QPluginLoader查找并调用生成接口实例。当然,还生成了返回插件元数据的函数。
再说Q_INTERFACES。 从动态库的层面来讲,有了Q_PLUGIN_METADATA,QPluginLoader就能加载插件(LoadLibrary),并正确查找(GetProcessAddress)并调用qt_plugin_instance()生成实例供外界调用了,为什么还要加上Q_INSTANCES?
试着对比了一下有Q_INSTANCES和没有Q_INSTANCES生成出来的moc文件,发现了以下差异:
没错,有Q_INSTANCES的moc文件中,qt_metacast()多了一条判断语句,如果转换目标对象的名字是我们目标接口的IID时,则进行相应的转换操作,并返回接口指针,否则将返回空。
这条语句到底有什么用?
注意前面的qt_plugin_instance()定义,返回的是一个QObject*. 我们通过QPluginLoader获得一格QObject之后,还需要判断这个QObject有没有实例化目标接口? 一般会调用qobject_cast()进行转换,而qobject_cast()最终就是调用这里的qt_metacast()。
---------总结---------
Q_PLUGIN_METADATA 让moc生成导出函数qt_plugin_instance(),供QPluginLoader()调用,创建接口实例,不过返回的是一个QObject*.
Q_INTERFACES 让qobject_cast()能正确进行QObject*到接口指针的转换,借此,我们可以判断插件合法性。