如何从网络共享文件中获取关联的图标

27
我正在使用Icon.ExtractAssociatedIcon来获取用户在openfiledialog中选择的文件的图标。问题是,如果用户从网络共享中选择图标,则openfiledialog的文件名属性以UNC格式显示,这会导致ExtractAssocaitedIcon抛出ArgumentException异常:
Value of '\\server\share\filename' is not valid for 'filePath'.

Stack Trace:
   at System.Drawing.Icon.ExtractAssociatedIcon(String filePath, Int32 index)

我的问题是,给定一个指定为\\server\share\filename的文件,我该如何获取图标?

注意: .NET 2.0


发布异常消息和堆栈跟踪。 - Hans Passant
@HansPassant 不一定需要堆栈跟踪,因为错误已经可以按照描述进行复现。但是我今天看到了堆栈跟踪。 - Ian Boyd
5个回答

37

使用 Reflector 查看,最终调用了 shell32.dll 中的 ExtractAssociatedIcon

您尝试过通过 PInvoke 访问 BCL 来解决问题吗?

示例代码(来自 PInvoke.Net):

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr ExtractAssociatedIcon(IntPtr hInst, StringBuilder lpIconPath,
   out ushort lpiIcon);

 // ... snip
    ushort uicon;
    StringBuilder strB = new StringBuilder(260); // Allocate MAX_PATH chars
    strB.Append(openFileDialog1.FileName);
    IntPtr handle = ExtractAssociatedIcon(IntPtr.Zero, strB, out uicon);
    Icon ico = Icon.FromHandle(handle);

    pictureBox1.Image = ico.ToBitmap();
 // ... snip

16
这实际上有效,这让我想知道,为什么在.NET中存在这种限制。 - Rahul
2
在我的电脑上也有效。看起来如果在调用时没有句柄可用,第一个参数可以设置为“IntPtr.Zero”。 - AFract
1
@AFract hInst被标记为“_Reserved_”,因此从未被使用。文档真的很糟糕。 - poizan42
如果有人因为它不能直接使用而感到沮丧,请投票支持此用户建议 - poizan42
1
@antikbd 260来自Windows API的限制,即允许路径的最大长度(参考https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation)。在编写Windows代码时,如果您的任何路径超过该长度,您将遇到各种问题!我同意这个共识。我来到这里寻找解决方案,因为我遇到了相同的UNC问题。感谢Brett Veenstra提供的解决方案,它非常出色,并且完美地解决了我的问题! - Westley Bennett
显示剩余3条评论

25

为了完整起见,这里有一个可用的 ExtractAssociatedIcon 程序:

/// <summary>
/// Returns an icon representation of an image contained in the specified file.
/// This function is identical to System.Drawing.Icon.ExtractAssociatedIcon, xcept this version works.
/// </summary>
/// <param name="filePath">The path to the file that contains an image.</param>
/// <returns>The System.Drawing.Icon representation of the image contained in the specified file.</returns>
/// <exception cref="System.ArgumentException">filePath does not indicate a valid file.</exception>
public static Icon  ExtractAssociatedIcon(String filePath)
{
    int index = 0;

    Uri uri;
    if (filePath == null)
    {
        throw new ArgumentException(String.Format("'{0}' is not valid for '{1}'", "null", "filePath"), "filePath");
    }
    try
    {
        uri = new Uri(filePath);
    }
    catch (UriFormatException)
    {
        filePath = Path.GetFullPath(filePath);
        uri = new Uri(filePath);
    }
    //if (uri.IsUnc)
    //{
    //  throw new ArgumentException(String.Format("'{0}' is not valid for '{1}'", filePath, "filePath"), "filePath");
    //}
    if (uri.IsFile)
    {
        if (!File.Exists(filePath))
        {
            //IntSecurity.DemandReadFileIO(filePath);
            throw new FileNotFoundException(filePath);
        }

        StringBuilder iconPath = new StringBuilder(260);
        iconPath.Append(filePath);

        IntPtr handle = SafeNativeMethods.ExtractAssociatedIcon(new HandleRef(null, IntPtr.Zero), iconPath, ref index);
        if (handle != IntPtr.Zero)
        {
            //IntSecurity.ObjectFromWin32Handle.Demand();
            return Icon.FromHandle(handle);
        }
    }
    return null;
}


/// <summary>
/// This class suppresses stack walks for unmanaged code permission. 
/// (System.Security.SuppressUnmanagedCodeSecurityAttribute is applied to this class.) 
/// This class is for methods that are safe for anyone to call. 
/// Callers of these methods are not required to perform a full security review to make sure that the 
/// usage is secure because the methods are harmless for any caller.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shell32.dll", EntryPoint = "ExtractAssociatedIcon", CharSet = CharSet.Auto)]
    internal static extern IntPtr ExtractAssociatedIcon(HandleRef hInst, StringBuilder iconPath, ref int index);
}

注意:任何代码均已发布到公共领域。无需归属。


谢谢你的代码! - tttony

2

实现这一目标的方法之一是获取您的UNC路径并将其临时映射到驱动器号,然后在您的.ExtractAssociatedIcon方法中使用该驱动器。当您检索到图标后,可以取消映射驱动器。

虽然不太优雅,但应该完全有效。


是的,我想过这个问题,如果用户不是管理员或者UAC,会不会有潜在的安全问题?在底层映射时,是否会存在问题?我想这可能涉及到权限提升的问题,还不确定,我会进一步调查。谢谢。 - Rahul

1

另一个选择是将用户选择的文件复制到他们的%TEMP%并在那里使用Icon.ExtractAssociatedIcon。只需记得在完成后进行清理。

如果您支持大文件,显然不是一个好的解决方案!


0

仅为补充Ian Boyd的答案,如果文件是图像,您可以使用FileStream来预览它,而不是文件图标:

foreach (string item in Directory.GetFiles(actualPath))
{

    fi = new FileInfo(item);

    using (FileStream stream = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read))
     {
          myImageList.Images.Add(Image.FromStream(stream));

     }
}

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