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

Ele*_*ios 19 .net c# vb.net winapi icons

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

在此处输入图片说明

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

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

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

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

[.ShellClassInfo]
IconResource=FolderPreview.ico,0
Run Code Online (Sandbox Code Playgroud)

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

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

前

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

后

我的问题是:在 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
Run Code Online (Sandbox Code Playgroud)

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

我做错了什么?

更新 2

使用WindowsAPICodePack v1.1 库(来自 Nuget 管理器)我遇到了与使用UPDATE 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
Run Code Online (Sandbox Code Playgroud)

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

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

更新 3

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

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

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

Ele*_*ios 3

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

目标

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

代码方法

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

问题

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

问题的根源

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

因此,在使用 64 位 Windows 时,我总是在 x86 模式下运行代码,这导致我的 x86 进程生成的图标条目的哈希值与操作系统在其中创建的条目不同,而我却没有意识到这个问题。 Iconcache_nnn.db文件,这就是操作系统再次缓存图标的原因,因为操作系统无法识别 x86 进程中缓存图标的哈希值

我真的不知道为什么会这样,但事情就是这样,所以拯救@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="/sf/answers/4674163581/"/>
    ''' </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
Run Code Online (Sandbox Code Playgroud)

用法示例:

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

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

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