;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Rapid Random Rectangles
;;
;; Copyright (c) 1998 G. Adam Stanislav
;;
;; Started:     Jan 12, 1998
;; Finished:    Jan 14, 1998
;;
;; The purpose of this program is to illustrate how assembly language
;; programming can speed up Windows 95 applications, as well as to
;; show to anyone with an interest in assembly language programming
;; for Windows 95 how to do it.
;;
;; Any questions, contact G. Adam Stanislav, Whiz Kid Technomagic,
;; Rhinelander, WI. e-mail: whizkid@bigfoot.com
;;
;; You may distribute this program freely and at no charge, as long as
;; you distribute it in its original ZIP file without deleting any files
;; from it.
;;
;; History:
;;
;;      Version 1.0.0.0 - released Jan 13, 1998
;;      Version 1.0.0.1 - Jan. 14, 1998
;;              Fixed a bug that caused an exception when the program
;;              was minimized.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.386
.model flat

include win.inc

option prologue:none
option epilogue:none

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; NAME MANGLING
;;
;; Windows 95 uses a calling convention that pushes parameters on the stack
;; from the right to the left. It also mangles procedure/function names
;; by appending the at sign (@) after the name, followed by the number
;; of bytes pushed on the stack. It also precedes the name by an
;; undersacore (_).
;;
;; For example GetStockObject uses a DWORD argument, i.e., the caller
;; pushes 4 bytes on the stack. Therefore, the name is mangled to
;; _GetStockObject@4.
;;
;; When such a procedure/function is not linked with our program but called
;; from a DLL, we need to be able to find it somehow. We have no way of
;; knowing at what address the procedure will be. As a matter of fact,
;; it may be at a different address when our program is run under different
;; circumstances.
;;
;; The linker creates an import table in the executable file. When Windows
;; loads our program, it fills the import table with the addresses of the
;; procedures referred to in the table. We can then call the procedures
;; indirectly by jumping to the address stored in the table.
;;
;; Contrary to popular belief, we cannot call the procedure directly.
;; Unlike a static linker, Windows will not fill out the appropriate
;; addresses inside our CODE segment; it will only fill out the import
;; table.
;;
;; We can still call _GetStockObject@4 from our code directly, at it will
;; assemble, link and run. How so? If we do that, the linker will CREATE
;; a small proedure called _GetStockObject@4 and link it with our code.
;; That procedure will contain the following code:
;;
;;      jmp     DWORD PTR __imp__GetStockObject@4
;;
;; In other words, that procedure will find the real _GetStockObject@4 in
;; the import table, and jump to it. So, in reality, calling _GetStockObect@4
;; from our code will not only not jump to the desired location directly,
;; it will add extra indirection.
;;
;; We can call the Windows procedures indirectly ourselves if we know
;; how to find their addresses in the import table. That is quite easy
;; because the linker names each entry in the import table by the name
;; of the mangled function preceded by __imp_ - so, in our example,
;; GetStockObject is mangled to _GetStockObject@4, and the appropriate
;; entry in the import table will be named __imp__GetStockObject@4.
;; This may sound a bit confusing at first, but it is really quite
;; simple, as the name mangling follows exactly defined rules.
;;
;; By declaring the names of the references in the import table as EXTRN
;; and as DWORD, we can call them and jump to them. The assembler will
;; automatically understand that we want an INDIRECT call and jump, since
;; the DWORD designation tells the assembler that the label refers to data,
;; not to code.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EXTRN   __imp__GetModuleHandleA@4:DWORD
EXTRN   __imp__UpdateWindow@4:DWORD
EXTRN   __imp__TranslateMessage@4:DWORD
EXTRN   __imp__DispatchMessageA@4:DWORD
EXTRN   __imp__PeekMessageA@20:DWORD
EXTRN   __imp__RegisterClassExA@4:DWORD
EXTRN   __imp__GetStockObject@4:DWORD
EXTRN   __imp__CreateWindowExA@48:DWORD
EXTRN   __imp__ShowWindow@8:DWORD
EXTRN   __imp__LoadCursorA@8:DWORD
EXTRN   __imp__LoadIconA@8:DWORD
EXTRN   __imp__GetTickCount@0:DWORD
EXTRN   __imp__DefWindowProcA@16:DWORD
EXTRN   __imp__PostQuitMessage@4:DWORD
EXTRN   __imp__GetDC@4:DWORD
EXTRN   __imp__ReleaseDC@8:DWORD
EXTRN   __imp__CreateSolidBrush@4:DWORD
EXTRN   __imp__DeleteObject@4:DWORD
EXTRN   __imp__FillRect@12:DWORD

.data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; By declaring the window class in the data segment we can initialize some
;; of its values at assembly time rather than at run time.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wndclass        WNDCLASSEX      <size WNDCLASSEX, \
                                CS_HREDRAW OR CS_VREDRAW, \
                                _WinProc@16, \
                                0, \
                                0, \
                                0, \
                                0, \
                                0, \
                                0, \
                                0, \
                                szAppName, \
                                0>
