Qt Manual 已经专门介绍了Deploying Plugins 的问题。半年前Qt 插件学习(一) 也简单整理了一点路径相关的问题。
可是,一直以来没理清:图片插件、编解码插件、数据库插件... 到底是如何被加载的?
走马观花
如果我们需要打开或保存一个jpg格式的图片,那么需要加载jpg的插件。程序去何处找插件:
表面的答案:
$QTDIR/plugins/imageformats/ you_app_path/imageformats/
注:如果你只是想让插件能工作,对其他不感兴趣,直接在你的应用程序所在目录下创建一个 imageformats 目录,把插件放进去就行了。其他插件类推,分别创建相应的子目录即可。
真实答案
QStringList QCoreApplication::libraryPaths()
对于图片插件,程序遍历这个字符串列表,将每一个字符串分别和/imageformats 连接后构成新的路径,然后依次尝试加载这些路径下的动态库。
看点代码(有删节)
void QFactoryLoader::update() { QStringList paths = QCoreApplication::libraryPaths(); for (int i = 0; i < paths.count(); ++i) { const QString &pluginDir = paths.at(i); QString path = pluginDir + d->suffix; QStringList plugins = QDir(path).entryList(QDir::Files); QLibraryPrivate *library = 0; for (int j = 0; j < plugins.count(); ++j) { QString fileName = QDir::cleanPath(path + QLatin1Char('/') + plugins.at(j)); library = QLibraryPrivate::findOrCreate(QFileInfo(fileName).canonicalFilePath()); } } ...
对于图片插件来说,d->suffix 就是/imageformats ,这是通过一个返回值为QFactoryLoader* 的静态 loader() 函数来实现的:
Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, (QImageIOHandlerFactoryInterface_iid, QLatin1String("/imageformats")))
感觉不对?
前面似乎不对啊?不光和我们的表面答案对不上,而且Manual中或其他人的文章都提到还有很多东西,比如:
-
QLibraryInfo::location(QLibraryInfo::PluginsPath )
- :/qt/etc/qt.conf
- QT_PLUGIN_PATH
- ...
- QCoreApplication::addLibraryPath()
- QCoreApplication::setLibraryPaths()
除了后面两个直接相关外,其他合libraryPaths似乎没什么联系。你怎么能说只 和 QCoreApplication::libraryPaths() 有关呢??!!
尝试用事实说话...
QCoreApplication::libraryPaths()
- 创建一个空的QStringList
-
将 QLibraryInfo::location(QLibraryInfo::PluginsPath) 路径加入
-
确保将应用程序本身所在路径QCoreApplication::applicationFilePath() 加入
-
将 QT_PLUGIN_PATH 环境变量指定的路径依次加入
源码:
QStringList QCoreApplication::libraryPaths() { if (!coreappdata()->app_libpaths) { QStringList *app_libpaths = coreappdata()->app_libpaths = new QStringList; QString installPathPlugins = QLibraryInfo::location(QLibraryInfo::PluginsPath); if (QFile::exists(installPathPlugins)) { // Make sure we convert from backslashes to slashes. installPathPlugins = QDir(installPathPlugins).canonicalPath(); if (!app_libpaths->contains(installPathPlugins)) app_libpaths->append(installPathPlugins); } // If QCoreApplication is not yet instantiated, // make sure we add the application path when we construct the QCoreApplication if (self) self->d_func()->appendApplicationPathToLibraryPaths(); const QByteArray libPathEnv = qgetenv("QT_PLUGIN_PATH"); if (!libPathEnv.isEmpty()) { #if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) QLatin1Char pathSep(';'); #else QLatin1Char pathSep(':'); #endif QStringList paths = QString::fromLatin1(libPathEnv).split(pathSep, QString::SkipEmptyParts); for (QStringList::const_iterator it = paths.constBegin(); it != paths.constEnd(); ++it) { QString canonicalPath = QDir(*it).canonicalPath(); if (!canonicalPath.isEmpty() && !app_libpaths->contains(canonicalPath)) { app_libpaths->append(canonicalPath); } } } ...
现在似乎舒服多了,可是QLibraryInfo::location(QLibraryInfo::PluginsPath) 又是怎么来的呢?
QLibraryInfo::location(QLibraryInfo::PluginsPath)
它受两方面影响:
- 如果有符合条件的 qt.conf 文件,将使用该文件的内容
- 如果没有这样的 qt.conf 文件,将使用编译Qt时写死的内容
先看后者:
configure
编译Qt的第一步是configure,它将在 src/corelib/global目录下生成 qconfig.h 和 qconfig.cpp 两个文件。
打开 qconfig.cpp 文件(可以看到类似下面的内容):
static const char qt_configure_installation [11 + 12] = "qt_instdate=2011-06-08"; static const char qt_configure_prefix_path_str [512 + 12] = "qt_prfxpath=E://Qt5//qtbase-build"; static const char qt_configure_binaries_path_str [512 + 12] = "qt_binspath=E://Qt5//qtbase-build//bin"; static const char qt_configure_plugins_path_str [512 + 12] = "qt_plugpath=E://Qt5//qtbase-build//plugins"; #define QT_CONFIGURE_BINARIES_PATH qt_configure_binaries_path_str + 12; #define QT_CONFIGURE_PLUGINS_PATH qt_configure_plugins_path_str + 12;
注意一点:宏 QT_CONFIGURE_PLUGINS_PATH 指向 "qt_plugpath=E://Qt5//qtbase-build//plugins";
qt.conf
-
先看资源文件中有无 :/qt/etc/qt.conf
- 不存在则检查应用程序所在路径下有无 qt.conf 文件
- 找到则返回相应的QSettings,否则返回0
QSettings *QLibraryInfoPrivate::findConfiguration() { QString qtconfig = QLatin1String(":/qt/etc/qt.conf"); if (!QFile::exists(qtconfig) && QCoreApplication::instance()) { { QDir pwd(QCoreApplication::applicationDirPath()); qtconfig = pwd.filePath(QLatin1String("qt.conf")); } } if (QFile::exists(qtconfig)) return new QSettings(qtconfig, QSettings::IniFormat); return 0; }
合二为一
直接贴点代码片段,不解释
QString QLibraryInfo::location(LibraryLocation loc) { QString ret; if(!QLibraryInfoPrivate::configuration()) { const char *path = 0; switch (loc) { ... case PluginsPath: path = QT_CONFIGURE_PLUGINS_PATH; break; ... default: break; } if (path) ret = QString::fromLocal8Bit(path); } else { QString key; QString defaultValue; switch(loc) { case PluginsPath: key = QLatin1String("Plugins"); defaultValue = QLatin1String("plugins"); break; ...
cache
为了加快插件的加载和校验,会用QSettings保存一些插件的信息。
看个代码片段:
QFactoryLoader::update() { QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); QString regkey = QString::fromLatin1("Qt Factory Cache %1.%2/%3:/%4") .arg((QT_VERSION & 0xff0000) >> 16) .arg((QT_VERSION & 0xff00) >> 8) .arg(QLatin1String(d->iid)) .arg(fileName); QStringList reg; reg << library->lastModified; reg += keys; settings.setValue(regkey, reg);
以及
bool QLibraryPrivate::isPlugin(QSettings *settings) { ... QString regkey = QString::fromLatin1("Qt Plugin Cache %1.%2.%3/%4") .arg((QT_VERSION & 0xff0000) >> 16) .arg((QT_VERSION & 0xff00) >> 8) .arg(QLIBRARY_AS_DEBUG ? QLatin1String("debug") : QLatin1String("false")) .arg(fileName); ...
看看Windows下的注册表内容:
HKCU/Software/Trolltech/OrganizationDefaults/Qt Factory Cache 4.7/com.trolltech.Qt.QImageIOHandlerFactoryInterface:/F:/Qt4/plugins/imageformats/qtjpeg4.dll = 2010-09-29T14:40:30 jpeg jpg
linux下 ~/.config/Trolltech.conf
[Qt Factory Cache 4.7] com.trolltech.Qt.QImageIOHandlerFactoryInterface:/home/Qt4/plugins/imageformats/qtjpeg4.dll = 2011-04-22T17:21:23 jpeg jpg