You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows下如何提取文件覆盖图标或路径以用于自动化测试校验

问题描述

我们开发了一个图标覆盖扩展,会根据文件状态显示不同的覆盖图标。为开展自动化测试,需要验证文件是否显示了正确的覆盖图标,因此想了解是否可以提取覆盖图标本身或获取其路径,用于后续的对比检查。

我尝试了两段C#代码,但都未达到预期效果:

第一段代码

SHFILEINFO shfi = new SHFILEINFO();
IntPtr result = SHGetFileInfo(filePath, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_OVERLAYINDEX);

if (result != IntPtr.Zero)
{
    int overlayIndex = shfi.iIcon >> 24; 
    Console.WriteLine($"Overlay Index: {overlayIndex}");

    DestroyIcon(shfi.hIcon);
}

问题:overlayIndex始终返回0。

第二段代码

private static Icon GetOverlay(string filePath)
{
    var imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
    IMAGELIST sysImageList;
    var result = SHGetImageList(SHIL_SMALL, ref imageListGuid, out sysImageList);

    if (result == 0)
    {
        var shfi = new SHFILEINFO();
        var hSuccess = SHGetFileInfo(filePath, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_OVERLAYINDEX | SHGFI_SMALLICON);

        if (hSuccess != IntPtr.Zero)
        {
            var iconIndex = shfi.iIcon;
            var overlayIndex = iconIndex & 0xF;

            var hOverlayIcon = ImageList_GetIcon(sysImageList.handle, overlayIndex, 0);
            var overlay = Icon.FromHandle(hOverlayIcon);

            return overlay;
        }
    }

    return null;
}

问题:获取到的是系统标准图标,而非目标覆盖图标。

请问有没有可靠的方法获取独立的覆盖图标,用于自动化测试中的对比检查?


解决方案

要正确提取文件的覆盖图标,需要遵循Windows Shell API中覆盖索引的解析规则,以及系统图像列表的正确使用方式,以下是修正后的实现方案:

1. 必要的P/Invoke定义

首先补充完整的Shell和User32 API交互定义:

[StructLayout(LayoutKind.Sequential)]
public 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;
}

[ComImport]
[Guid("46EB5926-582E-4017-9FDF-E8998DAA0950")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMAGELIST
{
    [PreserveSig]
    int GetOverlayImage(int iOverlay, ref int piIndex);
    [PreserveSig]
    int GetIcon(int i, uint flags, ref IntPtr picon);
    // 其他接口方法可根据需求补充,此处保留核心方法
}

public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_SMALLICON = 0x1;
public const uint SHGFI_OVERLAYINDEX = 0x40;
public const int SHIL_SMALL = 0x1;

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

[DllImport("shell32.dll")]
public static extern int SHGetImageList(int iImageList, ref Guid riid, out IMAGELIST ppv);

[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr hIcon);

2. 修正后的覆盖图标获取方法

public static Icon GetFileOverlayIcon(string filePath)
{
    SHFILEINFO shfi = new SHFILEINFO();
    // 调用SHGetFileInfo获取包含覆盖索引的文件信息
    IntPtr hResult = SHGetFileInfo(
        filePath,
        0,
        ref shfi,
        (uint)Marshal.SizeOf(shfi),
        SHGFI_ICON | SHGFI_SMALLICON | SHGFI_OVERLAYINDEX
    );

    if (hResult == IntPtr.Zero)
        return null;

    try
    {
        // 提取覆盖索引:高8位有效,索引从1开始(0表示无覆盖)
        int overlayIndex = (shfi.iIcon >> 24) & 0xFF;
        if (overlayIndex == 0)
            return null;

        // 获取系统小图标图像列表
        Guid imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
        IMAGELIST sysImageList;
        int apiResult = SHGetImageList(SHIL_SMALL, ref imageListGuid, out sysImageList);
        if (apiResult != 0)
            return null;

        // 将覆盖索引转换为图像列表中的实际图标索引
        int overlayImageIndex = 0;
        apiResult = sysImageList.GetOverlayImage(overlayIndex, ref overlayImageIndex);
        if (apiResult != 0)
            return null;

        // 从图像列表中提取覆盖图标
        IntPtr hOverlayIcon = IntPtr.Zero;
        apiResult = sysImageList.GetIcon(overlayImageIndex, 0, ref hOverlayIcon);
        if (apiResult != 0 || hOverlayIcon == IntPtr.Zero)
            return null;

        return Icon.FromHandle(hOverlayIcon);
    }
    finally
    {
        // 释放SHGetFileInfo返回的临时图标句柄,避免资源泄漏
        if (shfi.hIcon != IntPtr.Zero)
            DestroyIcon(shfi.hIcon);
    }
}

关键注意事项

  • 覆盖索引解析:SHGFI_OVERLAYINDEX标记下,SHFILEINFO.iIcon的高8位是覆盖索引(1-based),低16位是基础图标索引,必须用(shfi.iIcon >> 24) & 0xFF正确提取。
  • 图像列表交互:系统覆盖图标存储在图像列表的覆盖层中,需要通过IMAGELIST.GetOverlayImage将覆盖索引转换为实际的图标索引,才能提取到目标覆盖图标。
  • 资源泄漏:必须释放SHGetFileInfo返回的hIcon句柄,避免GDI资源泄漏。
  • 扩展生效条件:确保自定义图标覆盖扩展已正确注册,且系统Shell已完成初始化(自动化测试时可延迟几秒等待加载)。

图标对比方案

获取到覆盖图标后,可将其转换为Bitmap,通过以下方式进行对比:

  1. 计算图标的哈希值(如MD5),与预期图标的哈希值比对。
  2. 逐像素对比图标内容,判断是否一致。

内容的提问来源于stack exchange,提问作者Action Heinz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:57:06