【问题标题】:Preload folder icon for a specific folder in Windows Icon cache, in C# or VB.NET在 C# 或 VB.NET 中为 Windows 图标缓存中的特定文件夹预加载文件夹图标
【发布时间】:2026-01-06 20:45:02
【问题描述】:

我需要提到一个第 3 方程序,或者更好地说是 WinThumbsPreloader 程序的源代码,它包含用 C# 编写的所有必要代码,用于在 Windows 缩略图缓存中预加载文件的缩略图,如您在这个演示:

问题是我需要预加载的是文件夹的图标,但是IThumbnailCache::GetThumbnail方法不允许传递文件夹项,会返回错误码0x8004B200(WTS_E_FAILEDEXTRACTION):

Shell 项目不支持缩略图提取。例如,.exe 或 .lnk 项目。

换句话说,我需要做与 WinThumbsPreloader 程序相同的事情,但是对于文件夹图标而不是文件夹/图标缩略图。

所以,我的意思是我有一个文件夹,里面有一个 desktop.ini 文件,您可能知道它可以用来替换存储该文件的文件夹的默认图标/缩略图desktop.ini 文件。 desktop.ini 文件内容示例:

[.ShellClassInfo]
IconResource=FolderPreview.ico,0

我需要预加载文件夹的原因是为了避免每次浏览文件夹时生成图标缓存。

为了消除疑虑,我想避免这种缓慢的文件夹图标生成:

相反,获得这种改进的速度:

我的问题是:在 C# 或 VB.NET 中,如何以编程方式在 Windows 图标缓存中预加载特定文件夹的图标?

看来IThumbnailCache接口不是解决这个问题的办法……


更新 1

根据@Jimi 的评论建议,这是我正在尝试的方法:

Public Shared Sub PreloadInIconCache(path As String, 
                                     Optional iconSize As Integer = 256)

    Dim iIdIShellItem As New Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")
    Dim shellItem As IShellItem = Nothing
    Dim shellItemIF As IShellItemImageFactory
    Dim iconFlags As IShellItemImageFactoryGetImageFlags = 
                     IShellItemImageFactoryGetImageFlags.IconOnly

    SHCreateItemFromParsingName(path, IntPtr.Zero, iIdIShellItem, shellItem)
    shellItemIF = DirectCast(shellItem, IShellItemImageFactory)
    shellItemIF.GetImage(New NativeSize(iconSize, iconSize), iconFlags, Nothing)

    Marshal.ReleaseComObject(shellItemIF)
    Marshal.ReleaseComObject(shellItem)
    shellItemIF = Nothing
    shellItem = Nothing

End Sub

它缓存图标。如果我调用这个方法让我们说一个包含 1000 个带有自定义图标的子文件夹的目录,那么 iconcache_256.db 的大小会增加大约 250 MB,因此它清楚地证明了图标正在被缓存,但是我做错了,因为操作系统不使用这些缓存的图标。我的意思是,如果在调用该方法然后手动使用 Explorer.exe 导航到该目录后,操作系统会再次开始提取和缓存创建新图标引用的所有 1000 个子文件夹的图标,所以 iconcache_256.db 将其文件大小翻倍至 500 MB 左右,这证明 iconcache_256.db 包含我使用上述方法缓存的图标和操作系统本身缓存的图标,所以我调用上述方法生成的图标缓存引用在某种程度上与操作系统本身生成的图标缓存引用不同,这就是我做错了......

我做错了什么?

更新 2

使用 WindowsAPICodePack v1.1 库(来自 Nuget 管理器)我遇到了与使用 IShellItemImageFactory.GetImageUPDATE 1 代码中描述的相同的问题:我可以提取图标并将图标缓存在 iconcache_256.db 文件中(以及其他较小的 con 缓存大小的 *.db 文件),但是如果我通过 Explorer.exe 导航到该目录,则操作系统开始再次提取和缓存我已经缓存的相同文件夹的图标...

完整代码示例:

Imports Microsoft.WindowsAPICodePack.Shell
...

Dim directoryPath As String = "C:\Directory"
Dim searchPattern As String = "*"
Dim searchOption As SearchOption = SearchOption.AllDirectories

For Each dir As DirectoryInfo In New DirectoryInfo(directoryPath).EnumerateDirectories(searchPattern, searchOption)

    Console.WriteLine($"Extracting icon for directory: '{dir.FullName}'")

    Using folder As ShellFolder = DirectCast(ShellFolder.FromParsingName(dir.FullName), ShellFolder)
        folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnly
        folder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Default

        Using ico As Bitmap = folder.Thumbnail.Bitmap ' Or: folder.Thumbnail.Icon
            ' PictureBox1.Image = ico
            ' PictureBox1.Update()
            ' Thread.Sleep(50)
        End Using

    End Using

