;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Timed Random Rectangles
;;
;; Copyright (c) 1998 G. Adam Stanislav
;;
;; Started:     Jan 13, 1998
;; Finished:    Jan 14, 1998
;;
;; The purpose of this program is to illustrate the use of the timer in
;; assembly language programming for Windows 95.
;;
;; 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:
;;              1.0.0.0 on January 13, 1998
;;              1.0.0.1 on January 14, 1998 - Fixed the same bug as in rand.asm
;;
;; As of version 1.0.0.1, this program illustrates additional programming
;; techniques (only the use of the timer in 1.0.0.0). By studying this
;; code you will learn how to:
;;
;;      use the timer
;;      process the mouse input
;;      create a memory bitmap and fill it with contents of window client area
;;      copy the bitmap to the clipboard
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.386
.model flat

include win.inc

option prologue:none
option epilogue:none

ID_TIMER        equ     1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 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__MessageBoxA@16:DWORD
EXTRN   __imp__UpdateWindow@4:DWORD
EXTRN   __imp__TranslateMessage@4:DWORD
EXTRN   __imp__DispatchMessageA@4:DWORD
EXTRN   __imp__GetMessageA@16: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__SetTimer@16:DWORD
EXTRN   __imp__KillTimer@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
EXTRN   __imp__OpenClipboard@4:DWORD
EXTRN   __imp__EmptyClipboard@0:DWORD
EXTRN   __imp__SetClipboardData@8:DWORD
EXTRN   __imp__CloseClipboard@0:DWORD
EXTRN   __imp__CreateCompatibleDC@4:DWORD
EXTRN   __imp__DeleteDC@4:DWORD
EXTRN   __imp__CreateCompatibleBitmap@12:DWORD
EXTRN   __imp__SelectObject@8:DWORD
EXTRN   __imp__BitBlt@36:DWORD

flgs    record monochrome:1

.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
seed            dd              0
szAppName       db              "TimRanRec", 0
flags           flgs            <0>
align DWORD
szTitle         db              "Timed Random Rectangles (by Whiz Kid Technomagic)", 0
align DWORD
noTimer         db              "No timer available at this time.", 0Ah
                db              "Close some applications that use a timer", 0Ah
                db              "and retry. Or quit for now.", 0

.code

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Random number generator
;;
;;      Unlike in RAND we cannot keep the old random value in EBP
;;      because we are called by the Windows timer. Therefore, we use
;;      a global variable called seed.
;;
;;      Returns new random number in EAX.
;;      Sets EDX = 0.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align   DWORD
random  proc    private

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

