QT插件机制中宏Q_PLUGIN_METADATA和Q_INTERFACES的作用

今天简单地了解了一下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*到接口指针的转换,借此,我们可以判断插件合法性。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值