Next dir

我不确定为什么操作系统在我已经缓存它们时坚持提取新图标并缓存它们,这将 iconcache_256.db 中的缓存图标引用乘以 x2(以及另一个 * .db 文件),因为如果我从一个目录中迭代 1000 个子文件夹以提取和缓存它们的图标,如果在我这样做之后我通过 Explorer.exe 导航到该文件夹​​,那么操作系统将再次提取并缓存那些在 iconcache_256.db (以及其他 *.db 文件)中创建新条目的 1.000 个子文件夹图标。

我不知道如何读取iconcache_256.db文件的数据库格式,所以我不知道结构格式,但是如果结构将目录路径作为其字段之一然后我怀疑我使用的代码方法可能会强制添加一个目录路径字段,当我导航到文件夹以通过 Explorer.exe 缓存图标时,该字段与操作系统在图标缓存字段中添加的内容不同,并且可能出于这个原因图标缓存引用乘以 x2...只是我在推测...

更新 3

我认为真正的问题可能是图标缓存 *.db 文件中添加的引用可能是每个进程的,因此当我使用 Explorer.exe 导航到目录时,它开始再次提取和缓存图标我在 Visual Studio 中调试我的可执行文件时已经提取和缓存了...

所以我做了一个测试,运行相同的代码来从两个不同的进程中提取/缓存文件夹图标,只需将同一个项目构建到两个具有不同名称和不同程序集 guid 的可执行文件。我发现当我运行第二个可执行文件时,*.db 文件中的图标缓存引用没有增加/复制,因此放弃了每个进程的想法。

我真的不知道我还能尝试什么...以及 *.db 图标缓存文件中的“重复”引用有什么不同。

【问题讨论】:

  • IShellItemImageFactory::GetImage备注部分的注释
  • @Jimi 感谢您的评论,但不确定我必须在那里看到什么,您能详细解释一下吗?理论上 ShellItemImageFactory::GetImage 将用于提取图标(或获取缓存的图标,如果存在)但不会强制操作系统提取它然后将其缓存在系统的图标/缩略图缓存中,是这不对吗?。
  • 在您的示例文件夹中,没有缩略图,因为您将其强制为带有 desktop.ini 的图标 (folder.ico)。
  • 我不能保证什么,但测试起来并不难。图标有点过时了。 IShellItemImageFactory(注意“图像”通用术语)看起来 1)用于缩略图和 2)用于图标(顺便说一句,当它查找缩略图时它使用 IThumbnailCache)。它并不是真正的缓存机制。
  • 您是否尝试过使用ThumbCache Viewer 来检查数据库内容,然后发布您的代码?

标签: c# .net vb.net winapi icons


【解决方案1】:

让我从头开始总结所有内容,因为 cmets 框中可能有这么多文本和 cmets,问题和赏金可能会让用户感到非常困惑:

目标

我的主要目标是并且是从一堆特定文件夹中预加载文件夹图标。该图标在每个文件夹内的“desktop.ini”文件中指定,这是一项操作系统功能,操作系统在该功能上将文件夹直观地表示为图标,它实际上用文件内容预览替换了常见的黄色默认文件夹图标,用于在“desktop.ini”文件中指定的您选择的图标。这是一个漂亮的预览功能,例如包含视频游戏、音乐专辑或电影的文件夹,您可以在其中使用游戏、电影或音乐封面作为该文件夹的图标。

代码方法

使用一些代码调用IShellItemImageFactory.GetImage() 函数,或者使用WindowsAPICodePack 库的相同操作,我能够让操作系统生成图标并将它们各自的条目添加到Iconcache_nnn.db 文件。

问题

我发现的问题是操作系统忽略了这些图标,它不想使用我缓存的图标来预览文件夹图标,而是操作系统创建了具有不同哈希但相同图标的新条目,换句话说,操作系统会重新缓存我已经缓存的图标。

问题的根源

通过反复试验,我发现运行我的代码以调用 IShellItemImageFactory.GetImage() 函数的进程架构 (x86 / x64) 非常重要。

因此,拥有 64 位 Windows 时,我总是在 x86 模式下运行我的代码,这导致我的 x86 进程生成的图标条目的哈希值与操作系统的条目不同在 Iconcache_nnn.db 文件中创建,这就是为什么图标被操作系统再次缓存的原因,因为操作系统没有从我的 x86 进程中识别出我缓存图标的哈希值。 p>

