C#のWPFでEXEファイルのアイコンを抽出するコード

コンピュータ

WPFでアイコンを扱う場合System.Drawingを使う方法がありますが、今回はWindowsAPIを使う方法を試してみます。

サンプルコード

動作確認のためにコンソールアプリで、notepad.exeのアイコンを取得しpngファイルとして保存します。

ファイル名:ExtractIconSample.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

ファイル名:Program.cs

using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;

internal static class Program
{
    [STAThread]
    private static void Main(string[] args)
    {
        // 取得対象: notepad.exe
        string exePath = Environment.ExpandEnvironmentVariables(@"%SystemRoot%\system32\notepad.exe");
        if (!File.Exists(exePath))
        {
            Console.Error.WriteLine("notepad.exe が見つかりません: " + exePath);
            return;
        }

        // まずは 256px(Jumbo) を狙い、ダメなら順次縮小、最後に ExtractIcon でフォールバック
        BitmapSource? icon = GetShellIcon(exePath, 256)
                          ?? GetShellIcon(exePath, 48)
                          ?? GetIconViaExtractIcon(exePath);

        if (icon == null)
        {
            Console.Error.WriteLine("アイコン取得に失敗しました。");
            return;
        }

        // PNG保存
        string outPath = Path.GetFullPath("notepad_icon_256.png");
        using (var fs = File.Create(outPath))
        {
            var enc = new PngBitmapEncoder();
            enc.Frames.Add(BitmapFrame.Create(icon));
            enc.Save(fs);
        }

        Console.WriteLine("Saved: " + outPath);
    }

    private enum SHIL : int { Small = 0, Large = 1, ExtraLarge = 2, SysSmall = 3, Jumbo = 4 }

    [ComImport, Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IImageList
    {
        // 必要なのは GetIcon だけ
        [PreserveSig] int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);
        [PreserveSig] int ReplaceIcon(int i, IntPtr hicon, ref int pi);
        [PreserveSig] int SetOverlayImage(int iImage, int iOverlay);
        [PreserveSig] int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);
        [PreserveSig] int AddMasked(IntPtr hbmImage, int crMask, ref int pi);
        [PreserveSig] int Draw(ref IMAGELISTDRAWPARAMS pimldp);
        [PreserveSig] int Remove(int i);
        [PreserveSig] int GetIcon(int i, int flags, out IntPtr picon);
        // 以降のメソッドは今回未使用
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct IMAGELISTDRAWPARAMS
    {
        public int cbSize;
        public IntPtr himl;
        public int i;
        public IntPtr hdcDst;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int xBitmap; // 未使用
        public int yBitmap; // 未使用
        public int rgbBk;
        public int rgbFg;
        public int fStyle;
        public int dwRop;
        public int fState;
        public int Frame;
        public int crEffect;
    }

    [DllImport("shell32.dll", EntryPoint = "#727")]
    private static extern int SHGetImageList(int iImageList, ref Guid riid, out IImageList ppv);

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct SHFILEINFO
    {
        public IntPtr hIcon;
        public int iIcon;
        public uint dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]  public string szTypeName;
    }

    private const uint SHGFI_ICON = 0x000000100;
    private const uint SHGFI_LARGEICON = 0x000000000;
    private const uint SHGFI_SYSICONINDEX = 0x000004000;
    private const int  ILD_TRANSPARENT = 0x00000001;

    private static BitmapSource? GetShellIcon(string exePath, int size)
    {
        // 1) システムイメージリスト上のインデックスを取得
        var shfi = new SHFILEINFO();
        SHGetFileInfo(exePath, 0, out shfi, (uint)Marshal.SizeOf<SHFILEINFO>(),
                      SHGFI_SYSICONINDEX | SHGFI_ICON | SHGFI_LARGEICON);
        int index = shfi.iIcon;

        // 2) 目的サイズに応じたリストを選択
        SHIL list = size >= 256 ? SHIL.Jumbo
                  : size >= 48  ? SHIL.ExtraLarge
                  : size >= 32  ? SHIL.Large
                  : SHIL.Small;

        Guid iidImageList = typeof(IImageList).GUID;
        if (SHGetImageList((int)list, ref iidImageList, out var imgList) != 0)
            return null;

        // 3) 取り出し
        IntPtr hIcon = IntPtr.Zero;
        int hr = imgList.GetIcon(index, ILD_TRANSPARENT, out hIcon);
        if (hr != 0 || hIcon == IntPtr.Zero) return null;

        try
        {
            var bmp = Imaging.CreateBitmapSourceFromHIcon(
                hIcon,
                Int32Rect.Empty,
                BitmapSizeOptions.FromWidthAndHeight(size, size));
            bmp.Freeze();
            return bmp;
        }
        finally
        {
            DestroyIcon(hIcon);
            if (shfi.hIcon != IntPtr.Zero) DestroyIcon(shfi.hIcon);
        }
    }

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool DestroyIcon(IntPtr hIcon);

    private static BitmapSource? GetIconViaExtractIcon(string exePath)
    {
        IntPtr hIcon = ExtractIcon(IntPtr.Zero, exePath, 0);
        if (hIcon == IntPtr.Zero)
            return null;

        try
        {
            var bmp = Imaging.CreateBitmapSourceFromHIcon(
                hIcon,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            bmp.Freeze();
            return bmp;
        }
        finally
        {
            DestroyIcon(hIcon);
        }
    }
}

実行結果

画像ファイルが作成される。

感想

WPFでアプリケーションランチャーを作ろうと思い調べてみました。比較的大きいサイズの画像が取得出来たので使えそうな感じがします。

コメント