大家有没有发现,我们任意一个软件发送到桌面快捷方式,就可以有个清晰的图标展示出来。当我们在系统桌面通过右键菜单“查看”来切换图标时,选择大图标依然可以很清晰的显示出来。这是为什么呢?我们通过C#能不能获取这些图标呢?今天就来给大家答疑解惑!

背景

当我们要实现一个功能,用来显示桌面上的应用程序,或者管理第三方应用程序。这个时候,我们需要将应用程序自带的图标在管理软件中显示出来,因为这样,我们才能让用户知道自己想要的软件在什么位置。但是这些图标从哪里来,怎么获取就是我们面临的问题了(当然,我们可以通过第三方软件把图标给扣出来)。

为什么图标调整大小后可以这么清晰,难道是因为图标本身就很大?

大家应该还记得,在VS中,我们每个应用程序都可以设置图标:打开visual studio,选中 解决方案 -> 右键 属性 -> 应用程序 -> 浏览 选择图标。

我们会发现,我们能浏览的图标必须是ico格式。为什么不能选择PNG格式呢?

其实ico格式文件,是一组图片集合,也就是对应系统api中的:ImageList。我们可以任意找一个ico图标,我们会发现ico图标的大小会比通常的jpg,png等格式的图片更大。我们用visual studio打开查看,我们会发现,其实ico图标里面是有一组图标,可以是pngjpgbmp等格式。分辨率包含有:16 * 1624 * 2432 * 3248 * 4864 * 64 128 * 128,甚至是256 * 256难怪可以这么清晰! exe或dll资源也可用ResourceHacker工具打开,能看到更多详细信息和图标。

如何通用csharp获取系统图标

获取图标的方法有好几种

  1. 通过 System.Drawing.Icon.ExtractAssociatedIcon(filename) 获取
  2. 通过 windows api ExtractIconEx 获取
  3. 通过 windows api SHGetFileInfo等组合方法获取

注意:Icon格式的图标文件,可以通过ToBitmap()方法,将其转换成winform和wpf程序使用。

System.Drawing.Icon.ExtractAssociatedIcon(filename)

该方法比较简单,只需要引入dll System.Drawing.dll,调用此方法,传入文件路径即可返回Icon格式的图标文件。
该方法获取的图标大小不确定,跟图标的索引有关,针对部分图标会存在获取失败或者崩溃的现象。
该方法无法对参数做过多的设置,难以满足业务要求。

方法内部实现也是调用系统api:

Declare Function ExtractAssociatedIcon Lib "shell32.dll" Alias "ExtractAssociateIconA" (ByVal hInst As Long, ByVal lpIconPath As String, lpiIcon As Long) As Long

通过ExtractIconEx获取

ExtractIconEx方法也是通过调用系统 shell32.dll 中的 api来实现。
具体调用方式如下:

[DllImport("shell32.dll")]
public static extern int ExtractIconEx(string lpszFile, int niconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, int nIcons);
方法名 描述
lpszFile 表示文件路径
niconIndex 表示图标索引
phiconLarge 指向图标句柄数组的指针,它可接收从文件获取的大图标的句柄。如果该参数是NULL没有从文件抽取大图标。
phiconSmall 指向图标句柄数组的指针,它可接收从文件获取的小图标的句柄。如果该参数是NULL,没有从文件抽取小图标。
nIcons 指定要从文件中抽取图标的数目

调用示例


        private static void ExtractIconToPng(string appPath, string saveFileName)
        {
            int IconCount = Shell32.ExtractIconEx(appPath, -1, null, null, 0);

            var largeIcons = new IntPtr[IconCount];

            var smallIcons = new IntPtr[IconCount];

            Shell32.ExtractIconEx(appPath, 1, largeIcons, smallIcons, IconCount);

            for (int i = 0; i < IconCount; i++)
            {
                var icon = Icon.FromHandle(largeIcons[i]);
                var bitmap = icon.ToBitmap();

                var fs = new FileStream(saveFileName, FileMode.Create);

                var ic2 = System.Drawing.Icon.ExtractAssociatedIcon(appPath);

                ic2.ToBitmap().Save(fs, System.Drawing.Imaging.ImageFormat.Png);
                fs.Close();
            }
        }

