Handling arguments in Win32 style procs. Passing arg. to invoke. Addressing to arguments. (Based on analiz of comctls.asm - masm32\example1\comctls.asm) For beginners -----------------------
Look at the code. m2m caW,lParam ;client area width m2m caH,lParam ;client area height To understand it fully in need to have a clear picture of what actually are arguments in stdcall procs, locals, stack, and stack frame. In such times I really hate R.Hyde - he might have wrote good, big and detailed articles on such topics, but he prefered his HLA when proceeded to Win32 ASM. I'm not going now in detail of stack framing, only basic information. When in a proc like: WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD ... and then you addressing to any of the argument like mov eax,hWnd or .IF uMsg==WM_SIZE (really: cmp uMsg,5) You are in real code addressing to data throu ebp as a pointer. At the begining of such procs there is stack framing routine: ;upon entry in stack return address and followed it arguments puch ebp ;now in stack old ebp, ret address,arg mov ebp,esp ;it's for using ebp as pointer for memory in stack sub esp,number of bytes needed for locals .... after that you may see that data in = old ebp data in = return address data in = first argument (the same is  as you can see step is 4: ,, because all this data has DWORD size (4 bytes) So now you probably can see that mov eax,hWnd is really mov eax,dword ptr  sub eax,uMsg is really sub eax,dword ptr ; if wParam == WM_SIZE is really cmp dword ptr [10h],5 or eax,lParam is really or eax,dword ptr [14h] So what is lParam? it's [14h]+2 which is [16h] Now let's have a look at the code: m2m caW,lParam ;client area width m2m caH,lParam ;client area height invoke GetWindowRect,hStatus,ADDR Rct mov eax, Rct.bottom sub eax,Rct.top sub caH,eax invoke MoveWindow,hStatus,0,caH,caW,caH,TRUE It is for repositioning status bar. m2m is macro and means push memsource pop memdest caW and caH are local varibles and also addressed with help of ebp but with minus offset 'cause they are between old esp and new esp after substruction from esp number of bytes we need for local. Because frame rutine put old esp to ebp (mov ebp,esp) and stack is growing down (all new data with pushes go to less adresses then previous) memory for locals are always in region between ebp (wich has bigger address) and new esp (wich has less address). It preserves locals from owewriting by stack operations and allow address to them using ebp as a pointer so = address of the first local dword = address of the second and so on. so m2m caW,lParam ;client area width m2m caH,lParam ;client area height might for example means push  pop push  pop [-8] Enough for now. Hope it help at least understand m2m caW,lParam ;client area width m2m caH,lParam ;client area height is 4 stack operation with memory addressed by ebp. Actually Steve (the author) found not bad way to address to width and height partts in lParam. Instead of shifing and anding to extract low and high word from DWORD lParam he just shift address by 2 so that low word of lParam gets in low word of one local variable and high word of lParam gets in LOW WORD of the other local variable. Good trick, Steve :) Let's now count size and clocks for this part of code and try to do it faster and shorter. push  pop push  pop [-8] Push memory 2 clocks, size with this addr. mode - 3 bytes Pop memoty 3 clocks,size with this addr mode - 3 bytes Altogether 10 clocks , 12 bytes. Second part mov eax, Rct.bottom ;1 clock ,3 bytes sub eax,Rct.top ;2 clocks,3 bytes sub caH,eax ;3 clocks,3 bytes ;6 clocks, 9 bytes to culculate invoke MoveWindow,hStatus,0,caH
Here is an alternative approach but it suffers from the same crudity as the original example, it uses what is properly a WORD size address from the stack and puts it into a DWORD size parameter in the MoveWindow() function call. It appears to work because the parameters for MoveWindow for the client area size can never be above the WORD limit of 64k and apparently it only reads the first WORD of the parameter. Replacing PUSH & POP in the m2m macro with the following code that accesses the stack directly gives a smaller instruction count.
For safety reasons it is worth carrying the extra instruction to endure the data content is correct,
.elseif uMsg == WM_SIZE invoke SendMessage,hToolBar,TB_AUTOSIZE,0,0 ; ------------------------------------------------- invoke GetWindowRect,hStatus,ADDR Rct mov eax, Rct.bottom sub eax, Rct.top movzx edx, word ptr sub edx, eax invoke MoveWindow,hStatus,0,edx,,edx,TRUE ; -------------------------------------------------
movzx eax, invoke MoveWindow,hStatus,0,edx,eax,edx,TRUE
movxz eax, WORD ptr can't be just movxz eax,
You are right, there are some things you should not post at 2am before you go to sleep, here is the working version.
.elseif uMsg == WM_SIZE invoke SendMessage,hToolBar,TB_AUTOSIZE,0,0 invoke GetWindowRect,hStatus,ADDR Rct mov eax, Rct.bottom sub eax, Rct.top movzx edx, word ptr sub edx, eax movzx eax, word ptr invoke MoveWindow,hStatus,0,edx,eax,edx,TRUE
Hi all. Lots of interesting stuff here. First let me stress that you don't need to keep specifying :DWORD in arguments, locals and immed. pushes. This is the default size for any 32 bit dataseg - which .model FLAT creates. myproc proc arg1, arg2, ... ; these args will be dwords by default local l1, l2 ... ; as will these locals It seems noone knows that Masm has a limited "casting" ability? Many times we want to resize a child control to the whole client area. Typicaly we do: .if umsg == WM_SIZE movzx eax, word ptr wparam ; width movzx edx, word ptr wparam+2 ; height invoke MoveWindow, hCtl, 0, 0, eax, edx, TRUE but you can just do this... d equ
; for brevity w equ .if umsg == WM_SIZE invoke MoveWindow, hCtl, 0, 0, d, d, TRUE We need a "double" cast here. The "" forces a word access. This is'nt enough though - you will crash because Masm will push words instead of dwords (** bug? The proto should forbid this). Adding the second (outer) cast to dword however causes Masm to promote the word to a dword! If however you reference a variable which is already of type byte or word, Masm will promote it to dword on it's own. .data b1 sbyte 43 .code invoke dwtoa, b1, addr buffer ... will faithfully produce the string "43" in buffer!
Oops... I just discovered that auto promotion does'nt always work. I invoked dwtoa with a byte, word and dword. It worked every time. Tried the same thing with MoveWindow ... crashed. It did work with a dword override though - ie. iwidth dw 600 iheight dw 400 invoke MoveWindow, hCtl, 0, 0, dword ptr iwidth, dword ptr iheight, TRUE It would seem that Masm IS promoting the words to dwords correctly. As opposed to just pushing the dword at that address on to the stack! I'd be interested in hearing how others fair with this technique. Do I have a corrupt Masm.exe? gfalen
.if umsg == WM_SIZE invoke MoveWindow, hCtl, 0, 0, d, d, TRUE is the same as: .if umsg == WM_SIZE invoke MoveWindow, hCtl, 0, 0, lparam, lparam+2, TRUE it will result in (d, d): push ;dword push ;dword
to Hutch: I did even more funny thing: I spelt movxz instead of movzx. And I did it twice :) As to your version: Add size and speed of instruction to get clear picture. Number of instruction means nothing. The only matter is their speed and size. The Svin.
Svin, the code invoke MoveWindow, hCtl, 0, 0, lparam, lparam+2, TRUE assumes there are 2 dwords at lparam & lparam+2 (MoveWindow expects this). But lparam actually contains 2 words packed into a dword (WM_SIZE msg). Masm does'nt know this and pushes the 2 dwords at those addresses. But those addresses DO NOT contain the width & height as dwords! This will fail. I've tried it. That is the reason we need a cast here (word to dword). Do you see what I'm driving at? gfalen
I have no idea what are you driving at, the only thing I can see in debugger or in opcodes is just that all three version in source will produce the same opcode. They all push dword ptr and dword ptr I can write it in pure opcodes or with any of sintax above it'll be the same. MoveWindow, as Steve noted, just doesn't check high word of those arguments. so when you push dword (or lParam) the low word will be width and high word will be height but the func. just doesn't care as it appearse to be what is in high word, then when you push dword ptr (or lParam+2) the high word of original lParam gets to low word of pushed argument and high word of this argument is filled with the stack garbidge. You may write anything you want in your source the matter is not what you write but what opcode is translated from your code. It's very easy for asm programmer figure out what actually tranclated - look at opcode in debugger or disams it. The Svin.
I understand. I was'nt aware of this behavior in the MoveWindow API. On my system it crashes if I do what you say - which would seem to indicate that this function IS sensitive to the hiword. Have you actually tried to resize a control using your method for retrieving width/height from lparam? I know typicaly people use movzx to convert the words to dwords. gfalen
NT and Win2k are supposed to be sensitive to the 16 high bits, because they use 32-bit coordinates. Also Win2k is supposed to have a multi-monitor capability which requires a large coordinate space. (An image can be dragged from one monitor into another.) This message was edited by tank, on 4/2/2001 6:33:50 PM This message was edited by tank, on 4/2/2001 7:14:37 PM
Hey Tank. Did you get the jist of what I was saying? All this speculation aside, what I was really trying to convey was this method of promoting a byte or word to a dword by a "double cast". dword ptr The inner cast FORCES a word access. The outer cast dword ptr then causes Masm to promote/convert it to dword. I think this is really neat. I just stumbled on this a few weeks ago. I've never seen it documented or even mentioned anywhere. gfalen
How can MASM force word access? Push just fill memory of the stack that's all. to tank: Well, I was a little bit alarm when saw Steve's code. But it works on both NT2000 and NT4. Anyway I it weren't - Steve would have recieved a lot of bag reports, but MASM32 with comctrls.exe has been distrebuted for long and nobody's reported bug yet. The Svin.
to gfalen This particular thread is discusing opt. of one prog. It is in masm32 example1. Of course I tested all code on both OS (NT4 and 98) In original and optimized proc in both cases we sent garbige in high words. I had not known it myself before analyze Steve's code. But it works! The Svin.
to Svin re: How can MASM force word access? Simple. LPARAM is a dword, If you push it, masm will push 4 bytes. The overides this and forces a word access. This was the whole point of my post. How to coerce a word(or byte) to a dword w/o using movzx/movsx! gfalen
I don't ........ The Svin.
;Write a program: .586 .model flat,stdcall option casemap:none include C:\masm32\include\windows.inc include C:\masm32\include\user32.inc include C:\masm32\include\kernel32.inc includelib kernel32.lib includelib user32.lib WndProc proto :DWORD,:DWORD,:DWORD,:DWORD .code start: d equ
w equ WndProc proc hWnd,uMsg,wParam,lParam invoke MoveWindow,0,0,0,d, d, TRUE invoke MoveWindow,0,0,0,lParam,lParam+2,TRUE invoke MoveWindow,0,0,0,,,TRUE ret WndProc endp end start ;Compile and disassemble it (don't run though :) LOOK!!!!!!!!!!!!!!: .00401010: 55 push ebp .00401011: 8BEC mov ebp,esp .00401013: 6A01 push 001 .00401015: FF7516 push d, .00401018: FF7514 push d, .0040101B: 6A00 push 000 .0040101D: 6A00 push 000 .0040101F: 6A00 push 000 .00401021: E82A000000 call .000401050 -------- (1) .00401026: 6A01 push 001 .00401028: FF7516 push d, .0040102B: FF7514 push d, .0040102E: 6A00 push 000 .00401030: 6A00 push 000 .00401032: 6A00 push 000 .00401034: E817000000 call .000401050 -------- (2) .00401039: 6A01 push 001 .0040103B: FF7516 push d, .0040103E: FF7514 push d, .00401041: 6A00 push 000 .00401043: 6A00 push 000 .00401045: 6A00 push 000 .00401047: E804000000 call .000401050 -------- (3) CAN YOU SEE ANY DIFFERENCE ?!
Guys, The original example was written about 2 years ago and it was a standard trick to save a few instructions by pushing the address of the forst word value as one DWORD parameter followed by the second as a DWORD parameter. What you get pushed on the stack is real easy to find out, just run it through a decompiler. The risk with the technique that I used 2 years ago is that a later version OS may not be tolerant of the highword that will produce a different DWORD value. Pulling the value directly off the stack as a WORD and converting it to a DWORD with MOVZX is a viable and safe technique that does not suffer much code for doing it. As far as the idea of double type casting, what you see when you decompile it is what you get, there is nothing magic about an address on the stack, push a DWORD from that address and you get the 4 bytes at that address. Regards, firstname.lastname@example.org
Ok. This is hard to refute. I just tried it here... MoveWindow does behave as you said! I guess it's this behavior that caused me to misinterpret what was really happening. One thing I still don't understand though. I can do this: .data buf db 20 dup(?) b1 db 1 w1 dw 2 d1 dd 4 .code invoke dw2a, b1, addr buf ; or invoke dw2a, w1, addr buf ; or invoke dw2a, d1, addr buf ...they all work! But the proto for dw2a has a dword as the 1'st arg. So why does'nt masm flag this as an error? Puzzling behavior... gfalen
Puzzling behavior :) It's puzzling only if you don't know opcode or don't use debugger and disassembler. You must get debbuger and disassm and attatch to you asm shell. Steve's editor already has option "disassmble", all you need is attach debugger to his shell. It's possible 'cause his editor is highly configurable. You don't need to run program if there is something puzzling. All you need to look in opcodes in mnemonics in debugger or disasmed code. MASM doesn't do anything with data in stack ( I can't imagine how can it be done for all the future situation even if it was discribed in SFiction). All MASM does is writing opcodes. What does it mean push d,? For example ebp = 1000 esp = 1100 It commands the processor 1. Cuclulate address of data by adding ebp and 22. Processor gets value 1022 2. Get data (dword - 4 bytes) from address 1022 3. Write the data to address 1100. 4. Add to esp 4. Now in address 1100 we have for bytes taken from address 1022. They occupide adresses 1100,1101,1102,1103 And now esp has value 1104. Now about this "black magic double casting". As both of you said is that MASM forced access to word and then convert it double word, zeroing that way first dword. Or may be even it commands to MoveWindow how to treat the word pushed? Exuse me but it's absolute nonsence. Masm just push dword that's all. To do the job you thought masm is doing, it needs to generate two commands. pushw  pushw 0 pushw  pushw 0 Then is the stack you would have two dwords with zeroed high words. But masm doesn't do it. It just pushes dwords looking at your d w statement. It's actually mean push dword ptr data wich started from word ptr In other word with data in ebp+20 it means: push dword ptr data wich has the same address as ebp+20 in other word push dword ptr ebp+20 :) So it produce exactly the same code. The Svin.