FindResource不使用Bitmaps

时间:2014-05-12 18:04:39

标签: c# .net winapi resources pinvoke

http://khason.net/images/2008/12/image-32.png

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);

以上代码适用于PNG和其他自定义类型,但它不适用于BITMAP,我尝试了所有可能的组合:

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");

有谁知道我在这里失踪了什么?

我不想在这里使用LoadBitmap,因为此功能可以满足我的所有需求。

编辑:

以下内容返回一些正确大小*但数据不正确的数据(如某种不同的编码):

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

*每次减少大约12-14个字节

P / Invoke签名:

[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);

1 个答案:

答案 0 :(得分:5)

基本问题是RT_BITMAP资源不以位图文件的格式存储。如果将原始数据保存到文件,则不会得到有效的位图文件。原始数据旨在由LoadBitmapLoadImage进行解释,以创建HBITMAP

更多详情请见here

  

如果应用程序调用FindResource()(使用RT_BITMAP类型),LoadResource()和LockResource(),而不是调用LoadBitmap(),则会得到指向打包DIB的指针。压缩DIB是BITMAPINFO结构,后跟包含位图位的字节数组。

因此,如果您完全不使用LoadBitmapLoadImage,那么您将不得不弄清楚如何将打包的DIB转换为位图文件。基本上,您需要写出相应的位图文件头,然后使用打包的DIB数据进行跟踪。

本质上代码可能如下所示。首先定义文件头类型。

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

然后将各种尺寸放入局部变量以方便:

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

然后为文件内容分配足够的空间:

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

现在填充标题:

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

最后,用文件头填写字节数组,然后填充压缩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);
}

然后你可以像以前一样将字节数组保存到磁盘上,你应该好好去。


FindResource的声明如下:

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

虽然lpNamelpType声明为以null结尾的C字符串,但它们并不总是那种形式。它们可以使用MAKEINTRESOURCE宏形成。文件说:

  

返回值是低位字中的指定值,高位字中为零。

这确实是RT_BITMAP定义的方式。它是MAKEINTRESOURCE(2),详见resource types文档。

所以你应该添加一些重载的p / invoke声明:

[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);

然后您可以按如下方式定义RT_BITMAP

public const uint RT_BITMAP = 0x00000002;

然后当你调用函数传递

(IntPtr)RT_BITMAP

作为lpType参数。

作为替代方案,您可以坚持问题中的声明,并使用documentation中指定的替代机制:

  

如果字符串的第一个字符是井号(#),则其余字符表示十进制数,指定资源名称或类型的整数标识符。例如,字符串“#258”表示整数标识符258。

您似乎尝试了所有这些不同的选项。我建议您选择一个选项并坚持下去。

除此之外,你已经省略了对LockResource的调用。您拨打FindResourceLoadResource。但你没有打电话给LockResource。请注意,LoadResource会返回HGLOBAL。要获取指向资源数据的指针,您必须将HGLOBAL传递给LockResource。虽然,事实证明,在现代Windows的实现中,你可以在不执行LockResource步骤的情况下离开,但你仍然应该遵守规则来执行它。

现在,如果你SizeofResource返回一个非零值,那么很明显你对FindResource的调用成功了。你必须在某种程度上误认为SizeofResource返回的大小不正确。假设SizeofResource这样的基础API按设计工作,必须是安全的。

我还要强调您的代码省略了错误检查。你真的应该补充一点。如果您这样做,您可能会发现您获得了有用的诊断信息。没有错误检查,你不知道你的代码可能会失败。