This weekend I was thinking about writing a tool that would extract icons from a library or executable. As you know, some system libraries (like shell32.dll or user32.dll) have integrated resources, that can be bitmaps, icons, cursors and whatnot. The reason why I needed those icons is to be able to build consistent UIs with the Windows OS instance I am currently running.
It appeared to me a fairly easy task, but there were some interesting nuances I didn't consider before. Initially I thought about getting all image resources from a library at once. LoadImage was the perfect function for this, however it required the user to specify the resource ID and I had none of those. So I needed to somehow get the list of resources. For this purpose, I looked at EnumResourceNames, that, according to MSDN:
Enumerates resources of a specified type within a binary module.
Perfect! I now needed to add a signature that will allow me calling this method, and here is what I initially came up with:
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool EnumResourceNames(IntPtr hModule, int lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
Seems to be the exact implementation, however with some serious flaws, as I will discuss later. What this function required is a handle of the actual library (or executable) that contains the resources. Yet another P/Invoke:
public extern static IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, int dwFlags);
I got this one right and whenever I wanted to load a specific library for resource analysis, I simply did this:
IntPtr libHandle = LoadLibraryEx(@"C:\windows\system32\shell32.dll", IntPtr.Zero, 2);
With the handle available, I thought that I can now go back to EnumResourceNames, pass it and enumerate every single icon available. Notice the fact that I am also passing an EnumResNameProc delegate, that will be triggered each time an item is found.
My implementation for it was simple enough:
public delegate bool EnumResNameProc(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
Here is the actual method that is passed as an implementation:
public bool ListCallback(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam)
The method always returns true and the enumeration will stop when all resources are listed. You can modify this to drop the enumeration process at any time (for example, when a specific name or ID is hit).
Initially, I tried calling EnumResourceNames with a integer parameter for the type - int lpszType. However, it didn't go that well - I was constantly getting a Win32 error identified as -532462766.
This error code was not listed in the default document, so I assumed that I hit something on a much lower level than I should've hit. Thanks to Hans Passant, the error was found - the type should not be passed as an int but as an IntPtr instead. That being said, I took it even further as to use the unmanaged LPStr, as it is required in the documentation. So here is what I got:
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool EnumResourceNames(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
Now it worked perfectly for some resource types, but not for the others. Here is a piece of sample code:
EnumResourceNames(libHandle, "RT_GROUP_ICON", new EnumResNameProc(ListCallback), IntPtr.Zero);
The same library handler is now used to get the list of icons from shell32.dll - after all, it has those inside it. But the call will fail fiving you a 1813 - resource type not found. The problem with this lies in the fact that some resource groups can be referenced by a name and some can be referenced by an identifier.
This is also stated in the official documentation:
If IS_INTRESOURCE(lpszType) is TRUE, then lpszType specifies the integer identifier of the given resource type. Otherwise, it is a pointer to a null-terminated string. If the first character of the string is a pound sign (#), then the remaining characters represent a decimal number that specifies the integer identifier of the resource type. For example, the string "#258" represents the identifier 258.
To experiment with this situation, I opened shell32.dll in Visual Studio and here is what I saw:
It's one of those "notice the difference" cases. If the resource group has quotes around it's name, it can be referenced by that name. For example, this will yield the correct results:
EnumResourceNames(libHandle, "XML", new EnumResNameProc(ListCallback), IntPtr.Zero);
Resource groups that don't have quotes cannot be accessed directly by passing the name and should be referenced by the identifier (a list of standard resource type identifiers can be found here). RT_ICON has the ID set to 3, therefore I can pass this number with a starting pound (#) sign in the string to get all items in that group:
EnumResourceNames(libHandle, "#3", new EnumResNameProc(ListCallback), IntPtr.Zero);
And now I will get a list of all icons. As you can see, there are specific points that you should consider and sometimes the most obvious implementations aren't the correct ones. If you see that an API call requires an integer, remember that in some cases that won't be an actual integer in a managed application. Same applies to strings and many other data types.