msg             MSG             <>
rect            RECT            <>
xDim            dd              0
yDim            dd              0
szAppName       db              "RapRanRec", 0
align DWORD
szTitle         db              "Rapid Random Rectangles (by Whiz Kid Technomagic)", 0

.code

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Random number generator
;;
;;      Gets last random number (seed) from EBP.
;;      Returns new random number in EAX.
;;      Stores it in EBP as the seed for the next call.
;;      Sets EDX = 0.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align   DWORD
random  proc    private

        mov     eax, 214013t
        imul    ebp
        sub     edx, edx                ; Prevent divide overflow by caller
        add     eax, 2531011t
        mov     ebp, eax
        ret

random  endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This is what C programs call WinMain(). However, it is different.
;; In C the function is called by start-up code with 4 parameters passed.
;; In assembly language, this procedure is called directly by Windows
;; and it receives no parameters. We need to call GetModuleHandle to
;; receive the handle to current instance. Other than that, we have
;; we need.
;;
;; Note that I have named the procedure "rand," same as the program.
;; That reminds me this is the entry procedure, but it is different
;; from WinMain().
;;
;; Also note, this procedure has no local variables. Any values needed
;; stored in REGISTERS, most notably hwnd is in ESI. Also the address of
;; msg is kept in EDI, so it is not necessary to load it over and over
;; again as a C compiler would do. Since the routines use no local variables,
;; we can also use EBP -- we use it to keep the seed for the random number
;; generator.
;;
;; Although this is the entry to the program, it does not need to be the
;; first line of the code, as the existence of the random routine
;; above illustrates.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align   DWORD
rand    proc stdcall

        ; One would think that an operating system would be robust enough
        ; not to expect an assembly language program (or any language
        ; program) to save any registers.
        ;
        ; Not so with Windows 95! It expects us to preserve EBP.
        ; If we do not, the system will be confused and cause an
        ; exception after our program exits. Bah humbug!!!
        ;
        ; It does not appear that other registers need to be saved,
        ; but considering that not saving EBP causes problems,
        ; that Windows is written in C, and that Microsoft C
        ; expects ESI and EDI to be saved, we will save them
        ; just in case it causes problems with a future version of
        ; Windows.
        ;
        ; On second thought, let's just save ALL registers,
        ; something any decent OS would do before getting here...
        pusha

        push    0
        call    __imp__GetModuleHandleA@4
        mov     esi, eax        ; "hInstamce"

        ; While we have declared the window class outside of here,
        ; in .data, to preload it with values, some of them we do
        ; not know at assembly time. We need to load them now.

        mov     wndclass.hInstance, esi

        ; Find the icon handle

        push    100             ; Icon ID in the resource file
        push    esi             ; hInstance
        call    __imp__LoadIconA@8
        mov     wndclass.hIcon, eax
        mov     wndclass.hIconSm, eax

        ; Find the cursor handle

        push    IDC_ARROW
        push    0
        call    __imp__LoadCursorA@8
        mov     wndclass.hCursor, eax

        ; Get the handle of the background brush

        push    WHITE_BRUSH
        call    __imp__GetStockObject@4
        mov     wndclass.hbrBackground, eax

        ; Register the class with Windows

        push    OFFSET FLAT:wndclass
        call    __imp__RegisterClassExA@4

        ; Create a window

        sub     eax, eax        ; EAX = 0
        push    eax
        push    esi             ; "hInstance"
        push    eax
        push    eax
        mov     edx, CW_USEDEFAULT
        push    edx
        push    edx
        push    edx
        push    edx
        push    WS_OVERLAPPEDWINDOW
        push    OFFSET FLAT:szTitle
        push    OFFSET FLAT:szAppName
        push    eax
        call    __imp__CreateWindowExA@48
        mov     esi, eax        ; ESI = hwnd

        ; Show and update the window

        push    SW_SHOWDEFAULT  ; "nCmdShow"
        push    esi             ; hwnd
        call    __imp__ShowWindow@8
        push    esi
        call    __imp__UpdateWindow@4

        ; Initialize random number generator seed

        call    __imp__GetTickCount@0
        mov     ebp, eax

        ; This is the "critical section of the code, i.e., the section
        ; that is looped over and over again. It, therefore needs to
        ; be tightly optimized.
        lea     edi, msg        ; EDI = address of ("pointer to") msg

