home

Desperately Seeking Params

(or Extracting Packed WORDs from a DWORD)

  This tutorial isn't so much to teach you any one thing (though yes it does have a point to it) but will go a little deep into how and why we write code in a particular manor. Through the years I have had the opportunity to work on code I have written long long ago, long enough I have no idea what I myself was thinking of when I originally wrote it. It is with thoughts of maintaining my own code, thus making sure it is a readable and plain in it's intention that I write this essay.

  What started this essay was a nagging thought I had since I first started doing Win32 asm, that is the way I've seen and use myself to get the hi and low WORDs from a DWORD. This is typically found when you are doing Windows message processing, and for example lParam holds the x and y position of the mouse packed into a single DWORD. We all do (well, I do, Iczelion does (he taught it to me), so does hutch) something like this:

mov eax, lParam 
and eax, FFFFH ; get the low word in ax
{ do something with the value }
mov eax, lParam
ror eax, 16 ; get the high word in ax
{ do something else with the other value }
  What nagged me was here we are, every instruction possible at our disposal, and we're doing unnecessary computations to get the values. Think about it for a second, why can't we just transfer the WORD value we want to ax? Why do we move the whole DWORD?

  Well, there is no real good reason why we don't do it that way. "C" does it this way for it's own reasons, and I imagine the first people blazing the asm trail into win32 did a lot of C code disassembly, and copied the technique from there. However, the reason we write asm in the first place is to make blazingly fast code because we control everything as intimately as possible.

  When we write asm code, we're defining symbols for the compiler to interpret, and if we and the compiler agree on the definitions then we get good code. The problem here is to make the compiler see the same thing in various ways. Here's the simplest way to get the compiler to get the WORDS we want directly: 

mov ax, [ebp+14h] ; get the low word part of lParam
{ do something with the value }
mov ax, [ebp+16h] ; get the high word part of lParam
{ do something else with the other value }
  That's two instructions less for the same result. And it's the worst thing you could do. Why? Honestly, how many of you right off the bat know 100% why that works? And if you do, are you sure I didn't grab wParam instead?. It happens this code is correct for lParam (wParam requires offsets of 10H and 12H for lo and hi words, respectively). But it's too damn obscure !!!

  Let's think about maintainable code.

  So, the task at hand is to define something that both we AND the compiler can agree on. And let me tell you, no matter how hot a code jockey you think you are, YOU are the weak link. Humans forget things, assume things, ignore things. We get tired, lazy, get sick, pissed off... you name it. So whatever we do we want to do it because it's EASY on people. I'll tell you right now, any approach we choose will just generate the same code as above. The best thing to do is do something people can understand.

  So what to do to keep it easy on us poor stupid humans? One approach is what hutch did when I tweaked him with this problem. He wrote himself some equates like so:

wParamL equ <WORD PTR [ebp + 16]>
wParamH equ <WORD PTR [ebp + 18]>
lParamL equ <WORD PTR [ebp + 20]>
lParamH equ <WORD PTR [ebp + 22]>
  I like this, I'm not completely sold, but it is a good solid approach. What he has done is take the obscure code we and the compiler need to use, and give it a nice human friendly name. It's the type of thing that once you see it there is a good chance you will always remember it.

  I have one problem with his approach. It's too specific. It's not extensible to other values. What if we have some other packed DWORD we wish to use the same technique on? 

  Why can't we define a method we can use over and over, and most importantly without trying to figure out where our values are in the stack or memory or anywhere. As it was, I got the stack offset values by disassembling some code. I'd hate to have to work this out every time I want to use this technique. Besides, that's why I use INVOKE and LOCAL, to let the compiler do my lowest level bookkeeping for me. I know I'm saving many many hours by not doing this myself. I'm a human too, and I make as many mistakes doing simple addition on my fingers as the next guy.

  Besides, I'm too busy tracking down GDI resources I need to create and release in the correct order.  <g>

  The approach I used was to define a structure to tell the compiler what my intent is. I mean that very literally. What I like the most about structures is they are not code, they are directives. The biggest structure you can think of, added into your code generates not one single byte of code or data. What they do is they tell the compiler where your data is, how big it is, and how it relates to other data. I mean they specify address offsets, and they also provide type defs. Very powerful things. 

 And who cares if in use they look like VB object reference syntax? (I actually hear this objection. Incredible).

  Here is my solution is. Let's define ourselves a structure for a packed DWORD:

packedDW UNION
    value DWORD ?
    STRUCT
        loword WORD ?
        hiword WORD ?
    ENDS
packedDW ENDS
  Now we have something reusable. Any packed DWORD can be defined as this type, and we can be confident we're getting the results we want from the compiler. This would be used in our code like this:
 
mov ax, lParam.loword ; get the low word
{ do something with the value }
mov ax, lParam.hiword ; get the high word
{ do something else with the other value }


 And should we wish to get DWORDs back from our mov:

movsx eax, lParam.loword ; get the low word
{ do something with the value }
movsx eax, lParam.hiword ; get the high word
{ do something else with the other value }
Again, very simple in use. 

  Let's not forget sometimes we need the full DWORD value, say for the default message handler invoke. Not a problem, that's also in our packed struct:
 

invoke DefWindowProc, hWin, uMsg, wParam.value, lParam.value
  Of course, for all this to work we have to define wParam and lParam as this type:
WndProc PROTO :HWND, :UINT, :packedDW, :packedDW

WndProc PROC hWnd:HWND, uMsg:UINT, wParam:packedDW, lParam:packedDW

  Yikes you say, I have to change those too? Well of course you do, but how many times? I sure do hope you have a hunk of code you use as the base of your work, something that sets up your .asm file the way you are used to it. Then you build the rest of your app on top of this. 

  Just change THAT file, adding the struct definition, the WndProc and the DefWindowProc, and you are done. 

  And I am done too. I've spent way too much time on this one little topic.
 

home