<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
JSON 与Qt插件的元信息 MetaData
Qt插件的源码中,基本都能见到一个 xxx.json 的文件,这个文件中通常只包含一句:
{
"Keys": [ "yyy" ]
}
我们可以猜到这个文件中的"Keys"应该是指定了与插件相关的关键字。那这个 .json 文件到底是如何起作用的?
先来认识一下 JSON .
JSON是一种存储结构化数据的格式,它有6中基本数据类型,分别是:
bool 布尔型,取值可以是 true 或 false
double 数字类型
string 字符串类型
array 数组类型
object 对象类型
null 空类型
具体可参见 Qt Assistant 中关于"JSON Support in Qt "的介绍。
A simple JSON document encoding a person, his/her age, address and phone numbers could look like:
- {
- "FirstName": "John", # FirstName是变量(字段)的名称;John是变量的值
- "LastName": "Doe",
- "Age": 43,
- "Address": {
- "Street": "Downing Street 10",
- "City": "London",
- "Country": "Great Britain"
- },
- "Phone numbers": [
- "+44 1234567",
- "+44 2345678"
- ]
- }
值得一提的是,数组类型的字段在.json文件中赋值时应该用方括号 '[' 和 ']' 括起来,对象类型的字段在赋值时
应用花括号 '{' 和 '}' 括起来,普通类型的数据则不需要括。每一个 .json 文件描述了一个 JSON对象,而一个JSON
对象中的对象类型字段,又可以看做是一个子JSON对象(JSON对象的嵌套)。
再回过头来看看插件源码中的 xxx.json 文件,可以发现其中的变量 Keys 其实是个数组变量(因为它赋值时用方括号
括住了),只不过这个数组中通常只有一个元素而已,即一个插件一般只有一个关键字,当然也可以为一个插件设置多个
关键字。
.json在Qt插件中主要用于存储Qt插件的元信息(metaData),在Qt中,有一个专门的类 QJsonObject 来描述一个JSON。
QLibraryPrivate::metaData() 返回的是一个QJsonObject类的指针,通过这个指针可以访问该 QLibraryPrivate 对象对应的库的元信息;
QFactoryLoader::metaData() 返回的是一个 QList<QJsonObject> 列表,这个列表中顺序存放了与该 QFactoryLoader 对象相关的所有库
的元信息(动态库的元信息在前,静态库的元信息在后)。
另外值得注意的是,QLibraryPrivate::metaData() 返回的QJsonObject对象中一般都有一个 MetaData 字段,这个字段是对象类型的数据,
他可以看做是库的元信息的一个子JSON对象,而且它对应的就是 .json 文件中的内容。(.json文件中的内容只是一个库的元信息的一部分)
每个QFactoryLoader对象都有一个 d->iid 成员 (d是与QFactoryLoader对象关联的QFactoryLoaderPrivate实例),可用于描述插件的种类,
每一类插件都有一个独立的 IID 值, 比如平台输入法类插件的IDD都应该是 org.qt-project.Qt.QPlatformInputContextFactoryInterface,
平台类插件的IDD都应是 org.qt-project.Qt.QPA.QPlatformIntegrationFactoryInterface.5.2 等。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
QLibraryPrivate的metaData(库的元信息)的产生过程
在编写Qt插件时需要用到 Q_PLUGIN_METADATA 这个宏来设置一个插件的元信息,但在Qt的源码中,发现这个宏是空的,所以不用说,这个
宏是被MOC解析的而不是C++编译器。那就看看MOC会怎么对付这个宏吧。找了个很简单的文件,用moc处理一下,看看都生成了什么。
main.cpp文件内容如下,其中使用了Q_PLUGIN_METADATA宏
- #include <qpa/qplatforminputcontextplugin_p.h>
- #include <QtCore/QStringList>
- #include "QtMinimalInputMethodFrame.h"
- QT_BEGIN_NAMESPACE
- class QtMinimalInputMethodFramePlugin : public QPlatformInputContextPlugin
- {
- Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPlatformInputContextFactoryInterface" FILE "qtminimal.json")
-
- public:
- QtMinimalInputMethodFrame *create(const QString &, const QStringList &);
- };
- QtMinimalInputMethodFrame *QtMinimalInputMethodFramePlugin::create(const QString &system, const QStringList ¶mList)
- {
- Q_UNUSED(paramList);
- if (system.compare(system, QStringLiteral("qtminimal"), Qt::CaseInsensitive) == 0)
- return new QtMinimalInputMethodFrame;
- return 0;
- }
- QT_END_NAMESPACE
- #include "main.moc"
qtminimal.json文件内容如下:
- {
- "Keys": [ "qtminimal" ]
- }
使用 "moc main.cpp moc_main.cpp" ,生成moc_main.cpp文件,打开,发现里面有一部分代码如下:
- ...
- ...
-
- static const unsigned char qt_pluginMetaData[] = {
- 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', ' ',
- 0x71, 0x62, 0x6a, 0x73, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0xec, 0x00, 0x00, 0x00, 0x1b, 0x03, 0x00, 0x00,
- 0x03, 0x00, 0x49, 0x49, 0x44, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x6f, 0x72, 0x67, 0x2e, 0x71, 0x74,
- 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
- 0x2e, 0x51, 0x74, 0x2e, 0x51, 0x50, 0x6c, 0x61,
- 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6e, 0x70,
- 0x75, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
- 0x74, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79,
- 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63,
- 0x65, 0x00, 0x00, 0x00, 0x9b, 0x0c, 0x00, 0x00,
- 0x09, 0x00, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4e,
- 0x61, 0x6d, 0x65, 0x00, 0x1f, 0x00, 0x51, 0x74,
- 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x49,
- 0x6e, 0x70, 0x75, 0x74, 0x4d, 0x65, 0x74, 0x68,
- 0x6f, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x50,
- 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x00, 0x00, 0x00,
- 0x5a, 0x60, 0xa0, 0x00, 0x07, 0x00, 0x76, 0x65,
- 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x65,
- 0x62, 0x75, 0x67, 0x00, 0x95, 0x16, 0x00, 0x00,
- 0x08, 0x00, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61,
- 0x74, 0x61, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x14, 0x03, 0x00, 0x00, 0x04, 0x00, 0x4b, 0x65,
- 0x79, 0x73, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x71, 0x74, 0x6d, 0x69, 0x6e, 0x69,
- 0x6d, 0x61, 0x6c, 0x00, 0x8b, 0x01, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0xa4, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
- 0x98, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00
- };
- ...
- ...
-
-
- QT_MOC_EXPORT_PLUGIN(QtMinimalInputMethodFramePlugin, QtMinimalInputMethodFramePlugin)
-
- ...
- ...
上面的代码中生成了一个名为 qt_pluginMetaData 的数组,看其名字就能猜出来这个数组是与插件的元信息有关的,
将该数组用字符显示,结果为:
- QTMETADATA qbjs^A^@^@^@^@^A^@^@^K^@^@^@ì^@^@^@^[^C^@^@^C^@
- IID^@^@^@7^@org.qt-project.Qt.QPlatformInputContextFactoryInterface
- ^@^@^@<9b>^L^@^@^@
- className^@^_^@QtMinimalInputMethodFramePlugin
- ^@^@^@Z` ^@^G^@
- version^@^@^@^Q^@^@^@^E^@
- debug^@<95>^V^@^@^H^@
- MetaData^@^@8^@^@^@^C^@^@^@4^@^@^@^T^C^@^@^D^@
- Keys^@^@^\^@^@^@^B^@^@^@^X^@^@^@^@qtminimal
- ^@<8b>^A^@^@^L^@^@^@^L^@^@^@¤^@^@^@T^@^@^@<98>^@^@^@<88>^@^@^@
可以看到其中已经包含了IDD、Keys等信息,因此可以确定与插件相关的元信息就是存在这个数组中。
上面给出的代码最后,使用了QT_MOC_EXPORT_PLUGIN宏,它的作用是定义两个函数,qt_plugin_instance() 和
qt_plugin_query_metadata(),前者用于返回插件类(QtMinimalInputMethodFramePlugin)的一个实例,后者用于
返回数组qt_pluginMetaData的指针。因此,当这个库被加载后,通过库中的qt_plugin_query_metadata()函数就可以获得
库的元信息数组(二进制数据)。
- #define Q_PLUGIN_INSTANCE(IMPLEMENTATION) \ // 这个宏用于返回一个类名是IMPLEMENTATION的类的静态实例
- { \
- static QT_PREPEND_NAMESPACE(QPointer)<QT_PREPEND_NAMESPACE(QObject)> _instance; \
- if (!_instance) \
- _instance = new IMPLEMENTATION; \
- return _instance; \
- }
-
- #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 (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 (const char *)qt_pluginMetaData; } \
- Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \ 这个函数返回插件类PLUGINCLASS的一个静态实例
- Q_PLUGIN_INSTANCE(PLUGINCLASS)
-
- #endif
我们虽然可以通过qt_plugin_query_metadata()函数获得库的元信息数组(二进制数据),但是还需要把这些
二进制数据转换成 QJsonObject 对象,这样才方便在程序中对其各个字段进行访问。QLibraryPrivate类有个
静态成员函数fromRawMetaData,可以从二进制数据中构造一个 QJsonDocument 对象,
而QJsonDocument对象的 object() 方法就能返回所需的 QJsonObject 对象(库的元信息)。
通过这些可以猜测,QLibraryPrivate类的 metaData 成员是这样被设置的:
加载一个库文件后,将库中的qt_plugin_query_metadata()这个符号resolve出来,并通过它得到数组 qt_pluginMetaData 的指针,
然后调用QLibraryPrivate类的fromRawMetaData方法讲二进制存储的元信息转换到一个QJsonObject 对象中,最后将这个对象赋值
给QLibraryPrivate类的metaData成员。
现在问题是, QLibraryPrivate的metaData是在哪里,被谁设置的?
当第一次使用 QLibraryPrivate类的 isPlugin() 方法调查一个库是否是插件时,这个方法内部会调用 QLibraryPrivate::updatePluginState() 。
- bool QLibraryPrivate::isPlugin()
- {
- if (pluginState == MightBeAPlugin)
- updatePluginState();
-
- return pluginState == IsAPlugin;
- }
而QLibraryPrivate::updatePluginState()函数中,需要读取QLibraryPrivate类的 metaData 信息来确认一个库是不是插件,而 metaData 也就是在这时
被设置的。从这个函数中还可以看出,只有Qt插件才有 metaData 元信息, 普通的库是没有的(这一点我上不能打保票,不过从下面这个函数的代码上看,应该
是这样的)。
- void QLibraryPrivate::updatePluginState()
- {
- errorString.clear();
- if (pluginState != MightBeAPlugin)
- return;
-
- bool success = false;
-
- #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
- if (fileName.endsWith(QLatin1String(".debug"))) {
-
-
-
-
-
-
- errorString = QLibrary::tr("The shared library was not found.");
- pluginState = IsNotAPlugin;
- return;
- }
- #endif
-
-
- if (!pHnd) {
-
-
-
-
- success = findPatternUnloaded(fileName, this);
- } else {
-
-
-
-
-
-
- QtPluginQueryVerificationDataFunction getMetaData = NULL;
- getMetaData = (QtPluginQueryVerificationDataFunction) resolve("qt_plugin_query_metadata");
-
- success = qt_get_metadata(getMetaData, this);
- }
-
- if (!success) {
-
-
- if (errorString.isEmpty()){
- if (fileName.isEmpty())
- errorString = QLibrary::tr("The shared library was not found.");
- else
- errorString = QLibrary::tr("The file '%1' is not a valid Qt plugin.").arg(fileName);
- }
- pluginState = IsNotAPlugin;
- return;
- }
-
- pluginState = IsNotAPlugin;
-
-
- uint qt_version = (uint)metaData.value(QLatin1String("version")).toDouble();
- bool debug = metaData.value(QLatin1String("debug")).toBool();
- if ((qt_version & 0x00ff00) > (QT_VERSION & 0x00ff00) || (qt_version & 0xff0000) != (QT_VERSION & 0xff0000)) {
- if (qt_debug_component()) {
- qWarning("In %s:\n"
- " Plugin uses incompatible Qt library (%d.%d.%d) [%s]",
- (const char*) QFile::encodeName(fileName),
- (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff,
- debug ? "debug" : "release");
- }
- errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]")
- .arg(fileName)
- .arg((qt_version&0xff0000) >> 16)
- .arg((qt_version&0xff00) >> 8)
- .arg(qt_version&0xff)
- .arg(debug ? QLatin1String("debug") : QLatin1String("release"));
- #ifndef QT_NO_DEBUG_PLUGIN_CHECK
- } else if(debug != QLIBRARY_AS_DEBUG) {
-
- errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library."
- " (Cannot mix debug and release libraries.)").arg(fileName);
- #endif
- } else {
- pluginState = IsAPlugin;
- }
- }