3
votes

The Problem: After using this code in a loop 3322 times (1246 times using the bottom method), a generic GDI+ exception is thrown at GetHIcon().

Sample Project: http://dl.dropbox.com/u/18919663/TestGDICursorDrawing.zip

What I'm trying to do: Draw a new cursor from a bitmap in a loop to do a simple focusing animation.

What I've already checked: I made sure that all bitmaps and graphics are being disposed and monitored memory leaks to make sure. Also made sure no other process had a visible leak. Tried alternative methods and ways to ensure Bitmaps were being used correctly.

What Google has told me: There seems to be a bug in GDI+ and nobody has offered a solution. One person tried to create his own Bitmap to Icon converter, but it's not flexible enough to do non-generic image sizes.

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;
}

Other Method I tried:

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 
}
2
Why are you trying to create the cursor dynamically from a bitmap? Why don't you just include a cursor resource with your application? - Cody Gray
This is animated currently, so it would be a few cursors. However, in many situations when I use this, it's for drag and drops, and the drag image has custom information that mirrors what they are dragging, so that needs to be dynamic. Which is why I am looking for a solution rather than just not doing it that way. - corylulu
Do you dispose of the cursors when done? - 500 - Internal Server Error
@500-InternalServerError It's actually not really possible when I'm done, but I can do it on my next call, so what I did was dispose of the current cursor every time I set a new one and used the DestroyIcon(). I eventually got it working after doing that. All the other Disposes were working fine, it was the DestroyIcon() call that needed to be made before I set a new cursor. - corylulu

2 Answers

6
votes

This problem definitely looks like a memory leak to me, given its symptoms. It runs fine for a while, then blows up.

And as it turns out, the second method you tried is leaking GDI objects badly. When you call GetIconInfo to fill an ICONINFO structure, it actually creates two bitmaps corresponding to the icon/cursor, hbmMask and hbmColor. You must call DeleteObject to delete these when you are finished using them, otherwise you'll leak them. According to the Remarks section of the documentation:

GetIconInfo creates bitmaps for the hbmMask and hbmColor members of ICONINFO. The calling application must manage these bitmaps and delete them when they are no longer necessary.

That's not the only leak you've got here, either. With either method, I see at least two additional leaks:

  • The Bitmap.GetHicon method requires you to call DestroyIcon when you get finished using the icon. You haven't done that either, so you're leaking that icon each time.

  • You're not disposing the Bitmap, Graphics, GraphicsPath, and Cursor objects that you're creating inside the tight while loop in the DrawRingAroundCursor until the very end, which means all those temporary objects created for each iteration are leaked. (I recommend wrapping the creation of GDI+ objects in a using statement, rather than trying to remember to call their Dispose methods.)

When you fix all of those leaks, the execution more than doubles, making it so that I can't even see the concentric circles show up anymore. But I still can't get it to run indefinitely without crashing, so there's bound to be some more leaks in there that I just haven't found.

Stuff like Thread.Sleep also raises red flags and makes loud warning bells go off in my head.

Perhaps here is a good time to say that I strongly urge you to try a different design? Creating all of these objects, even if you're properly managing their lifetimes, is going to be relatively expensive and seems rather unnecessary. Moreover, as soon as the user moves the cursor outside of your application's window and over some other object, Windows will send a new WM_SETCURSOR message to the new hovered window and it will change the cursor to something else entirely. It's all too easy to make this effect "go away" with minimal effort.

2
votes

Please, use DestroyIcon after GetHicon, to prevent memory leak

[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

My code sample :

 [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;
 }