简单说解决方法就是LoadImage第一个参数不要传递NULL,而使用GetModuleHandle(NULL)代替。
以下是邮件原文:
Hi,
There is a bug that I get with LoadImage once in a while where it does not
load the image from the file that I specify but instead loads up a system
graphic. This only happens very rarely but is annoying when it does. To the
point where this issue has been annoying me on and off for probably the last
5 years. I use LoadImage as follows:
hbm = (HBITMAP)LoadImage(NULL, filename, IMAGE_BITMAP, m_ddsd.dwWidth,
m_ddsd.dwHeight, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
Luckily, with the current build of my software is it is finally occurring
every time giving me a chance to debug this. Below is what I have discovered
is going wrong with LoadImage and a workaround at the end.
Stepping into the disassembly LoadImage calls the function _LoadBmp@20, with
the first parameter as NULL for the handle to the instance of the module.
The second parameter is the pointer to the string for the bmp file to open,
where my string is at address 0x00197fe8, and staying at 0x00197fe8 each time
at the moment which is why I could debug this.
Below is the disassembly with some of my own comments noting the important
parts:
_LoadBmp@20:
77D55E95 mov edi,edi
77D55E97 push ebp
77D55E98 mov ebp,esp
77D55E9A sub esp,24h
77D55E9D mov ecx,dword ptr [ebp+0Ch] ; Copies address of filename
into ecx
77D55EA0 push ebx
77D55EA1 push esi
77D55EA2 mov esi,dword ptr [ebp+8] ; Copies paramater handle to
instance of module to esi
77D55EA5 push edi
77D55EA6 xor edi,edi ; Sets edi to zero
77D55EA8 cmp esi,edi ; Checks if parameter handle to instance of
module is NULL
77D55EAA mov dword ptr [ebp-4],edi
77D55EAD mov dword ptr [ebp-8],edi
77D55EB0 je _LoadBmp@20+21h (77D57C6Eh) ; If esi was NULL jumps
to code at 77D57C6E
77D55EB6 push dword ptr [ebp+18h]
77D55EB9 push dword ptr [ebp+14h]
77D55EBC push dword ptr [ebp+10h]
77D55EBF push 2
77D55EC1 push ecx
77D55EC2 push esi
77D55EC3 call _ObjectFromDIBResource@24 (77D5298Dh) ; This is what
we want it to call
The two important parts above are:
1. The code at 77D55E9D. Which copies the address of the bmp file string
into ecx.
2. The code at 77D55EA8. This checks if the hinst parameter passed into
LoadImage is NULL. If we jump to the code at address 77D57C6E becuase of this
line:
77D55EB0 je _LoadBmp@20+21h (77D57C6Eh) ; If esi was NULL jumps
to code at 77D57C6E
This takes us to this assembly:
77D57C6E mov esi,dword ptr [_hmodUser (77DA0264h)]
77D57C74 movzx eax,cx
77D57C77 xor ebx,ebx
77D57C79 sub eax,7FDCh
77D57C7E mov dword ptr [ebp+0Ch],edi
77D57C81 mov dword ptr [ebp+8],edi
77D57C84 je 77D745B6
77D57C8A sub eax,16h
77D57C8D je _LoadBmp@20+141h (77D74471h)
77D57C93 sub eax,0Dh
77D57C96 je _LoadBmp@20+128h (77D74458h)
77D57C9C xor eax,eax
77D57C9E cmp cx,7FE2h
77D57CA3 sete al
77D57CA6 mov edx,77D41A98h ;edx is set here to address 77D41A98h
77D57CAB mov dword ptr [ebp-0Ch],eax
77D57CAE xor eax,eax
77D57CB0 cmp cx,7FF7h
77D57CB5 sete al
77D57CB8 mov dword ptr [ebp-10h],eax
77D57CBB xor eax,eax ; sets eax to zero
77D57CBD cmp word ptr [edx],cx ; cx is the lower 16 bits of the
filename addesss
77D57CC0 je _LoadBmp@20+81h (77D57CCEh) ; jumps if cx equals
address in edx
77D57CC2 add edx,6 ; edx += 6
77D57CC5 inc eax ; eax++
77D57CC6 cmp edx,offset _gwszShellFont2 (77D41B58h) ; checks if end
loop
77D57CCC jl _LoadBmp@20+70h (77D57CBDh)
77D57CCE cmp eax,20h ; if we looped around 0x20
77D57CD1 je 77D55EB6 ; times then jumps back to 77D55EB6
The loop at the end is important part, and is what causes the issue. I will
step through the important parts:
The register edx is set to the value 77D41A98.
77D57CA6 mov edx,77D41A98h
Here is the loop:
77D57CC6 cmp edx,offset _gwszShellFont2 (77D41B58h) ;
77D57CCC jl _LoadBmp@20+70h (77D57CBDh) ;
This keeps looping around back to 77D57CBD while edx is less than the value
77D41B58. Incrementing edx by 6 each time. So the following calculation:
0x77D41B58 - 0x77D41A98 = 0xC0 / 6 = 0x20
Means the max eax can equal is 0x20. This is important because the next two
lines after the loop are:
77D57CCE cmp eax,20h ; if we looped around 0x20
77D57CD1 je 77D55EB6 ; times then jumps back to 77D55EB6
This is what we want as that way it jumps back to the code at 77D55EB6 which
is the first lot of assembly code, and then continues down to the line:
77D55EC3 call _ObjectFromDIBResource@24 (77D5298Dh)
Which loads the bmp from the file, and all is good.
Now the issue is at the the line the loop keeps jumping to:
77D57CBD cmp word ptr [edx],cx
ecx is where the address of the bmps filename is stored shown earlier.
However, this line uses the register cx which is the lower 16 bits of the
address. And it is checking to see if it matches a value at the address of
edx which is being incremented each time through the loop. As my string is
at address 0x00197fe8 it is therefore checking if 0x7fe8 is in the list in
memory between 0x77D41A98 and 0x77D41B58, this is the list of OEM images. In
my case it gets a match when eax equals 16, and leaves the loop. It then
continues down through the code to:
77D57D56 call _CreateScreenBitmap@24 (77D52723h)
Which then creates the OEM image instead of giving loading the bitmap I
specified.
The above loop shows why it is rare to get the wrong image. As the bottom
16 bits of the filenames address has to match an OEM's ID for it to occur.
The following code will replicate this issue:
HBITMAP hbm = 0;
hbm = (HBITMAP)LoadImage(NULL, (TCHAR*)0x00197fe8, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE|LR_CREATEDIBSECTION);
If you use this code you will find you get a handle to a bitmap even though
you have given an address which will have random data in it. This is because
it will never try use access this address, but will just use the 0x7fe8 part
of it as it happens to match and OEM image ID.
The fix for this when loading from a file is to always pass
'GetModuleHandle(0)' as the first parameter to LoadImage. Even though the
documentation states:
hinst - [in] Handle to an instance of the module that contains the image to
be loaded.
This is because of the following in the first lot of assembly I posted is:
77D55EA2 mov esi,dword ptr [ebp+8]
This copies the handle to instance of module into esi.
It then checks if this is zero with the following code:
77D55EA6 xor edi,edi ; Sets edi to zero
77D55EA8 cmp esi,edi ; Checks if parameter handle to instance of
module NULL
If it is not NULL then it does not do the next lot of assembly which checks
if the lower 16 bits of your address matches an OEM image.
So the the verdict is if you are loading a bmp from a file you should always
pass the first parameter to LoadImage, till this issue is fixed ( if ever ).
My problem is the case where I am currently experiencing this bug I am not
actually calling LoadImage directly, but calling the DirectX function
'D3DXLoadTextureFromFile' which ends up calling LoadImage internally, passing
NULL as the first parameter.
I guess I will have to go back to LoadImage for loading bmps into DirectX,
and replicate the extra couple of features 'D3DXLoadTextureFromFile' provides
that I use.
Adam Dokter