Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting an icon group from a .dll file in C#

Tags:

c#

icons

I'm trying to extract an icon from imageres.dll. Specifically the "My Computer" or "This PC" icon. The problem is that at between Win7 and Win10, the icon number changes. However, the icon group does not (109). Is there a way to get that icon group, and then let the computer figure out which icon to use of that group, in the same way it figures out which icon to use for my app?

This is the code I'm using to get the specific icon via the index:

public class GetIcon {
    public static Icon Extract(string file, int number) {
        IntPtr large;
        IntPtr small;
        ExtractIconEx(file, number, out large, out small, 1);
        try {
            return Icon.FromHandle(small);
        }
        catch {
            return null;
        }
    }
    [DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
}

Thanks.

like image 957
David Avatar asked Sep 14 '25 22:09

David


1 Answers

There are a couple of ways to do this. The most reliable, and potentially most time consuming (provided you can't find an existing library), is to parse the PE File (i.e. .exe, .dll) and extract the relevant Icon group data yourself. Here's a good resource for the format: https://msdn.microsoft.com/en-us/library/ms809762.aspx

The second way, can be done easily enough with Windows functions, however there is one caveat. It will only work on PE files that are of the same bit-type as your application. So, for example, if your application is 64-bit, it will only work on 64-bit PE files.

Here's a function I just wrote - based off this: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648051(v=vs.85).aspx#_win32_Sharing_Icon_Resources, that takes a file name, group number, and desired icon size, and returns a System.Drawing.Icon

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, int lpName, int lpType);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32.dll")]
static extern IntPtr LockResource(IntPtr hResData);
[DllImport("user32.dll")]
static extern int LookupIconIdFromDirectoryEx(byte[] presbits, bool fIcon, int cxDesired, int cyDesired, uint Flags);
[DllImport("user32.dll")]
static extern IntPtr CreateIconFromResourceEx(byte[] pbIconBits, uint cbIconBits, bool fIcon, uint dwVersion, int cxDesired, int cyDesired, uint uFlags);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

const int RT_GROUP_ICON = 14;
const int RT_ICON = 0x00000003;

private System.Drawing.Icon GetIconFromGroup(string file, int groupId, int size)
{
    IntPtr hExe = LoadLibrary(file);
    if(hExe != IntPtr.Zero)
    {

        IntPtr hResource = FindResource(hExe, groupId, RT_GROUP_ICON);

        IntPtr hMem = LoadResource(hExe, hResource);

        IntPtr lpResourcePtr = LockResource(hMem);
        uint sz = SizeofResource(hExe, hResource);
        byte[] lpResource = new byte[sz];
        Marshal.Copy(lpResourcePtr, lpResource, 0, (int)sz);

        int nID = LookupIconIdFromDirectoryEx(lpResource, true, size, size, 0x0000);

        hResource = FindResource(hExe, nID, RT_ICON);

        hMem = LoadResource(hExe, hResource);

        lpResourcePtr = LockResource(hMem);
        sz = SizeofResource(hExe, hResource);
        lpResource = new byte[sz];
        Marshal.Copy(lpResourcePtr, lpResource, 0, (int)sz);

        IntPtr hIcon = CreateIconFromResourceEx(lpResource, sz, true, 0x00030000, size, size, 0);

        System.Drawing.Icon testIco = System.Drawing.Icon.FromHandle(hIcon);

        return testIco;
    }

    return null;
}

The process basically works like this:

  1. use LoadLibrary to load up the .exe or .dll file
  2. Get the handle & data of the RT_GROUP_ICON resource
  3. Pass the data, along with the desired size to LookupIconIdFromDirectoryEx, to get the icon index
  4. From there, you can either use ExtractIconEx, or just repeat step 2 with the icon index, and RT_ICON instead, followed by using CreateIconFromResourceEx to get your icon handle.
like image 191
Digital_Utopia Avatar answered Sep 17 '25 13:09

Digital_Utopia