HOME> 梅西世界杯队友> 如何获取文件(夹)图标 without Cache

如何获取文件(夹)图标 without Cache

前情提要(迫真)书接上回,我们已经介绍了如何设置文件夹图标,想必大家都意犹未尽 那么根据有Set必有Get定理,本节我们来讲讲图标的获取...

前情提要(迫真)书接上回,我们已经介绍了如何设置文件夹图标,想必大家都意犹未尽

那么根据有Set必有Get定理,本节我们来讲讲图标的获取(on Windows)

众所周知,Qt是一个Convenient的C++库

So,长话短说,两行代码秒了

12QFileIconProvider iconPro;QIcon icon = iconPro.icon(dirPath);

好的,请大家有序退场👋

新吃席观众还在懵逼,老吃席观众已经开始排排坐了:都知道笔者出手必有大坑

缓存(Cache)大家看看这是什么☝,老朋友了

缓存是个好东西啊,空间换时间,现代CPU都上到三级缓存了

可是没有什么是永恒的,是非好坏、真假对错,甚至是这句话本身 —— MrBeanC

和文件系统打交道,时效性确实不太能保证,无论是HDD亦或是SSD,在Memory面前就是弟弟

那么Qt给QFileIconProvider上缓存也不难理解了

文件夹图标缓存没错,在程序生命周期内,使用QFileIconProvider::icon获取文件夹图标,会在全局范围内进行缓存

无论后续文件夹图标是否改变,都只会返回第一次缓存的结果

基操分割线到目前为止,都是基操(基本操作)

但是捏,对于缓存这件事,QFileIconProvider的文档里只字未提,甚至查不到Cache这个词

而且哈,这个类,也没有提供任何方法(method)来清除缓存或选择不缓存

No, Nothing

这或许就是无形的手吧.🤌

😠😾🤬💬🗯︎

So What可能(不可能)有人觉得这没什么

万一,我是说万一哈,有这么一款软件,它的功能是修改文件夹图标

// 源码级广告植入:Dr.Folder

修改完成后,软件会重新获取并展示该图标(或者是不重启的情况下刷新)

然后假如获取的还是旧图标,你说这不完犊子了😑

返璞归真那没办法了,你仁我义(的反义词)

还是看看远方的Windows API吧,家人们

12345678910111213141516QIcon getFileIcon(QString filePath) { filePath = QDir::toNativeSeparators(filePath); // IMPORTANT: 否则会找不到文件 CoInitialize(NULL); // important for SHGetFileInfo,否则失败 SHFILEINFO sfi; memset(&sfi, 0, sizeof(SHFILEINFO)); QIcon icon; if (SHGetFileInfo(filePath.toStdWString().c_str(), 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON)) { icon = QtWin::fromHICON(sfi.hIcon); // 貌似Qt6删除了这个模块 哭 DestroyIcon(sfi.hIcon); } CoUninitialize(); return icon;}

Okay,完美,兄弟们,可以获取到原汁原味的图标了

小丑好的,缓存是没缓存了,但是卡炸了

这波啊,这波是时间换时效

系统级缓存(Attention)注意,除了Qt内部的图标缓存外,Windows文件系统也会对进行一定的缓存

参考我的上一篇文章:更改文件夹图标 & 使其立即生效 - MrBeanC-Blog (cls.ink)

这么说吧,在一定时间内连续调用SHGetFileInfo,其速度会越来越快

但是第一次的调用时长可达数秒(最多)或数百毫秒(常见),这是不可接受的

姜还是老的辣但是QFileIconProvider人家就不卡,这可和什么内部缓存无关啊

人家第一次获取就不卡的

而且比SHGetFileInfo快了一个数量级(WTF?)

这不科学吧,用了什么黑科技

QFileIconProvider源码剖析

都把自己的心肺肠子翻出来,晒一晒,洗一洗,拾掇拾掇 —— 康熙

12345678910QIcon QFileIconProviderPrivate::getIcon(const QFileInfo &fi) const { return QGuiApplicationPrivate::platformTheme()->fileIcon(fi, toThemeIconOptions(options));}QIcon QFileIconProvider::icon(const QFileInfo &info) const { ... QIcon retIcon = d->getIcon(info); if (!retIcon.isNull()) return retIcon; ...}

我们可以看到实现细节隐藏在了platformTheme()->fileIcon()

这也是Qt实现跨平台的秘诀:每个平台写一份代码

这里我们来重点关注一下Windows的实现(qtbase/src/plugins/platforms/windows/qwindowstheme.cpp)

123QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const { return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));}

看到这里不禁感叹:妙哉妙哉

原来我们通过QFileIconProvider::icon获取的图标里并没有真正的图像数据,而是封装了一个Engine(用于获取图标的中介)

12345678910111213class QWindowsFileIconEngine : public QAbstractFileIconEngine{public: explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) : QAbstractFileIconEngine(info, opts) {} QList availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) const override { return QWindowsTheme::instance()->availableFileIconSizes(); }protected: QString cacheKey() const override; QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;};

