FindResource不使用Bitmaps

Elm*_*lmo 3 .net c# pinvoke resources winapi

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);
Run Code Online (Sandbox Code Playgroud)

上面的代码适用于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");
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:

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

FindResource(hMod, "#" + ID, "#2"); //RT_BITMAP = 2
FindResource(hMod, ID, 2); //RT_BITMAP = 2; changed sig
Run Code Online (Sandbox Code Playgroud)

*每次减少大约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);
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 6

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

更多细节在这里给出:

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

因此,如果你完全没有设置使用LoadBitmap,LoadImage那么你将不得不弄清楚如何将打包的DIB转换为位图文件.基本上,您需要写出适当的位图文件头,然后使用打包的DIB数据进行跟踪.

实质上,代码可能看起来像这样.首先定义文件头类型.

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

然后将各种大小放入局部变量以方便:

int resSize = SizeofResource(hMod, hRes);
int headerSize = Marshal.SizeOf(typeof(BitmapFileHeader));
Run Code Online (Sandbox Code Playgroud)

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

byte[] Bytes = new byte[headerSize + resSize];
Run Code Online (Sandbox Code Playgroud)

现在填充标题:

BitmapFileHeader header;
header.id = 0x4D42;
header.size = Bytes.Length;
header.res1 = 0;
header.res2 = 0;
header.offset = headerSize + 40; 
// magic constant, size of BITMAPINFOHEADER
Run Code Online (Sandbox Code Playgroud)

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

然后,您可以像以前一样将字节数组保存到磁盘,您应该很高兴.


声明FindResource如下:

HRSRC WINAPI FindResource(
  _In_opt_  HMODULE hModule,
  _In_      LPCTSTR lpName,
  _In_      LPCTSTR lpType
);
Run Code Online (Sandbox Code Playgroud)

虽然lpNamelpType宣布为空值终止的C字符串,它们并不总是这种形式的.它们可以使用MAKEINTRESOURCE宏形成.文件说:

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

这确实是如何RT_BITMAP定义的.它MAKEINTRESOURCE(2)资源类型文档中有详细说明.

所以你应该添加一些重载的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);
Run Code Online (Sandbox Code Playgroud)

然后,您可以定义RT_BITMAP如下:

public const uint RT_BITMAP = 0x00000002;
Run Code Online (Sandbox Code Playgroud)

然后当你调用函数pass时

(IntPtr)RT_BITMAP
Run Code Online (Sandbox Code Playgroud)

作为lpType参数.

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

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

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

最重要的是你省略了对它的调用LockResource.你叫FindResourceLoadResource.但你没有打电话LockResource.请注意,LoadResource返回一个HGLOBAL.要获取指向资源数据的指针,必须将其传递HGLOBALLockResource.虽然,事实证明,在现代Windows的实现中,你可以逃脱而不执行这LockResource一步,你仍然应该遵守规则来执行它.

现在,如果您SizeofResource返回非零值,那么您的呼叫显然是FindResource成功的.你必须在断言中错误地认为返回的大小SizeofResource不正确.假设这样的基础API SizeofResource按设计工作,必须是安全的.

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