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

  • 759
  • 0
  • 2025-12-10

最近整理照片,但是一個檔案夾裡面上萬張照片 只透過檔名真的很難判斷
都是要靠系統 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 幫助寫成

--

本文原文首發於我的個人部落格:呼叫 IShellItemImageFactory 為照片預先建立縮圖 

---

---

Yesterday I wrote down the code. I bet I could be your hero. I am a mighty little programmer.