怪不得.icon()获取图标那么快呢,敢情是没有真数据啊

好的,盯帧一下我们就能发现filePixmap这个函数才是真正的重点

QWindowsFileIconEngine::filePixmap文件夹缓存1234567891011121314151617181920bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();if (cacheableDirIcon) { QMutexLocker locker(&mx); int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon : **dirIconEntryCache.object(filePath); if (iIcon) { QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize), &pixmap); if (pixmap.isNull()) // Let's keep both caches in sync dirIconEntryCache.remove(filePath); else return pixmap; }}......if (cacheableDirIcon) { QMutexLocker locker(&mx); QPixmapCache::insert(key, pixmap); dirIconEntryCache.insert(filePath, FakePointer::create(info.iIcon));}

从这里我们可以发现:

只对文件夹(Directory)进行了缓存(cacheableDirIcon),没有缓存文件(file); // Why?

使用了QPixmapCache进行缓存,而且是::insert静态方法

静态方法说明是全局状态

[现在可以公开的情报]

!!因此,可以使用QPixmapCache::clear()来清除QFileIconProvider的缓存

😑还得是源码

深入虎穴Okay,都到这里了,让我们看看Qt到底用了什么方法来获取图标吧

filePixmap()中调用了以下函数:

12345678910111213141516171819202122static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes, SHFILEINFO *info, UINT flags, qint64 timeOutMSecs = 5000){ static QShGetFileInfoThread *getFileInfoThread = nullptr; if (!getFileInfoThread) { getFileInfoThread = new QShGetFileInfoThread; getFileInfoThread->start(); //启动线程,调用run()函数 } bool result = false; QShGetFileInfoParams params(fileName, attributes, info, flags, &result); if (!getFileInfoThread->runWithParams(¶ms, timeOutMSecs)) { // Cancel and reset getFileInfoThread. It'll // be reinitialized the next time we get called. getFileInfoThread->cancel(); getFileInfoThread = nullptr; qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName; return false; } return result;}

首先这个命名非常有意思,为什么是Background呢,难道是多线程?(第一行代码就结束了比赛

好吧,关注点难道不应该是shGetFileInfoBackground把SHGetFileInfoAPI写脸上了吗

这里有一点比较疑惑:为什么Qt在这里要使用多线程呢?

首先,这是一个filePixmap()是一个同步函数,也没法异步返回

其次,在主线程中调用了m_doneCondition.wait进行等待,等待被子线程唤醒或超时(5s),意味着还是会阻塞主线程,所以子线程有什么意义

其实最主要的意义就是这个超时机制,由于前面说的SHGetFileInfo可能非常耗时,我们不能无限期等待下去(GUI会卡死)

所以利用多线程 + 条件变量的方式实现超时机制

仅单线程的话,控制权会陷入API调用SHGetFileInfo,无法退出

下面附上QShGetFileInfoThread的代码:

123456789101112131415161718192021222324252627282930313233343536373839404142434445class QShGetFileInfoThread : public QThread {... void run() override { m_init = CoInitializeEx(nullptr, COINIT_MULTITHREADED); QMutexLocker readyLocker(&m_readyMutex); while (!m_cancelled.loadRelaxed()) { if (!m_params && !m_cancelled.loadRelaxed() && !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll))) continue; if (m_params) { const QString fileName = m_params->fileName; SHFILEINFO info; const bool result = SHGetFileInfo(reinterpret_cast(fileName.utf16()), m_params->attributes, &info, sizeof(SHFILEINFO), m_params->flags); m_doneMutex.lock(); if (!m_cancelled.loadRelaxed()) { *m_params->result = result; memcpy(m_params->info, &info, sizeof(SHFILEINFO)); } m_params = nullptr; m_doneCondition.wakeAll(); m_doneMutex.unlock(); } } if (m_init != S_FALSE) CoUninitialize(); } bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs) { QMutexLocker doneLocker(&m_doneMutex); m_readyMutex.lock(); m_params = params; m_readyCondition.wakeAll(); m_readyMutex.unlock(); return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs)); }...};

Why QFileIconProvider Fast好的,接下来还剩下一个问题,为什么用QFileIconProvider 比直接用SHGetFileInfo更快(在加载大量图标的时候)

首先就是内部缓存机制,其次是延迟获取机制(先返回QIcon + Engine,在合适的时候再调用filePixmap)

这样可以结合Qt的绘制机制进行一些奇怪的优化(多线程、局部重绘之类的)

以上,我猜的

好的,Peace

RefMrBeanCpp/Dr-Folder: Select icons for your software folders automatically. (github.com)

更改文件夹图标 & 使其立即生效 - MrBeanC-Blog


奥克斯电热水器质量怎么样?四款旗舰产品深度测评,推荐给您! 苹果设备如何下载GTA5手游?安全渠道与注意事项全解析