Icon.FromHandle: 我应该Dispose它还是调用DestroyIcon?

8
我使用Win32 SHGetFileInfo获取与某个文件相关联的图标句柄。有很多描述如何执行此操作的内容,例如在stackoverflow上:Get icons used by shell
调用该函数后,您将获得一个具有图标句柄的结构体。使用静态方法Icon.FromHandle,我可以将其转换为System.Drawing.Icon类的对象。该类实现了System.IDisposable。正确的使用方法应该像这样:
using (Icon icon = Icon.FromHandle(shFileInfo.hIcon))
{
    // do what you need to do with the icon
}

离开 using 语句后,图标对象将被处理。

Icon.FromHandle (点击查看) 的描述中,MSDN 警告:

使用此方法时,您必须使用 Win32 API 中的 DestroyIcon 方法处理原始图标,以确保释放资源。

并且在 Icon.Dispose (点击查看) 中:

释放此图标使用的所有资源。

问题:

只 Dispose() 对象是否足够?还是应该同时调用 Dispose() 和 DestroyIcon,或者只调用 DestroyIcon 而不是 Disposing 对象?

3个回答

8
.NET的Icon类非常笨重,需要自己处理。MSDN关于SHFILEICON的文章毫不含糊地指出,您必须调用DestroyIcon()方法。Icon.FromHandle()的MSDN文章也是如此。调用DestroyIcon()的确切时机也非常重要,它必须被延迟直到某些代码已经复制了图标,或者直到您不再需要该图标并通常会调用其Dispose()方法。

注意MSDN文章中的代码片段,它显示了一个早期调用DestroyIcon()的场景。在这种特定情况下,它被分配给Form.Icon属性。这是一个边角案例,肯定不是您想做的。

唯一真正好的处理方法是覆盖Icon.FromHandle()的行为,并强制对象拥有本地图标句柄的所有权。这样当您处置它时,它将自动调用DestroyIcon()。这需要一个hack,即允许您执行此操作的Icon构造函数是internal的。通过反射解决问题,您可以使用此帖子中的代码,注意GetConstructor()调用。通过编写一个执行此操作一百万次的小单元测试来感觉良好。如果您不喜欢它,则可以编写自己的IDisposable包装器,以便既可以处置图标,又可以调用DestroyIcon()。


7

在这个领域中,我遇到了无尽的麻烦 - 我一直试图在不泄露资源的情况下使表单的图标(以及任务栏中的图标)动画化。

当我处理图标时(如MSDN上建议的),资源泄漏了,当我使用“DestroyIcon”时,所有后续更新都失败了。下面的代码显示了正确顺序中的所有内容。

API声明:

[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);

最终解决方案:

IntPtr iconHandle = dynamicBitmap.GetHicon();
Icon tempManagedRes = Icon.FromHandle(iconHandle);
this.Icon = (Icon)tempManagedRes.Clone();
tempManagedRes.Dispose();
DestroyIcon(iconHandle);

此问题也发布在: Win32.DestroyIcon vs. Icon.Dispose


1
正确的做法是,在调用DestroyIcon()时,确保只调用一次GetHicon(),否则会出现内存泄漏。 - Sophit

6
补充说明:这个回答有错误。因为所有的评论,很难看清问题的本质。因此我决定编辑这个回答。(如果有冒犯之处,请见谅)
.net源代码在网上可以查看:http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Icon.cs,81a28d20524554ae 看一下Icon.FromHandle:
public static Icon FromHandle(IntPtr handle)
{
    IntSecurity.ObjectFromWin32Handle.Demand();
    return new Icon(handle);
}
internal Icon(IntPtr handle) : this(handle, false)
{
}
internal Icon(IntPtr handle, bool takeOwnership)
{
    if (handle == IntPtr.Zero)
    {
        throw new ArgumentException(SR.GetString(SR.InvalidGDIHandle,
              (typeof(Icon)).Name));
    }
    this.handle = handle;
    this.ownHandle = takeOwnership;
}

请注意,Icon.FromHandle之后,ownHandle为false。
让我们看一下Dispose方法:
void Dispose(bool disposing)
{
    if (handle != IntPtr.Zero)
    {
        DestroyHandle();
    }
}

internal void DestroyHandle()
{
    if (ownHandle)
    {
        SafeNativeMethods.DestroyIcon(new HandleRef(this, handle));
        handle = IntPtr.Zero;
    }
}

结论:在使用Icon.FromHandle后,ownHandle字段为false,因此Dispose / FromHandle不会调用DestroyIcon。
因此:如果您使用Icon.FromHandle创建图标,则必须像注释部分所说的那样同时调用Dispose()和DestroyIcon。

谢谢,我不知道源代码是公开的。不过很奇怪MSDN建议你同时Dispose对象和调用DestroyIcon。 - Harald Coppoolse
1
针对所有正在寻找一个无麻烦的Bitmap to Icon转换器的C#实现: https://gist.github.com/D4koon/2b397eca452b75115f19063560efbbb3 - Sebastian Ax

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