[C#] 呼叫 IShellItemImageFactory 為照片預先建立縮圖

2025-12-09

最近整理照片,但是一個檔案夾裡面上萬張照片 只透過檔名真的很難判斷
都是要靠系統 Windows 內建的縮圖來判斷 但是產生速度非常的慢 我就在想能不能先去把縮圖做好


目前問一下 GPT 的做法,看起來最好的解決方案就是去呼叫 Win32 先去產生縮圖

而且後來我才發現一件事情,以前會跟著檔案夾的 Thumbs.db 現在都已經統一在 

C:\Users\[用戶名稱]\AppData\Local\Microsoft\Windows\Explorer\thumbcache_256.db

接下來就是程式碼的部分,首先我們得先製作 Win32 的呼叫介面

C# Code:

public static class WindowsThumbnailProvider { public static IntPtr GetThumbnail(string fileName, int width, int height) { Guid iid = new Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B"); SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref iid, out IShellItemImageFactory factory); SIZE size = new SIZE() { cx = width, cy = height }; factory.GetImage(size, 0x0, out IntPtr hBitmap); return hBitmap; // 可再轉成 Bitmap } [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] private static extern void SHCreateItemFromParsingName( string pszPath, IntPtr pbc, ref Guid riid, out IShellItemImageFactory ppv ); } [ComImport] [Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IShellItemImageFactory { void GetImage(SIZE size, int flags, out IntPtr phbm); } [StructLayout(LayoutKind.Sequential)] internal struct SIZE { public int cx; public int cy; }

這段程式碼透過 Win32 API 取得 Windows 系統產生的縮圖 ,用 SHCreateItemFromParsingName 取得 IShellItemImageFactory

COM 物件 再呼叫 GetImage 產生指定尺寸的縮圖 並回傳 hBitmap你可以把它轉成 Bitmap 使用

重點在於這是系統級縮圖 不需要自己壓圖 ,比自己去滑動還簡單吧?

之後就是透過主程式呼叫讓你要去 "開光" 的檔案夾

static bool IsImage(string file) { string ext = Path.GetExtension(file).ToLower(); return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".gif" || ext == ".webp" || ext == ".heic" || ext == ".tiff"; } async static Task Main() { Console.WriteLine("請輸入做快取的檔案夾"); var folder = Console.ReadLine(); string[] files = Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly); var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 // 避免 COM 爆掉 }; Parallel.ForEach(files, options, file => { if (!IsImage(file)) return; bool ok = GenerateThumbnailSTA(file); Console.WriteLine($"{Path.GetFileName(file)} → {(ok ? "OK" : "Fail")}"); }); Console.WriteLine("全部縮圖建立完成!"); } /// <summary> /// 用 STA Thread 執行縮圖,避免 0x8000000A /// </summary> static bool GenerateThumbnailSTA(string file) { bool result = false; var thread = new Thread(() => { result = GenerateThumbnailWithRetry(file, 256, 256); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); return result; } static bool GenerateThumbnailWithRetry(string file, int width, int height, int retry = 3) { for (int i = 0; i < retry; i++) { try { WindowsThumbnailProvider.GetThumbnail(file, width, height); return true; } catch (COMException ex) { if (ex.HResult == unchecked((int)0x8000000A)) // E_PENDING { Thread.Sleep(50); // 等一會兒再嘗試 continue; } return false; } catch { return false; } } return false; // 重試仍失敗 }

這段程式碼會掃描指定資料夾的所有圖片,檢查副檔名後用 Parallel.ForEach 平行處理

每張圖都丟到 STA Thread 執行縮圖 避免 COM 出現 0x8000000A 的 E_PENDING 錯誤

若縮圖失敗就稍等後重試 最後把結果印出 目的就是提前建立 Windows 的縮圖快取 讓開資料夾時不用等縮圖生成

結果:


因為體感很難敘述或是感覺,所以我最後只能觀測 

C:\Users\[用戶名稱]\AppData\Local\Microsoft\Windows\Explorer\thumbcache_256.db

是不是檔案有變大,然後,我發現當 thumbcache_256.db 大於 140MB 的時候,會開始出現錯誤

我問 GPT 他的猜測可能是我打到 Windows 回收機制了,為這我改寫很多次,看來目前最好的做法

可能是自己設計一個資料結構製作縮圖才會比較好管理照片,不過這次也讓我學到不少事情..

部分程式請 GPT 幫助寫成



當麻許的碎念筆記 2014 | Donma Hsu Design.