How to load an embedded Cursor from resources in C# (Windows Forms)

How to load an embedded cursor from resources – one that has 32-bit colour, is a trick that has eluded me for a long time, until now.

Wait. What’s that, you say? The managed Cursor type does not support animated cursors (.ani files) or cursors with colours other than black and white. Shut up! Fuck… Yes, and this is why I have never asked the question on StackOverflow. Ten thousand copy-and-paste programmers will be sure to tell me it can’t be done.

If you have more than a couple of brain cells and you actually think about it, it should be obvious that this can be done, in much the same way as you can load a custom icon from an unmanaged native icon handle. The real question is, which Windows API function is the appropriate one to call? I tried all the documented Cursor functions in the MSDN documentation a long time ago, and to my dismay, none of them solved this problem. Thus for a long time, I used an annoying hack to work around it: Create temporary files from my resource cursors, then read them using the LoadCursorFromFile API function. Obviously this function should not be necessary, but I figured at least it works, and it shows that creating a managed Cursor instance from an unmanaged Cursor handle does work and that one day I will find the correct function to use when reading it from resources instead. That day is today.

Quite by accident I stumbled on the correct function: CreateIconFromResource, which can load icons or cursors. I couldn’t find it before because of the function’s name, and it is neither grouped with, nor linked to the cursor functions in the documentation.

So here’s the code. This is based on an example at the pinvoke.net page for CreateIconFromResource, except that the original author omitted to destroy the native cursor handle. (It’s one of my pet peeves. In almost every code sample I find online, the author either omits freeing up some memory for something when it is clearly documented as a requirement, or they implement IDisposable incorrectly.)

Using this code is as easy as adding the cursor to your application resources, then load it with something along the lines of:

handUpCursor = CursorResourceLoader.LoadEmbeddedCursor(Properties.Resources.Hand_up);
Load a cursor from resources in C#
  1. public static class CursorResourceLoader
  2. {
  3.     #region Methods
  4.  
  5.     public static Cursor LoadEmbeddedCursor(byte[] cursorResource, int imageIndex = 0)
  6.     {
  7.         var resourceHandle = GCHandle.Alloc(cursorResource, GCHandleType.Pinned);
  8.         var iconImage = IntPtr.Zero;
  9.         var cursorHandle = IntPtr.Zero;
  10.  
  11.         try
  12.         {
  13.             var header = (IconHeader)Marshal.PtrToStructure(resourceHandle.AddrOfPinnedObject(), typeof(IconHeader));
  14.  
  15.             if (imageIndex >= header.count)
  16.                 throw new ArgumentOutOfRangeException(“imageIndex”);
  17.  
  18.             var iconInfoPtr = resourceHandle.AddrOfPinnedObject() + Marshal.SizeOf(typeof(IconHeader)) + imageIndex * Marshal.SizeOf(typeof(IconInfo));
  19.             var info = (IconInfo)Marshal.PtrToStructure(iconInfoPtr, typeof(IconInfo));
  20.  
  21.             iconImage = Marshal.AllocHGlobal(info.size + 4);
  22.             Marshal.WriteInt16(iconImage + 0, info.hotspot_x);
  23.             Marshal.WriteInt16(iconImage + 2, info.hotspot_y);
  24.             Marshal.Copy(cursorResource, info.offset, iconImage + 4, info.size);
  25.  
  26.             cursorHandle = NativeMethods.CreateIconFromResource(iconImage, info.size + 4, false, 0x30000);
  27.             return new Cursor(cursorHandle);
  28.         }
  29.         finally
  30.         {
  31.             if (cursorHandle != IntPtr.Zero)
  32.                 NativeMethods.DestroyIcon(cursorHandle);
  33.  
  34.             if (iconImage != IntPtr.Zero)
  35.                 Marshal.FreeHGlobal(iconImage);
  36.  
  37.             if (resourceHandle.IsAllocated)
  38.                 resourceHandle.Free();
  39.         }
  40.     }
  41.  
  42.     #endregion Methods
  43.  
  44.     #region Native Methods
  45.  
  46.     static class NativeMethods
  47.     {
  48.         [DllImportAttribute(“user32.dll”, CharSet = CharSet.Unicode)]
  49.         [return: MarshalAs(UnmanagedType.Bool)]
  50.         public static extern bool DestroyIcon(IntPtr hIcon);
  51.  
  52.         [DllImport(“user32.dll”, SetLastError = true)]
  53.         public static extern IntPtr CreateIconFromResource(IntPtr pbIconBits, int dwResSize, bool fIcon, int dwVer);
  54.     }
  55.  
  56.     #endregion Native Methods
  57.  
  58.     #region Native Structures
  59.  
  60.     [StructLayout(LayoutKind.Explicit, Pack = 1)]
  61.     struct IconHeader
  62.     {
  63.         [FieldOffset(0)]
  64.         public short reserved;
  65.  
  66.         [FieldOffset(2)]
  67.         public short type;
  68.  
  69.         [FieldOffset(4)]
  70.         public short count;
  71.     }
  72.  
  73.     /// <summary>Union structure for icons and cursors.</summary>
  74.     /// <remarks>For icons, field offset 4 is used for planes and field offset 6 for
  75.     /// bits-per-pixel, while for cursors field offset 4 is used for the x coordinate
  76.     /// of the hotspot, and field offset 6 is used for the y coordinate.</remarks>
  77.     [StructLayout(LayoutKind.Explicit, Pack = 1)]
  78.     struct IconInfo
  79.     {
  80.         [FieldOffset(0)]
  81.         public byte width;
  82.  
  83.         [FieldOffset(1)]
  84.         public byte height;
  85.  
  86.         [FieldOffset(2)]
  87.         public byte colors;
  88.  
  89.         [FieldOffset(3)]
  90.         public byte reserved;
  91.  
  92.         [FieldOffset(4)]
  93.         public short planes;
  94.  
  95.         [FieldOffset(6)]
  96.         public short bpp;
  97.  
  98.         [FieldOffset(4)]
  99.         public short hotspot_x;
  100.  
  101.         [FieldOffset(6)]
  102.         public short hotspot_y;
  103.  
  104.         [FieldOffset(8)]
  105.         public int size;
  106.  
  107.         [FieldOffset(12)]
  108.         public int offset;
  109.     }
  110.  
  111.     #endregion Native Structures
  112. }
Advertisements

About Jerome

I am a senior C# developer in Johannesburg, South Africa. I am also a recovering addict, who spent nearly eight years using methamphetamine. I write on my recovery blog about my lessons learned and sometimes give advice to others who have made similar mistakes, often from my viewpoint as an atheist, and I also write some C# programming articles on my programming blog.
This entry was posted in Programming and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s