该方法不好获取到想要得图标,尤其是256*256这么大的图标。那么继续往下看第三种方法。

通过 SHGetFileInfo 方法获取

基本调用API

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

        [DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
        public static extern int DestroyIcon(IntPtr hIcon);

        [DllImport("shell32.dll")]
        public static extern uint SHGetIDListFromObject([MarshalAs(UnmanagedType.IUnknown)] object iUnknown, out IntPtr ppidl);

        [DllImport("Shell32.dll")]
        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        

常量的定义:


        public const int SHIL_LARGE = 0x0;
        public const int SHIL_SMALL = 0x1;
        public const int SHIL_EXTRALARGE = 0x2;
        public const int SHIL_SYSSMALL = 0x3;
        public const int SHIL_JUMBO = 0x4;
        public const int SHIL_LAST = 0x4;

        public const int ILD_TRANSPARENT = 0x00000001;
        public const int ILD_IMAGE = 0x00000020;

定义好方法所需要的结构体


 [Flags]
    public enum SHGFI : uint
    {
        /// <summary>get icon</summary>
        Icon = 0x000000100,
        /// <summary>get display name</summary>
        DisplayName = 0x000000200,
        /// <summary>get type name</summary>
        TypeName = 0x000000400,
        /// <summary>get attributes</summary>
        Attributes = 0x000000800,
        /// <summary>get icon location</summary>
        IconLocation = 0x000001000,
        /// <summary>return exe type</summary>
        ExeType = 0x000002000,
        /// <summary>get system icon index</summary>
        SysIconIndex = 0x000004000,
        /// <summary>put a link overlay on icon</summary>
        LinkOverlay = 0x000008000,
        /// <summary>show icon in selected state</summary>
        Selected = 0x000010000,
        /// <summary>get only specified attributes</summary>
        Attr_Specified = 0x000020000,
        /// <summary>get large icon</summary>
        LargeIcon = 0x000000000,
        /// <summary>get small icon</summary>
        SmallIcon = 0x000000001,
        /// <summary>get open icon</summary>
        OpenIcon = 0x000000002,
        /// <summary>get shell size icon</summary>
        ShellIconSize = 0x000000004,
        /// <summary>pszPath is a pidl</summary>
        PIDL = 0x000000008,
        /// <summary>use passed dwFileAttribute</summary>
        UseFileAttributes = 0x000000010,
        /// <summary>apply the appropriate overlays</summary>
        AddOverlays = 0x000000020,
        /// <summary>Get the index of the overlay in the upper 8 bits of the iIcon</summary>
        OverlayIndex = 0x000000040,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SHFILEINFO
    {
        public const int NAMESIZE = 80;
        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;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left, top, right, bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        int x;
        int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public 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;    // x offest from the upperleft of bitmap
        public int yBitmap;    // y offset from the upperleft of bitmap
        public int rgbBk;
        public int rgbFg;
        public int fStyle;
        public int dwRop;
        public int fState;
        public int Frame;
        public int crEffect;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGEINFO
    {
        public IntPtr hbmImage;
        public IntPtr hbmMask;
        public int Unused1;
        public int Unused2;
        public RECT rcImage;
    }

    [ComImportAttribute()]
    [GuidAttribute("192B9D83-50FC-457B-90A0-2B82A8B5DAE1")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IImageList
    {
        [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, ref IntPtr picon);

        [PreserveSig]
        int GetImageInfo(int i, ref IMAGEINFO pImageInfo);

        [PreserveSig]
        int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags);

        [PreserveSig]
        int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv);

        [PreserveSig]
        int Clone(ref Guid riid, ref IntPtr ppv);

        [PreserveSig]
        int GetImageRect(int i, ref RECT prc);

        [PreserveSig]
        int GetIconSize(ref int cx, ref int cy);

        [PreserveSig]
        int SetIconSize(int cx, int cy);

        [PreserveSig]
        int GetImageCount(ref int pi);

        [PreserveSig]
        int SetImageCount(int uNewCount);

        [PreserveSig]
        int SetBkColor(int clrBk, ref int pclr);

        [PreserveSig]
        int GetBkColor(ref int pclr);

        [PreserveSig]
        int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);

        [PreserveSig]
        int EndDrag();

        [PreserveSig]
        int DragEnter(IntPtr hwndLock, int x, int y);

        [PreserveSig]
        int DragLeave(IntPtr hwndLock);

        [PreserveSig]
        int DragMove(int x, int y);

        [PreserveSig]
        int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot);

        [PreserveSig]
        int DragShowNolock(int fShow);

        [PreserveSig]
        int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv);

        [PreserveSig]
        int GetItemFlags(int i, ref int dwFlags);

        [PreserveSig]
        int GetOverlayImage(int iOverlay, ref int piIndex);
    };

