【问题标题】:Generic GDI+ Exception while drawing Cursor ~3322 times绘制光标时出现通用 GDI+ 异常 ~3322 次
【发布时间】:2012-05-20 09:52:05
【问题描述】:

问题:在循环中使用此代码 3322 次(使用底部方法 1246 次)后,在 GetHIcon() 处引发了通用 GDI+ 异常。

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

我正在尝试做的事情: 从位图中循环绘制一个新光标来做一个简单的聚焦动画。

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

Google 告诉我的: GDI+ 中似乎存在错误,没有人提供解决方案。一个人试图创建自己的位图到图标转换器,但它不够灵活,无法处理非通用图像尺寸。

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 
}

【问题讨论】:

  • 为什么要从位图动态创建光标?为什么不在应用程序中包含光标资源?
  • 这是目前动画,所以它会是几个光标。但是,在很多情况下,当我使用它时,它是用于拖放,并且拖动图像具有反映他们拖动的内容的自定义信息,因此需要是动态的。这就是为什么我正在寻找解决方案,而不是不这样做。
  • 完成后你会处理游标吗?
  • @500-InternalServerError 完成后实际上不太可能,但我可以在下次调用时执行此操作,所以我所做的就是每次设置新光标时都处理当前光标并使用了 DestroyIcon()。这样做之后,我最终让它工作了。所有其他 Dispose 工作正常,在我设置新光标之前需要进行 DestroyIcon() 调用。

标签: c# .net bitmap cursor gdi+


【解决方案1】:

请在 GetHicon 之后使用 DestroyIcon,以防止内存泄漏

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

MSDN:https://msdn.microsoft.com/en-us/library/system.drawing.bitmap.gethicon%28v=vs.110%29.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;
 }

【讨论】:

    【解决方案2】:

    考虑到它的症状,这个问题绝对对我来说似乎是内存泄漏。它运行了一段时间,然后爆炸。

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

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

    这也不是您在这里遇到的唯一泄漏。使用 either 方法,我发现至少有两个额外的泄漏:

    • Bitmap.GetHicon 方法要求您在使用完图标后调用 DestroyIcon。您也没有这样做,因此您每次都在泄漏该图标。

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

    当您修复所有这些漏洞时,执行次数会增加一倍以上,以至于我什至看不到同心圆出现了。但是我仍然无法让它无限期地运行而不崩溃,所以肯定会有更多的漏洞我还没有发现。

    Thread.Sleep 之类的东西也会引发危险信号,并让我脑海中响起响亮的警钟。

    也许现在是我强烈建议您尝试不同设计的好时机?创建所有这些对象,即使您正确地管理它们的生命周期,也将相对昂贵并且似乎相当不必要。此外,只要用户将光标移出应用程序窗口并移到其他对象上,Windows 就会向新的悬停窗口发送新的WM_SETCURSOR 消息,并将光标完全更改为其他对象。用最小的努力使这种效果“消失”太容易了。

    【讨论】:

    • 感谢您的帮助。我尝试了很多这些事情,但没有解决问题。我将继续尝试找出为什么会这样,但也会研究其他方法。因为我会经常使用这个,也许我可以只调用一次这个方法,一旦我创建了它们,就将它们全部存储并重用它们。我知道将它们存储为文件会更有效,但这对于我正在尝试做的事情似乎非常不必要。
    • 就我尝试的另一种方法而言,它只是我不久前在 Stack Overflow 上找到的一些通用方法。不过你说的很有道理。感谢您的详细说明。不知道他们为什么首先采用这种方法。虽然 Thread.Sleep 只是为了减慢动画的速度,但有什么更好的方法呢?我想我可以在另一个线程上执行此操作并在每次调用时调用该方法,但这似乎效率更低。由于它只是为了在重新定位鼠标后产生快速效果,因此在短时间内占用 UI 线程应该不是问题。
    猜你喜欢
    • 1970-01-01
    • 2013-08-11
    • 1970-01-01
    • 2021-07-30
    • 2013-09-03
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    相关资源
    最近更新 更多