You may remember quite an old post of mine about the shortcomings of ExtractAssociatedIcon() which, surprisingly, still gets a fair number of hits. In light of some of the feedback i’ve received since the original post, i’ve decided to update the code to include the following fixes and improvements:

Both methods in IconTools now use SHGetFileInfo

As was pointed out by Michael (no surname provided), the native SHGetFileInfo() function (which we were already using to get the icon for existing files) can also be used to get the icon for a file’s type. The additional flag SHGFI_USEFILEATTRIBUTES instructs the API not to attempt to access the file, and simply locates the icon by examining its attributes. It turns out that you can get away with passing a file extension directly to this function.

The more long-winded implementation based on the ExtractIconEx() function is no longer used. This also removes the need to access the Windows Registry to find the DefaultIcon key.

Handles are now released using DestroyIcon

Another commenter on the original post, Cobein, pointed out (quite correctly) that the managed Icon class does not automatically release handles to icons created using the FromHandle() method.

This posed a slight design challenge, in that users could not be expected to do this after they finished using the icon returned by the method in the IconTools class. To get around this, I decided to use the Clone() method to copy the icon, before releasing the native handle and returning the managed copy instead.

The GetIconForFile() method now looks like this:

public static Icon GetIconForFile(string filename, ShellIconSize size) {
   SHFILEINFO shinfo = new SHFILEINFO();
   NativeMethods.SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), size);

   Icon icon = null;

   if (shinfo.hIcon.ToInt32() != 0) {
      // create the icon from the native handle and make a managed copy of it
      icon = (Icon)Icon.FromHandle(shinfo.hIcon).Clone();

      // release the native handle
      NativeMethods.DestroyIcon(shinfo.hIcon);
   }

   return icon;
}

Example Usage

Usage remains unchanged; if you used the previous version of this code, it will continue to work as before.

Icon smallIcon = IconTools.GetIconForFile(
   @"C:\Windows\System32\notepad.exe",
   ShellIconSize.SmallIcon
);

Icon largeIcon = IconTools.GetIconForExtension(".html", ShellIconSize.LargeIcon);

Download

IconTools.cs

5 thoughts on “IconTools Revisited

  1. Pingback: Building a Better ExtractAssociatedIcon | Brad Smith's Coding Blog

    • vote

      Not using this particular API, no. There may be a newer function in the Win32 API somewhere, but I have not looked into it yet.

      Reply
  2. vote

    We are using a variation of this to bring back the File Type but ran into an issue when running 64bit.


    public static string GetTypeForFile(string filename)
    {
    SHFILEINFO shinfo = new SHFILEINFO();
    SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), 0x000000610);
    return shinfo.szTypeName;
    }

    In the code above we would get things like “tcut” instead of “Shortcut” or “e Acrobat Document” instead of “Adobe Acrobat Document”. It turns out that the type of the iIcon property in the SHFILEINFO struct should be int, not IntPtr. I was pointed in the right direction by this SO question.

    Reply
    • vote

      Thanks for the extra info. What you’ve said makes sense because the IntPtr type can be a different size depending on the processor architecture, whereas the ‘int’ type is always 32-bits.

      Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required