Search code examples
c#winapimousegdi

How can I render the mouse cursor texture in c#?


Here's what I have so far:

[StructLayout(LayoutKind.Sequential)]
struct CursorInfo
{
  public Int32 cbSize;
  public Int32 flags
  public IntPtr hCursor;
  public POINT ptScreenPos;
}

[DllImport("user32.dll")]
static extern int GetSystemMetrics(SystemMetric smIndex);

public enum SystemMetric
{
  SM_CXICON              = 11, // 0x0B
  SM_CYICON              = 12, // 0x0C
}


[DllImport("user32.dll", SetLastError = true)]
static extern bool DrawIconEx(IntPtr hdc,
  int xLeft,
  int yTop,
  IntPtr hIcon,
  int cxWidth,
  int cyHeight,
  int istepIfAniCur,
  IntPtr hbrFlickerFreeDraw,
  int diFlags);

const int DI_MASK = 0x0001;
const int DI_IMAGE = 0x0002;
const int DI_NORMAL = 0x0003;
const int DI_COMPAT = 0x0004;
const int DI_DEFAULTSIZE = 0x0008;
const int DI_NOMIRROR = 0x0010;

public struct IconInfo
{
  public bool fIcon;
  public int xHotspot;
  public int yHotspot;
  public IntPtr hbmMask;
  public IntPtr hbmColor;
}

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);



Bitmap GetCursorBitmap()
{
  CursorInfo ci = new CursorInfo ();
    ci.cbSize = Marshal.SizeOf (typeof(CursorInfo));

  if (!GetCursorInfo (ref ci)) {
    throw new Exception("Failed to get cursor info");
  }

  IntPtr cursorPointer = ci.hCursor;

  int iconWidth = GetSystemMetrics (SystemMetric.SM_CXICON);
  int iconHeight = GetSystemMetrics (SystemMetric.SM_CYICON);

  Bitmap bmp;
  bmp = new System.Drawing.Bitmap(iconWidth, iconHeight, PixelFormat.Format32bppRgb);
  bmp.MakeTransparent();

  Graphics gfxBmp = Graphics.FromImage(bmp);       

  IntPtr hdcBitmap = gfxBmp.GetHdc();

  DrawIconEx(hdcBitmap, 0, 0, cursorPointer, iconWidth, iconHeight, 0, IntPtr.Zero, DI_NORMAL);

  // DrawIcon(hdcBitmap, 0, 0, cursorPointer); has the same problem

  IconInfo hotSpotInfo = new IconInfo ();
  GetIconInfo(cursorPointer, ref hotSpotInfo);

  Point hotSpot = new Point(hotSpotInfo.xHotspot, hotSpotInfo.yHotspot)

  gfxBmp.ReleaseHdc(hdcBitmap);               
  gfxBmp.Dispose();

  return bmp;
}

(I use the hotspot info elsewhere, but I'm omitting that part because what matters here is getting that info).

This works for quite a while, but eventually I get an error saying

A null reference or invalid value was found [GDI+ status: InvalidParameter]

Coming from the

IntPtr hdcBitmap = gfxBmp.GetHdc();

I'm fairly certain this is due to a memory leak. I call this method every update step in my application (about 30 steps per second), so I could believe that if there is one it would show up fairly shortly, as this one does. Where is the memory leak though? Or is there some other problem here?


Solution

  • public IntPtr hbmMask;
    public IntPtr hbmColor;
    

    Both of these fields end up containing bitmap handles after the call to GetIconInfo() and these must be released with a p/invoke call to DeleteObject().

    Don't forget to dispose the bmp in the caller.