Wednesday, January 16, 2008

How to load unmanaged (native) resources from managed C# code

Let's say, that you have native assembly, that holds some resources -  strings, bitmaps, icons and images and you want to get them from your managed code. How to do it? First of all, let's see how those resources looks like

image

What we can see here, that we have one PNG resources with ID 209, one REGISTRY resource with ID 303 etc. So, let's extract them.

Looking in user32, we'll find two handy methods LoadImage and LoadBitmap. Both receives string names of the resources and handle to the loaded module, that holds those resources. You can not load the library by using Assembly class from reflection namespace, due to fact, that our module is native code. So, we should load it as datafile. How to do it?

kernel32 holds method, named LoadLibraryEx, that actually receives the path to our file and some flags. So, let's interop it

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);

Now all we have to do is to load our file with LOAD_LIBRARY_AS_DATAFILE (0x00000002) flag.

IntPtr hMod = LoadLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);

Now, we have a pointer to our module and we can begin to extract resources. But we do not know the names (there are not) of embedded resources. The only information we have is those IDs. Looking into small note in MSDN, we can assume, that it's possible to assume, that this method can receive MAKEINTRESOURCE macro, to convert resource ID into name, how to do it in C#? Simple.

Encoding.Default.GetString(BitConverter.GetBytes(rID))

Thus, we have write our own override to LoadBitmap, that receives C# int (long in C++). We can even use it with LoadImage enhanced method.

[DllImport("User32.dll")]
public static extern IntPtr LoadImage(IntPtr hInstance, int uID, uint type, int width, int height, int load);
[DllImport("User32.dll")]
public static extern IntPtr LoadBitmap(IntPtr hInstance, int uID);

However, this wont work, due to fact, that our resource is not Bitmap. It's PNG. What to do? We should load it as "other" resource. But first of all, we should find it and measure it's size. We'll use the same dirty trick to convert string name of the resource into it's ID.

[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, int lpID, string lpType);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

Well, but what's the type of the resource? It's "PNG" for some reason :) So, using following should bring us the pointer into our resource and it's size.

IntPtr hMod = LoadLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
IntPtr hRes = FindResource(hMod, 209, "PNG");
uint size = SizeofResource(hMod, hRes);
IntPtr pt = LoadResource(hMod, hRes);

Well, now we have our pointer. Let's make Bitmap. Simple? Not so fast... Using Bitmap.FromHbitmap(pt) will throw exception, because of the type of resource. It is not hBitmap. It's PNG byte array. Let's copy it and create Bitmap from stream

byte[] bPtr = new byte[size];
Marshal.Copy(pt, bPtr, 0, (int)size);
using (MemoryStream m = new MemoryStream(bPtr))
  bmp = (Bitmap)Bitmap.FromStream(m);

Now it works. We have Bitmap, that actually PNG, stores in our native resource file. Next step is to extract string resources. The same way? No. IMHO, String is the best class ever in every programming language, thus, we have special method to extract string resources from unmanaged code.

[DllImport("User32.dll")]
static extern int LoadString(IntPtr hInstance, int uID, StringBuilder lpBuffer, int nBufferMax);

So, following code will bring us strings, we are looking for.

StringBuilder sb = new StringBuilder();
int ln = LoadString(hMod, 210, sb, 255);

string s = sb.ToString();

Well done, we're fininshed. Actually, this is not very straight forward to load native resources, however, even with small brain as mine, you can do it.

4 comments:

Ramem said...

Hi, I am struggling with the code. I need to access the bitmap resource rather than PNG from a dll. This line fails always:
IntPtr hRes = FindResource(hMod, 101, "Bitmap"); // hRes is 0

I have tried many combinations:
FindResource(hMod, 101, "BMP");
FindResource(hMod, "101", "Bitmap"); // after fixing declaration
etc...

What am I missing?

Tamir Khason said...

You should check what is your bitmap id in manifest

Wojciech Sura said...

When you want to load a string, remember to set capacity of StringBuilder to the same value as the one you put in LoadString as the buffer capacity. Default StringBuilder capacity is 16 characters, so if you don't first expand it to declared 255 characters and find a string, which is longer, application will crash (buffer overrun).

Wojciech Sura said...

Also, don't forget to FreeLibrary to avoid memory leaks.