random  endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Program entry
;;
;; If you are studying these comments, you should first read those
;; in rand.asm.
;;
;; The main difference between rand and trand is the use of the timer
;; in this program. Because the code to draw random rectangles is called
;; by the timer in this module, it was necessary to take it out from the
;; trand procedure, and place it in a separate procedure.
;;
;; For the same reason, it is no longer possible to keep the seed value
;; for the random number generator in EBP: Each time the timer calls
;; our code, registers are undefined to us.
;;
;; Therefore, we now have a global variable named seed in the data segment.
;;
;; Also, we are no longer using PeekMessage, but the more typical GetMessage
;; for the event processing loop.
;;
;; We added code to start the timer in trand, as well as code to inform the
;; user if a timer is not available (which is highly unlikely, but
;; theoretically possible.
;;
;; If we successfully start a timer, we must kill it before exiting.
;; We do that when processing the WM_DESTROY message.
;;
;; Other than that, the code here is identical to that in rand.asm. In fact,
;; I started writing this program by copying rand.asm into this file, then
;; making any necessary changes.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align   DWORD
trand   proc stdcall

        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

        ; Create a timer if available
@@:
        push    OFFSET FLAT:_tDraw@16
        push    55              ; a 55 ms timer
        push    ID_TIMER        ; timer ID
        push    esi             ; hwnd
        call    __imp__SetTimer@16
        or      eax, eax        ; if EAX = 0, no timer available
        jne     @F

        ; No timer, inform user, ask whether to retry
        push    MB_ICONEXCLAMATION or MB_RETRYCANCEL
        push    OFFSET FLAT:szTitle
        push    OFFSET FLAT:noTimer
        push    esi             ; hwnd
        call    __imp__MessageBoxA@16
        cmp     eax, IDCANCEL
        jne     @B

        ; Quit
        popa
        sub     eax, eax
        ret

@@:
        ; 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     seed, eax

        lea     edi, msg        ; EDI = address of ("pointer to") msg
        sub     esi, esi        ; ESI = 0

@@:
        push    esi
        push    esi
        push    esi
        push    edi
        call    __imp__GetMessageA@16
        or      eax, eax        ; EAX = 0 -> no message, else got message
        je      @F

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

        jmp     @B

@@:
        popa
        mov     eax, msg.wParam
        ret

trand   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     @F

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

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

        sub     eax, eax
        ret     16

        ; Process mouse messages:
        ;       if left button is pressed toggle between color and achromatic
        ;               rectangles
        ;       if right button is pressed, copy our rectangles from screen
        ;               to Windows clipboard.
@@:
        cmp     eax, WM_LBUTTONDOWN
        jne     @F
        xor     flags, mask monochrome
        sub     eax, eax
        ret     16

@@:
        cmp     eax, WM_RBUTTONDOWN
        jne     $WndNextCheck

        ; Let's copy the contents of the screen to the clipboard.
        ; This is not a trivial task. We need to do the following:
        ;
        ;  1. Open the clipboard. This may fail if another application has
        ;     it open. In that case, skip the rest of the steps;
        ;  2. Empty the clipboard;
        ;  3. Find the device context (DC) for our window client area;
        ;  4. Create a memory DC from the DC of step 3;
        ;  5. Create a bitmap compatible with the DC of step 3;
        ;  6. Select the bitmap into the memory DC of step 4;
        ;  7. Transfer the contents of the DC to the memory DC, effectively
        ;     copying the window client area to the bitmap;
        ;  8. Give away the bitmap to the clipboard;
        ;  9. Delete the memory DC;
        ; 10. Delete the handle to DC of window client area;
        ; 11. Close the clipboard.
        ;
        ; Note that we do not delete the bitmap because we no longer
        ; own it. The clipboard does.
        ;
        ; By the way, I changed the icon for this program after adding
        ; this section: I simply clicked the right mouse button to
        ; copy the output to the clipboard, then pasted it into an
        ; icon editor.

        push    esi
        mov     esi, hwnd[esp+4]        ; +4 because of the push

; Step 1
        push    esi
        call    __imp__OpenClipboard@4
        or      eax, eax
        jz      @F

        push    edi

; Step 2
        call    __imp__EmptyClipboard@0

; Step 3
        push    esi             ; hwnd
        call    __imp__GetDC@4
        push    eax                     ; for DeleteDC
        push    eax                     ; for CreateCompatibleDC
        mov     esi, eax                ; ESI = hdc

; Step 4
        call    __imp__CreateCompatibleDC@4
        push    eax                     ; for DeleteDC
        mov     edi, eax                ; EDI = hdcMem

; Step 5
        push    yDim
        push    xDim
        push    esi             ; hdc
        call    __imp__CreateCompatibleBitmap@12
        push    eax                     ; for SetClipboardData

; Step 6
        push    eax             ; hBitmap
        push    edi             ; hdcMem
        call    __imp__SelectObject@8

; Step 7
        push    SRCCOPY
        sub     eax, eax
        push    eax
        push    eax
        push    esi             ; hdc
        push    yDim
        push    xDim
        push    eax
        push    eax
        push    edi             ; hdcMem
        call    __imp__BitBlt@36

; Step 8
        push    CF_BITMAP
        call    __imp__SetClipboardData@8       ; hBitmap already pushed

; Step 9
        call    __imp__DeleteDC@4       ; hdcMem

; Step 10
        call    __imp__DeleteDC@4       ; hdc

; Step 11
        call    __imp__CloseClipboard@0

        pop     edi

@@:
        ; If we could not open the clipboard we skipped to here.
        ; In a real-world application, you would probably want to
        ; inform the user that the copy operation was unsuccessful.
        pop     esi
        sub     eax, eax
        ret     16

$WndNextCheck:
        cmp     eax, WM_DESTROY
        jne     @F

        ; Since we have been using a timer, we must kill it before exiting
        mov     eax, hwnd[esp]  ; Do this before pushing anything!
        push    ID_TIMER
        push    eax
        call    __imp__KillTimer@8

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

@@:
        jmp     __imp__DefWindowProcA@16

_WinProc@16     endp

        ;;;;;;;;;; Draw the rectangle ;;;;;;;;;;;;
align DWORD
_tDraw@16       proc    public

        push    esi
        mov     esi, hwnd[esp+4]        ; +4 because we pushed esi

        ;;;;!!!!!!;;;;;
        ; This program was causing an EXCEPTION before I put the PUSH EBX
        ; below (and a corresponding POP EBX) at the end.
        ;
        ; As far as I know, it is not documented anywhere that EBX must
        ; be saved. As far as I am concerned, this is a bug in Windows!
        ; An operating system should never rely on applications not
        ; changing registers. And if it does, it should be DOCUMENTED.
        ; The documentation for MASM 6.11 (current documentation) says
        ; no registers need be saved in pure assembly language programs,
        ; and EBP, ESI, and EDI are to be saved if linked into
        ; C programs. Obviously, that is not the case.
        ;
        ; The only way I figured this was because I have examined
        ; ASM output produced by MSC and it was saving EBX, so I tried
        ; it here and it worked.....
        push    ebx

        ; Produce a random rectangle
        call    random
        mov     ebx, xDim
        or      ebx, ebx
        jz      $Done
        idiv    ebx
        mov     rect.left, edx
        call    random
        mov     ecx, yDim
        jecxz   $Done
        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 or monochrome color
        call    random
        test    flags, mask monochrome
        jz      @F

        ; A color is monochrome, or more precisely achromatic (shade of gray)
        ; if r = g = b. There is a formula to calculate the gray level of
        ; any rgb color. However, since all we care here is to get a RANDOM
        ; shade of gray, we just move the random value of AL, to the red,
        ; green, and blue fields.
        mov     ah, al
        shl     eax, 8
        mov     al, ah

@@:
        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

$Done:
        pop     ebx
        pop     esi
        ret     16

_tDraw@16       endp

end     trand
