在 C# 或 VB.NET 中为 Windows 图标缓存中的特定文件夹预加载文件夹图标

Preload folder icon for a specific folder in Windows Icon cache, in C# or VB.NET

我需要提到一个第 3 方程序,或者更好地说 WinThumbsPreloader 程序的源代码,其中包含用 C# 编写的所有必要代码,以预加载 [=122= 中文件的缩略图] 缩略图缓存,如您在此演示中所见:

问题是我需要预加载的是一个文件夹的图标,但是IThumbnailCache::GetThumbnail method does not allow to pass a folder item, and it will return error code 0x8004B200 (WTS_E_FAILEDEXTRACTION):

The Shell item does not support thumbnail extraction. For example, .exe or .lnk items.

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

所以,我的意思是我有一个文件夹,里面有一个 desktop.ini 文件,你可能知道它可以用来替换默认的 icon/thumbnail 用于存储 desktop.ini 文件的文件夹。 desktop.ini文件内容示例:

[.ShellClassInfo]
IconResource=FolderPreview.ico,0

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

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

相反,提高速度:

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

IThumbnailCache 接口似乎不是解决这个问题的方法...


UPDATE 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 包含我使用该方法缓存的两个图标上面,以及操作系统本身缓存的图标,所以我调用上面的方法生成的图标缓存引用在某种程度上不同于操作系统本身生成的图标缓存引用,这就是我做错的地方。 ..

我做错了什么?

UPDATE 2

使用 WindowsAPICodePack v1.1 库(来自 Nuget 管理器)我遇到了与 的代码中描述的相同的问题更新 1 使用 IShellItemImageFactory.GetImage:我可以提取图标并且图标缓存在 iconcache_256.db 文件(以及其他 *.db 文件中更少的 con 缓存大小),但是如果我通过 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[=85= 中的缓存图标引用增加了 2 倍](以及其他 *.db 文件),因为如果我从一个目录中迭代 1000 个子文件夹以提取和缓存它们的图标,如果在我这样做之后我通过 Explorer.exe 导航到该文件夹​​然后 O.S.将再次提取并缓存那些在 iconcache_256.db(以及其他 *.db 文件)中创建新条目的 1.000 个子文件夹图标。

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

UPDATE 3

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

中调试可执行文件时已经提取和缓存的图标

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

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

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

Objective

我的主要目标是从一堆特定文件夹中预加载文件夹图标。图标在每个文件夹内的“desktop.ini”文件中指定,这是一个 O.S 功能,O.S 在视觉上将文件夹表示为图标,它从字面上取代了常见的带有文件内容预览的黄色默认文件夹图标,用于在“desktop.ini”文件中指定的您选择的图标。这是一个漂亮的预览功能,例如,对于包含视频游戏、音乐专辑或电影的文件夹,您可以在其中使用游戏、电影或音乐封面作为该文件夹的图标。

代码方法

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

问题

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

问题的根源

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

所以有一个 64 位 Windows 我总是 运行 在 x86 模式下运行我的代码,这导致我的 x86 进程生成了图标,而我并没有意识到这个问题与 O.S 在 Iconcache_nnn.db 文件中创建的条目具有不同散列的条目,这就是 [= 再次缓存图标的原因65=],因为 O.S 无法识别来自我的 x86 进程的缓存图标的哈希。

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

AFAIK, the icon/thumb hash for a path is computed using the size (doesn't exist for a folder), last modified date, and the file identifier

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

解决方案

我的 64 位 O.S 中的解决方案只是确保在 x64 中编译进程(以编程方式获取文件夹图标)以匹配 O.S / 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="
    ''' </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 访问文件夹时, O.S 将简单地忽略这些条目,而是再次缓存生成的文件夹图标新的附加条目。

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