我真的不知道为什么会这样,但就是这样,所以从@Simon Mourier那里抢救这条评论:

AFAIK,路径的图标/拇指哈希是使用大小计算的 (文件夹不存在)、上次修改日期和文件标识符

对于该计算,我们需要添加另一个因素:流程架构差异化?

解决方案

我的 64 位操作系统中的解决方案只是确保在 x64 中编译进程(以编程方式获取文件夹图标)以匹配操作系统 / Explorer.exe 的相同架构。如果是 32 位 Windows,没什么好说的,请运行 x86 进程。

这是一个使用WindowsAPICodePack库预加载文件夹图标的简单代码:

    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Preloads the icon of a directory into Windows Icon Cache system files (IconCache_xxx.db).
    ''' <para></para>
    ''' The folder must contain a "desktop.ini" file with the icon specified.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <remarks>
    ''' Because the Windows Icon Cache system creates icon's entry hashes 
    ''' with some kind of process architecture differentiation, 
    ''' the user must make sure to call <see cref="PreloadInIconCache"/> function from
    ''' a process that matches the same architecture of the running operating system. 
    ''' <para></para>
    ''' In short, <see cref="PreloadInIconCache"/> function should be called 
    ''' from a x64 process if running under Windows x64, and  
    ''' from a x86 process if running under Windows x86.
    ''' <para></para>
    ''' Otherwise, if for example <see cref="PreloadInIconCache"/> function is called  
    ''' from a x86 process if running under Windows x64, it will happen that 
    ''' when accesing the specified folder via Explorer.exe, the operating system 
    ''' will ignore the icon cached using <see cref="PreloadInIconCache"/> function,
    ''' and it will cache again the folder, generating a new additional entry 
    ''' in the Icon Cache system.
    ''' <para></para>
    ''' Read more about this problem at: <see href="https://*.com/a/66773765/1248295"/>
    ''' </remarks>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <example> This is a code example.
    ''' <code language="VB.NET">
    ''' Dim directoryPath As String = "C:\Movies\"
    ''' Dim searchPattern As String = "*"
    ''' Dim searchOption As SearchOption = SearchOption.TopDirectoryOnly
    ''' Dim iconSize As IconSizes = IconSizes._256x256
    ''' 
    ''' For Each dir As DirectoryInfo In New DirectoryInfo(directoryPath).EnumerateDirectories(searchPattern, searchOption)
    '''     Console.WriteLine($"Preloading icon ({CStr(iconSize)}x{CStr(iconSize)}) for directory: '{dir.FullName}'")
    '''     DirectoryUtil.PreloadInIconCache(dir.FullName, iconSize)
    ''' Next file
    ''' </code>
    ''' </example>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <param name="dirPath">
    ''' The directory path.
    ''' </param>
    ''' 
    ''' <param name="iconSize">
    ''' The requested icon size, in pixels. 
    ''' <para></para>
    ''' Default value is 256.
    ''' </param>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Public Shared Sub PreloadInIconCache(directory As DirectoryInfo, Optional iconSize As Integer = 256)
        PreloadInIconCache(directory.FullName, iconSize)
    End Sub

    <DebuggerStepThrough>
Public Shared Sub PreloadInIconCache(path As String, Optional iconSize As Integer = 256)
    Using folder As ShellFileSystemFolder = DirectCast(ShellObject.FromParsingName(path), ShellFileSystemFolder)
        folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnly
        folder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Default
        folder.Thumbnail.AllowBiggerSize = True
        folder.Thumbnail.CurrentSize = New System.Windows.Size(iconSize, iconSize)

        Using thumbnail As Bitmap = folder.Thumbnail.Bitmap
        End Using
    End Using
End Sub

示例用法:

Dim folders As DirectoryInfo() = New DirectoryInfo("C:\").GetDirectories("*", SearchOption.AllDirectories)
For Each folder As DirectoryInfo In folders
    PreloadFolderIcon(folder, 256)
Next folder

请记住,如果在 64 位 Windows 下运行,包含该代码的 .NET 程序集必须在 x64 模式下运行,否则该代码将在 Iconcache_nnn.db 中生成图标条目文件,但当通过 Explorer.exe 访问文件夹时,操作系统将简单地忽略这些条目,而是再次重新缓存文件夹图标,生成新的附加条目。

最后别忘了,您可以使用@Max 提到的工具thumbcacheviewer 来检查和预览Iconcache_nnn.db 文件中的图标条目。

【讨论】: