在C#中从.dll文件中提取图标组

3
我正在尝试从imageres.dll中提取图标,具体来说是“My Computer”或“This PC”图标。问题在于,在Win7和Win10之间,图标编号会发生变化。然而,图标组不会变(109)。有没有一种方法可以获取该图标组,然后让计算机决定使用该组中的哪个图标,就像它确定我的应用程序使用哪个图标一样?
以下是我使用的代码以通过索引获取特定图标:
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);
}

谢谢。
1个回答

4
有几种方法可以实现这一点。最可靠、也可能是最耗时的方法(如果你找不到现成的库),就是解析PE文件(即.exe、.dll文件)并自行提取相关的图标组数据。这里有一个好的资源可以参考格式:https://msdn.microsoft.com/en-us/library/ms809762.aspx 第二种方法可以很容易地使用Windows函数完成,但有一个注意事项。它只适用于与您的应用程序具有相同位类型的PE文件。例如,如果您的应用程序是64位的,则只能处理64位PE文件。
这是我刚写的一个函数,基于此:https://msdn.microsoft.com/en-us/library/windows/desktop/ms648051(v=vs.85).aspx#_win32_Sharing_Icon_Resources,它接受文件名、组号和所需的图标大小,并返回一个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;
}

这个过程基本上是这样的:
  1. 使用 LoadLibrary 加载 .exe 或 .dll 文件
  2. 获取 RT_GROUP_ICON 资源的句柄和数据
  3. 将数据与所需大小一起传递给 LookupIconIdFromDirectoryEx,以获取图标索引
  4. 从那里,您可以使用 ExtractIconEx,或者只需重复第2步,使用图标索引和 RT_ICON,然后使用 CreateIconFromResourceEx 获取您的图标句柄。

我不会尝试这个,因为这个项目已经过去很久了,但我很感激你花时间回答。希望它能帮助未来的某个人。 - David
1
我在尝试做同样的事情时遇到了这个问题,后来我解决了它,所以想分享一下 :p由于那个限制条件,我很可能会选择自己使用第一种方法。但是,希望这对其他人有所帮助。 - Digital_Utopia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接