The X-Rated Code Page
Some fast, small, handy or just weird routines for Win32 ASM coding.


The WndProc, the dirty way...

I assume you all know what a WndProc is, and what you need it for... Let me give you an example of a WndProc:

WndProc   PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF
    xor   eax, eax
    ret
WndProc   ENDP
This generates the following code:
    push  ebp                                   ; Create stack frame
    mov   ebp, esp                              ; Why does MASM use 'leave', but not 'enter'?

    cmp   dword ptr [ebp+0C], WM_DESTROY        ; ebp+0C is uMsg
    jne   @@notDestroy

    push  NULL
    Call  PostQuitMessage
    jmp   @@exitFromDestroy

@@notDestroy:
    push  [ebp+14]                              ; ebp+14 is lParam
    push  [ebp+10]                              ; epb+10 is wParam
    push  [ebp+0C]                              ; ebp+0C is uMsg
    push  [ebp+08]                              ; ebp+08 is hWnd

    Call  DefWindowProcA                        ; Let Windows handle the other messages
    leave                                       ; Remove stack frame
    ret   0010                                  ; Remove function arguments from stack and return

@@exitFromDestroy:
    xor   eax, eax                              ; Return 'FALSE'
    leave                                       ; Remove stack frame
    ret   0010                                  ; Remove function arguments from stack and return
Looks nice, and works fine... But, it builds a stack frame, even though we are not using local variables. And if you code in a good fashion, there almost never will be. After all, this procedure is just a messagehandler, and to keep your code tidy, you will not put all the code in here, but in separate procedures, which you will call from here.

So, wouldn't it be nice if we didn't build a stack frame at all? It will save some time and space. Sounds good, so let's try it.

There's only one reason why MASM builds a stack frame for a function: The function has a prototype for a hll call. A hll call uses the stack to transfer its arguments. So, all we have to do, is remove the prototype. That's easy: Just don't tell MASM that this function uses any arguments.
This simple tweak will do the trick:

WndProc   PROC
    ...
WndProc   ENDP
The arguments will still be passed to the function, since that part of the code is in the Windows kernel, and has not changed. Be careful though: Since MASM does not know that there are arguments on the stack, it no longer cleans up the stack. You have to specify that yourself.

Now we have a slight problem: How can we access the arguments now?
The answer is surprisingly easy: We create aliases for the addresses relative to the stack pointer (esp). MASM does the same, except that it uses the base pointer (ebp), since it created a stack frame, and saved the original stack pointer in ebp.
Knowing that Windows hll calls always push the arguments in reverse order, and that the return address is stored on the stack aswell, we can devise these indices for our parameters:

    hWnd    EQU    dword ptr [esp][4]
    uMsg    EQU    dword ptr [esp][8]
    wParam  EQU    dword ptr [esp][12]
    lParam  EQU    dword ptr [esp][16]
There, now we can refer to the arguments as usual.
There's 1 drawback however: Since the indices are relative to esp, they are only valid when esp is not touched. In other words: Don't try to push or pop anything and then use these arguments again. They can be used if you push some variables, then pop them again before you access any of these arguments again, because the stack pointer will be at the correct position again.

Let's say you need to use the stack again (eg. for an INVOKE), so the indices will be invalidated. You might think that the only option then is to save the stack pointer again, so we're back to the stack frame...
It's an option, but not the best one. Namely, ebp is a non-volatile register, and needs to be saved and restored after use.
But, there are more registers in the CPU, and most of them are volatile. How about using esi for example?

WndProc   PROC
    mov   esi, esp

    hWnd    EQU    dword ptr [esi][4]
    uMsg    EQU    dword ptr [esi][8]
    wParam  EQU    dword ptr [esi][12]
    lParam  EQU    dword ptr [esi][16]

    ...
WndProc   ENDP
And if you leave the stack as you found it (which should always be the case with decent code), you don't even need to restore esp again.
If you got dirty and the stack still contains variables you don't want anymore, then this is enough for a clean exit:
WndProc   PROC
    ...

    mov   esp, esi
    ret   4 * sizeof dword      ; As I mentioned earlier, we have to clean the stack ourselves.
                                ; We had 4 dword arguments, so this does the trick
WndProc   ENDP
Still less code, and thus faster than the original. And just as rigid. You have one register less to use during the WndProc, but as I said earlier, there shouldn't be too much code here, so should be able to spare the register.

Well, there's just 1 more thing that can be done with this tweaked WndProc. Namely, if you leave the stack as you found it, the arguments for the DefWindowProc are already in place, and the return address of our caller is there too.
So basically we can just jump to it without any further ado. The resulting WndProc that is equivalent to the original one will look like this then:

WndProc   PROC
    hWnd    EQU    dword ptr [esp][4]
    uMsg    EQU    dword ptr [esp][8]
    wParam  EQU    dword ptr [esp][12]
    lParam  EQU    dword ptr [esp][16]

    .IF uMsg == WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSE
        jmp  DefWindowProc
    .ENDIF
    xor   eax, eax
    ret   4 * sizeof dword      ; Be sure to clean that stack!
WndProc   ENDP
Yes, much shorter, and faster. Let's take a look at the generated code to get a better understanding of how much shorter it actually is:
    cmp   dword ptr [esp+08], WM_DESTROY
    jne   @@noDestroy

    push  NULL
    Call  PostQuitMessage
    jmp   @@exitFromDestroy

@@noDestroy:
    Jmp   DefWindowProcA

@@exitFromDestroy
    xor   eax, eax
    ret   0010
If you code it 'by hand' instead of with the .IF statement, there's another tweak we can pull, but the rest looks great, doesn't it?

Ofcourse these stunts can be applied to other procedures as well. Be careful, and use them in good health.


Doing signed .IF statements in MASM and TASM

Well, we all had a haunch it was possible, since there are SBYTE, SWORD and SDWORD types nowadays, and when using these, the assembler actually generates signed comparisons already.
For example:

.data
foo     SDWORD  -25

.code
    .IF eax < foo
        mov eax, [foo]
    .ENDIF
That's nice, but we want to be able to do signed compares with immediates aswell.
This is actually quite simple, if you know how to program in C. Namely, in C, you have to 'cast' variables from one type to another quite often. We have to do the same thing with the .IF macros, which are based on the high level C syntax.

In ASM, the thing that comes closest to a typecast is the PTR directive. This is what we need to use. We can cast the immediate operand to a signed type, and the macro will generate a signed comparison.

Looking something like this:

    .IF eax < SDWORD PTR 0
        xor eax, eax
    .ENDIF
And the last option we have is using registers to compare. Well I can be very short about that one, because it works in exactly the same way:
    .IF eax < SDWORD PTR edx
        xor eax, eax
    .ENDIF
While we're on the subject... Did you know you could also do a .IF statement on flags?

There are special symbols for the flags:

zero?
carry?
overflow?
sign?
parity?
With these you can make .IF statements like this:
    sub eax, 50
    .IF carry?
        xor eax, eax
    .ENDIF

And that's about all there is to tell you...


Feel free to submit your own code: X-Calibre@bigfoot.com

Go back to the Computers page