Question


(source: khason.net)

var ID = 1234;
var FilePath = "C:\\file.dll";
IntPtr hMod = LoadLibraryEx(FilePath, IntPtr.Zero, 2); //LOAD_LIBRARY_AS_DATAFILE = 2
IntPtr hRes = FindResource(hMod, "#" + ID, "PNG");
byte[] Bytes = new byte[SizeofResource(hMod, hRes)];
Marshal.Copy(LoadResource(hMod, hRes), Bytes, 0, Bytes.Length);
FreeLibrary(hMod);
System.IO.File.WriteAllBytes("C:\\img.png", Bytes);

The above code works fine for PNG and other custom types, but it doesn't work for BITMAP, I tried all possible combinations:

FindResource(hMod, "#" + ID, "RT_BITMAP");
FindResource(hMod, "#" + ID, "BITMAP");
FindResource(hMod, "#" + ID, "Bitmap");
FindResource(hMod, "#" + ID, "BMP");
FindResource(hMod, ID, "Bitmap"); //also changed P/Invoke signature
FindResource(hMod, ID, "BITMAP"); //...
FindResource(hMod, ID, "BMP");

Does anyone know what am I missing here?

I don't want to use LoadBitmap here, as this function can serve all my needs.

EDIT:

The following returns some data which is of the correct size* but not the correct contents (like some kind of different encoding):

FindResource(hMod, "#" + ID, "#2"); //RT_BITMAP = 2
FindResource(hMod, ID, 2); //RT_BITMAP = 2; changed sig

*about 12-14 bytes less every time

P/Invoke signatures:

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

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool FreeLibrary(IntPtr hModule);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, int lpName, int lpType);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr LockResource(IntPtr hResData);
Was it helpful?

Solution

The fundamental problem is that RT_BITMAP resources are not stored in the format of a bitmap file. If you save the raw data to a file, you don't end up with a valid bitmap file. The raw data is designed to be interpreted by LoadBitmap or LoadImage to create an HBITMAP.

More details are given here:

If, instead of calling LoadBitmap(), the application calls FindResource() (with RT_BITMAP type), LoadResource(), and LockResource(), a pointer to a packed DIB will be the result. A packed DIB is a BITMAPINFO structure followed by an array of bytes containing the bitmap bits.

So, if you are absolutely dead set against using LoadBitmap or LoadImage then you will have to work out how to turn a packed DIB into a bitmap file. Essentially you will need to write out the appropriate bitmap file header, and then follow it with the packed DIB data.

In essence the code might look like this. First of all defined the file header type.

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct BitmapFileHeader
{
    public ushort id;
    public int size;
    public ushort res1;
    public ushort res2;
    public int offset;
}

Then put the various sizes into local variables for convenience:

int resSize = SizeofResource(hMod, hRes);
int headerSize = Marshal.SizeOf(typeof(BitmapFileHeader));

Then allocate enough space for the file content:

byte[] Bytes = new byte[headerSize + resSize];

Now populate the header:

BitmapFileHeader header;
header.id = 0x4D42;
header.size = Bytes.Length;
header.res1 = 0;
header.res2 = 0;
header.offset = headerSize + 40; 
// magic constant, size of BITMAPINFOHEADER

Finally, fill out the byte array with file header, followed by packed DIB:

IntPtr headerPtr = Marshal.AllocHGlobal(headerSize);
try
{
    Marshal.StructureToPtr(header, headerPtr, false);
    Marshal.Copy(headerPtr, Bytes, 0, headerSize);
    Marshal.Copy(pRes, Bytes, headerSize, resSize);
}
finally
{
    Marshal.FreeHGlobal(headerPtr);
}

You can then save the byte array to disk as before and you should be good to go.


The declaration of FindResource is as follows:

HRSRC WINAPI FindResource(
  _In_opt_  HMODULE hModule,
  _In_      LPCTSTR lpName,
  _In_      LPCTSTR lpType
);

Although lpName are lpType declared as null-terminated C strings, they are not always of that form. They can be formed using the MAKEINTRESOURCE macro. For which the documentation says:

The return value is the specified value in the low-order word and zero in the high-order word.

And this is indeed how RT_BITMAP is defined. It is MAKEINTRESOURCE(2) as detailed in the resource types documentation.

So you should add some overloaded p/invoke declarations:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, string lpType);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, IntPtr lpType);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);

You can then define RT_BITMAP as follows:

public const uint RT_BITMAP = 0x00000002;

And then when you call the function pass

(IntPtr)RT_BITMAP

as the lpType parameter.

As an alternative, you can stick to the declaration in your question, and use the alternative mechanism as specified in the documentation:

If the first character of the string is a pound sign (#), the remaining characters represent a decimal number that specifies the integer identifier of the resource's name or type. For example, the string "#258" represents the integer identifier 258.

You do appear to have tried all of these various options. I suggest that you pick one option and stick to it.

On top of that you have omitted the call to LockResource. You call FindResource and LoadResource. But you fail to call LockResource. Note that LoadResource returns an HGLOBAL. In order to get a pointer to the resource data, you must pass that HGLOBAL to LockResource. Although, it turns out that in the implementation of modern day Windows, you can get away without performing the LockResource step, you still ought to perform it in the sake of abiding by the rules.

Now, if you get as far as SizeofResource returning a non-zero value then it is clear that your call to FindResource succeeded. You must be somehow mistaken in your assertion that the size returned by SizeofResource is incorrect. It must be safe to assume that such a foundational API as SizeofResource works as designed.

I would also like to stress that your code omits error checking. You really ought to add that. If you do that you may find that you are given useful diagnostic information. Without error checking you have no idea where you code could be failing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top