$BigLoop:
        ; Although a typical Windows program calls GetMessage here,
        ; we call PeekMessage instead. The reason: GetMessage waits for
        ; a message and freezes the program if none is waiting. This is
        ; usually desirable. However, since we want to draw as many
        ; rectangles as fast as we can, we need to use PeekMessage
        ; which returns immediately whether or not a message is waiting.
        sub     eax, eax        ; EAX = 0
        push    PM_REMOVE       ; If there is a message, remove it from queue
        push    eax
        push    eax
        push    eax
        push    edi             ; msg
        call    __imp__PeekMessageA@20
        or      eax, eax        ; EAX = 0 -> no message, else got message
        jne     $ProcessMessage

        ;;;;;;;;;; Draw the rectangle ;;;;;;;;;;;;

        ; Bug fix in revision 1.0.0.1:
        ;       In the original release, we did not check for division by 0
        ;       here. Rather, in WinProc, when processing WM_SIZE, we
        ;       checked for possible 0 dimensions, and if they existed,
        ;       we increased the dimensions to 1. The idea was to speed
        ;       everything up: Why check it here over and over again,
        ;       when we can check it only whenever the window client
        ;       area changes its size? Alas, when minimizing the program,
        ;       an exception occured. So, now we check for 0 dimensions
        ;       here, and if we find them, we just go back to the start
        ;       of the big loop.

        ; Produce a random rectangle
        call    random
        mov     ebx, xDim
        or      ebx, ebx        ; xDime = 0?
        je      $BigLoop        ; yes
        idiv    ebx
        mov     rect.left, edx
        call    random
        mov     ecx, yDim
        jecxz   $BigLoop        ; yDim = 0?
        idiv    ecx
        mov     rect.top, edx
        call    random
        idiv    ebx                     ; xDim
        mov     rect.right, edx
        call    random
        idiv    ecx                     ; yDim
        mov     rect.bottom, edx

        ; Produce a random RGB color
        call    random
        and     eax, 00ffffffh
        push    eax
        call    __imp__CreateSolidBrush@4

        ; One of the advantages of assembly language is
        ; that even when calling routines written with the C
        ; calling convention, arguments do not necessarily
        ; have to be pushed on the stack immediately before
        ; calling the routine.
        ;
        ; When calling Windows functions to get a value
        ; to be passed to several other functions, the value(s)
        ; can be pushed as soon as it is (they are) obtained.
        ; That eliminates the need for temporary local
        ; variables.
        ;
        ; Of course, it is important to push the arguments in
        ; proper order. Placing a comment to the right of each
        ; push helps the programmer to keep track.

        push    eax             ; hBrush for DeleteObject
        push    eax             ; hBrush for future reference

        push    esi             ; hwnd for GetDC
        call    __imp__GetDC@4

        pop     edx             ; hBrush

        push    eax             ; hdc for ReleaseDC
        push    edx             ; hBrush for FillRect
        push    OFFSET FLAT:rect
        push    eax             ; hdc

        ; Despite all of our efforts for optimization,
        ; we have no control over the code that actually
        ; draws the rectangle: It is built into Windows,
        ; and it is most likely not written in assembly language.
        ;
        ; Nevertheless, we call the built-in code. It is not
        ; a good idea to try to access the video hardware
        ; directly. Otherwise, we would have to know about
        ; every possible hardware configuration.
        ;
        ; The advantage of Windows is that it takes care of
        ; talking to the so many different types of hardware
        ; available.
        ;
        ; As assembly language programmers we need to take care
        ; of maximum optimization of our own code, while still
        ; relying on the system code in Windows to do its part.

        call    __imp__FillRect@12
        push    esi             ; hwnd
        call    __imp__ReleaseDC@8
        call    __imp__DeleteObject@4

        jmp     $BigLoop

$ProcessMessage:
        cmp     msg.message, WM_QUIT
        je      @F

        push    edi
        push    edi
        call    __imp__TranslateMessage@4
        call    __imp__DispatchMessageA@4

        jmp     $BigLoop

@@:
        popa
        mov     eax, msg.wParam
        ret

rand    endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This is the heart and soul of most Windows programs. Windows spends
;; much time here. That is why this procedure must be optimized for
;; maximum speed. It should be written in assembly language even if the
;; rest of the program is not.
;;
;; Here is an important trick: If we can avoid having any local variables
;; in this routine, we don not need to build an EBP-based stack frame.
;; Rather, we can address all parameters using ESP. And, because
;; DefWindowProc uses the same arguments as this procedure, we can simply
;; JUMP (JMP) to it rather than pushing the same values on the stack,
;; calling it and returning.
;;
;; Since we pass control on to DefWindowProc MOST of the time, this technique
;; will save us a considerable number of clock cycles.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
hwnd = 4
iMsg = 8
wParam = 12
lParam = 16
align DWORD
_WinProc@16     proc    public

        mov     eax, iMsg[esp]

        cmp     eax, WM_SIZE
        jne     $CheckWM_DESTROY

        ; The x-dimension of client area is in the low word lParam.
        mov     eax, lParam[esp]
        and     eax, 0000ffffh

comment % This was causing an exception in version 1.0.0.0, so we no longer
          do it here. Instead, we check for a 0 dimension in the big loop
          and if we find one, we restart the loop.

        jnz     @F
        inc     eax             ; Prevent division by 0

@@:
% end of comment
        mov     xDim, eax

        ; The y-dimension is in the high word of lParam
        movzx   eax, WORD PTR lParam[esp+2]

comment % see above
        or      eax, eax
        jnz     @F
        inc     eax             ; Prevent division by 0

@@:
% end of comment
        mov     yDim, eax

        sub     eax, eax
        ret     16

$CheckWM_DESTROY:
        cmp     eax, WM_DESTROY
        jne     @F

	push	0
        call    __imp__PostQuitMessage@4
        sub     eax, eax
        ret     16

@@:
        jmp     __imp__DefWindowProcA@16

_WinProc@16     endp

end     rand
