在绘制光标时发生通用GDI+异常 ~3322次

3
问题:在使用这段代码循环绘制3322次后(其中1246次使用底部方法),GetHIcon()抛出了一个通用的GDI+异常。

示例项目:http://dl.dropbox.com/u/18919663/TestGDICursorDrawing.zip

我想做什么:从位图中绘制新的光标,以实现简单的聚焦动画。

我已经检查过的事项:确保所有的位图和图形都被处理并监控内存泄漏情况。还确保没有其他进程有可见的泄漏。尝试了其他方法和方式来确保正确地使用位图。

Google告诉我的: GDI+存在一个bug,目前没有人提供解决方案。有人尝试创建自己的位图到图标转换器,但这不足以处理非通用图像大小。

public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
{
    //Shows me exactly when the error occurs.
    counter++;
    Console.WriteLine(counter + " GetHicon() calls");

    //GetHicon() is the trouble maker. 
    var newCur = new Cursor(bmp.GetHicon());
    bmp.Dispose();
    bmp = null;

    return newCur;
}

我尝试的其他方法:

public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
{
    //Tried this method too, but this method results in an error with even fewer loops.
    Bitmap newBitmap = new Bitmap(bmp);
    // was told to try to make a new bitmap and dispose of the last to ensure that it wasn't locked or being used somewhere. 
    bmp.Dispose();
    bmp = null;
    //error occurs here. 
    IntPtr ptr = newBitmap.GetHicon();
    ICONINFO tmp = new ICONINFO();
    GetIconInfo(ptr, ref tmp);
    tmp.xHotspot = xHotSpot;
    tmp.yHotspot = yHotSpot;
    tmp.fIcon = false;
    ptr = CreateIconIndirect(ref tmp);

    newBitmap.Dispose();
    newBitmap = null;

    return new Cursor(ptr);
}


[DllImport("user32.dll", EntryPoint = "GetIconInfo")]
public static extern bool GetIconInfo(IntPtr hIcon, ref ICONINFO piconinfo);

[DllImport("user32.dll")]
public static extern IntPtr CreateIconIndirect(ref ICONINFO icon);

[StructLayout(LayoutKind.Sequential)]
public struct ICONINFO
{
    public bool fIcon;         // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies 
    public Int32 xHotspot;     // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot 
    public Int32 yHotspot;     // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot 
    public IntPtr hbmMask;     // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, 
    public IntPtr hbmColor;    // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this 
}

你为什么要动态地从位图创建光标呢?为什么不在应用程序中包含一个光标资源呢? - Cody Gray
目前这是动画效果,所以会有几个光标。然而,在许多情况下,当我使用它进行拖放操作时,拖动图像具有自定义信息,反映了他们正在拖动的内容,因此需要是动态的。这就是为什么我正在寻找解决方案而不仅仅是不采用这种方式的原因。 - corylulu
1
完成后,您是否处理了游标? - 500 - Internal Server Error
@500-服务器内部错误 实际上,当我完成时这是不太可能的,但我可以在下一个调用中完成它,所以我所做的是每次设置新光标时处理当前光标并使用DestroyIcon()。在这样做之后,我最终使其正常工作。所有其他的Dispose都很好用,只有在设置新光标之前需要进行DestroyIcon()调用。 - corylulu
2个回答

7

这个问题看起来明显是内存泄漏,根据其症状判断。它在运行一段时间后会崩溃。

事实证明,你尝试的第二种方法严重泄漏 GDI 对象。当你调用 GetIconInfo 填充一个 ICONINFO 结构时,它实际上会创建两个位图对应于图标/光标,即 hbmMaskhbmColor。你必须在使用完它们后调用 DeleteObject 来删除它们,否则就会泄漏它们。 根据文档的备注部分:

GetIconInfoICONINFOhbmMaskhbmColor 成员创建了位图。调用应用程序必须管理这些位图并在不再需要它们时将它们删除。

这不是你唯一的泄漏,无论采用哪种方法,我都至少发现了另外两个泄漏:

  • Bitmap.GetHicon 方法要求你在完成使用图标后调用 DestroyIcon。你也没有这样做,所以每次都会泄漏该图标。

  • DrawRingAroundCursor 的紧密的 while 循环中创建的 BitmapGraphicsGraphicsPathCursor 对象,在最后才释放,这意味着为每个迭代创建的所有临时对象都会泄漏。(我建议将 GDI+ 对象的创建包装在 using 语句中,而不是试图记住调用它们的 Dispose 方法。)

当你修复了所有这些泄漏后,执行时间超过两倍,以至于我甚至看不见同心圆了。但是我仍然无法让它无限期地运行而不崩溃,因此肯定还有更多的泄漏我还没找到。

Thread.Sleep这样的东西也引起了我的警惕。

也许这是一个好时机来强烈建议你尝试不同的设计?即使你正确地管理它们的生命周期,创建所有这些对象也将相对昂贵并且似乎非常不必要。另外,一旦用户将光标移动到你应用程序窗口之外并且悬停在其他对象上,Windows 将向新悬停的窗口发送新的 WM_SETCURSOR 消息,它将将光标更改为完全不同的东西。轻松地让这种效果“消失”是非常容易的。


感谢您的帮助。我尝试了很多方法,但问题并没有得到解决。我将继续尝试找出原因,同时也会考虑其他方法。由于我经常使用这个,也许我只需要调用一次这个方法,然后创建所有对象后将它们存储起来以便重复使用。我知道将它们存储为文件会更有效率,但对于我所要做的事情来说,这似乎非常不必要。 - corylulu
至于我尝试的另一种方法,那只是我在一段时间前在Stack Overflow上找到的某种通用方法。但你说的很有道理。谢谢你的详细解释。不确定他们最初为什么会选择那种方法。然而,Thread.Sleep只是为了减缓动画速度,有更好的方法吗?我想我可以在另一个线程上执行它,并在每次调用时调用该方法,但这似乎更加低效。既然它只是在重新定位鼠标后进行快速效果,那么将UI线程占用短暂的时间应该不会有问题。 - corylulu

2
请在使用GetHicon之后使用DestroyIcon来防止内存泄漏。
[DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);

MSDN: https://msdn.microsoft.com/zh-cn/library/system.drawing.bitmap.gethicon(v=vs.110).aspx

我的代码示例:

 [DllImport("user32.dll", CharSet = CharSet.Auto)]
 extern static bool DestroyIcon(IntPtr handle);
 public static Icon ConvertoToIcon(Bitmap bmp)
 {
     System.IntPtr icH = bmp.GetHicon();
     var toReturn = (Icon)Icon.FromHandle(icH).Clone();
     DestroyIcon(icH);
     return toReturn;
 }

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