调用逻辑: 先根据要求获取图标的索引号,在通过图标列表方法,传入索引号来获取符合的图标。

具体调用如下:


        //ImageList Guid 固定死的,com id
        const string IID_IImageList2 = "192B9D83-50FC-457B-90A0-2B82A8B5DAE1";
        
        const string IID_IImageList = "46EB5926-582E-4017-9FDF-E8998DAA0950";

        /// <summary>
        /// 获取图标索引
        /// </summary>
        /// <param name="pszFile"></param>
        /// <returns></returns>
        private static int GetIconIndex(string pszFile)
        {
            SHFILEINFO sfi = new SHFILEINFO();
            Shell32.SHGetFileInfo(pszFile, 0, ref sfi, (uint)Marshal.SizeOf(sfi), (uint)(SHGFI.SysIconIndex | SHGFI.OpenIcon | SHGFI.UseFileAttributes));
            return sfi.iIcon;
        }

        /// <summary>
        /// 获取60*60图标
        /// </summary>
        /// <param name="iImage"></param>
        /// <returns></returns>
        private static Bitmap GetLargeIcon(int iImage)
        {
            IImageList spiml = null;
            Guid guil = new Guid(IID_IImageList);//or IID_IImageList
            Shell32.SHGetImageList(Shell32.SHIL_EXTRALARGE, ref guil, ref spiml);
            IntPtr hIcon = IntPtr.Zero;
            spiml.GetIcon(iImage, Shell32.ILD_TRANSPARENT | Shell32.ILD_IMAGE, ref hIcon);
            var fileIcon = Icon.FromHandle(hIcon).ToBitmap();
            Shell32.DestroyIcon(hIcon);
            return fileIcon;
        }

参数iImageList必须是以下值之一:

SHIL_LARGE(0x0)

图像尺寸通常为32x32像素。但是,如果从“显示属性”的“外观”选项卡的“效果”部分中选择了“使用大图标”选项,则图像为48x48像素。

SHIL_SMALL(0x1)

这些图像是Shell标准的小图标尺寸16x16,但是该尺寸可以由用户自定义。

SHIL_EXTRALARGE(0x2)

这些图像是Shell标准的超大图标尺寸。通常为48x48,但是大小可以由用户自定义。

SHIL_SYSSMALL(0x3)

这些图像是由指定的大小GetSystemMetrics的调用SM_CXSMICON和GetSystemMetrics的调用SM_CYSMICON。

SHIL_JUMBO(0x4)

Windows Vista及更高版本。图像通常为256x256像素。

SHIL_LAST

最大有效标志值,用于验证。

为什么在GetLargeIcon方法中,必须是固定的两个GUID

我们可以发现在IImageList接口中,有个 [GuidAttribute("192B9D83-50FC-457B-90A0-2B82A8B5DAE1")]得Attribute,这两个GUID是com标识,底层固定了,因为IMAGELIST有两个,通常使用第一个。接口IImageList也可以标记两个中的任意一个。

参考文献:
https://www.pinvoke.net/default.aspx/shell32.shgetimagelist
https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetimagelist

获取系统图标先到这里,如果大家有其它方法,请在下方评论留言。


本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E8%BF%9B%E7%A8%8B%E6%88%96dll%E7%9A%84ico%E5%9B%BE%E6%A0%87.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系