Icon.FromHandle:我应该处置它,还是调用 DestroyIcon?

Icon.FromHandle: should I Dispose it, or call DestroyIcon?

我使用 Win32 SHGetFileInfo 获取属于某个文件的图标的句柄。有很多描述如何做到这一点,也在 Whosebug 上,例如:Get icons used by shell

调用该函数后,您将获得一个带有图标句柄的结构。使用静态方法 Icon.FromHandle 我可以将它转换为 class System.Drawing.Icon 的对象。此 class 实现 System.IDisposable。正确的用法是:

using (Icon icon = Icon.FromHandle(shFileInfo.hIcon))
{
    // do what you need to do with the icon
}

离开 using 语句后,图标对象被释放。

MSDN 在 Icon.FromHandle (click to view) 的描述中警告:

When using this method, you must dispose of the original icon by using the DestroyIcon method in the Win32 API to ensure that the resources are released.

并且在Icon.Dispose (click to view)

Releases all resources used by this Icon.

问题:

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

Addition by OP. There is an error in this answer. Because of all the comments it became harsh to see the forest through the trees. Hence I decided to edit this answer. (Sorry if I offended someone)

.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

Therefore: if you create an Icon using Icon.FromHandle you'll have to Dispose() the Icon as well as call DestroyIcon, just as the remarks section says

.NET 图标 class 非常笨拙,需要您自己处理。 SHFILEICON 的 MSDN 文章对此毫不掩饰,您必须 调用 DestroyIcon()。 Icon.FromHandle() 的 MSDN 文章也是如此。您调用 DestroyIcon 的确切时间也很重要,它必须延迟到 some 代码已经复制了图标或者直到您不再需要该图标并且愿意通常调用它的 Dispose() 方法。

注意 MSDN 文章中的代码片段,它显示了提前调用 DestroyIcon() 的场景。在 特定的 情况下可以,因为它已分配给 Form.Icon 属性。一个极端的案例,肯定不是你想做的。

处理这个问题的唯一真正体面的方法是覆盖 Icon.FromHandle() 的行为并强制对象取得本机图标句柄的所有权。这样当你处理它时它会自动调用 DestroyIcon() 。这需要 hack,允许您执行此操作的 Icon 构造函数是 internal。反思救援,您可以使用代码 from this post,注意 GetConstructor() 调用。通过编写一个执行此操作一百万次的小单元测试开始感觉良好。如果你讨厌它,那么写你自己的 IDisposable 包装器,这样你就可以同时处理图标 pinvoke DestroyIcon().

我在这方面的悲伤没有尽头 - 我一直在尝试在不泄漏资源的情况下为表单的图标(以及任务栏中的图标)制作动画。

当我处理图标(如 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