Page 1 of 1

MASM - Our first window

#1 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,276
  • Joined: 28-March 11

Posted 19 October 2011 - 06:20 PM

MASM - Our first window.

Sure glad I can type, this will be a bit long...

Back in the day, to do anything like write to the screen we would have to use Interrupts to tell the CPU and DOS what to do.
I am sure you have seen Assembly code that looked like this:
        mov     ah, 8h
        int     21h                         
                      
        add     al, 3h
        mov     dl, al
        mov     ah, 2h
        int     21h 


and gotten scared off. All that does is get a nuber that typed in the console, add 3 to it and prints the result back to the console. Nowadays, this code will not run on Windows 7 and anything above without an emulator like DOSBox.
Luckily, Win32 Assembly is not that bad, and it is even a bit easier if you have used the Windows API in your programs. You CAN still write console apps, but they are not DOS apps and do not use Interrupts instead use the Windows API.

One Interrupt you CAN use and will use a lot is - int 3 this will call up your debugger and display your code at that point. In the 1st Getting Started tut is a link for OllyDbg, it is a good and powerful debugger.

There are 2 main ways to create windows and controls - with CreateWindowEx or using a resource editor and create dialogs. To get an app designed quickly, nothing beats a resource editor. I prefer CreateWindowEx, though sometimes I will
create the windows and controls in a resource editor and convert that to CreateWindowEx code (thanks to a Dialog to Window converter in the IDE I use. This tutorial I will use CreateWindowEx, next one I will do the same with Dialogs.

Few things before we start:
In Assembly, nothing is holding your hand like a runtime or compiler that checks your buffers, preserves the registers etc, so you have to be careful to write and read the correct addresses. Another IMPORTANT item is Register Preservation.
What the hell is that? The general purpose registers are eax, ebx, ecx, edx, esi, edi, (also ebp, and esp but these are special) Register preservation just means that when you call a Windows API, you are guaranteed
whatever value is in the registers ebx, esi, and edi before the call to an API, that same value will be there after the call. eax, ecx, and edx are not guaranteed to be preserved. Confused?

Lets say you do:
    mov     eax, 3
    invoke  MessageBox, NULL, NULL, NULL, MB_OK


and are expecting eax to still contain 3 after calling MessageBox, you are wrong. Especially with eax, the return values of most functions are returned in eax.
If you wanted eax to stay the same, you would push it onto the stack (another tutorial subject to come) and pop it off the stack after the call
    mov     eax, 3
    push    eax
    invoke  MessageBox, NULL, NULL, NULL, MB_OK
    ; check return value
    pop     eax


Or just use a register that has to get preserved like esi
    mov     esi, 3
    invoke  MessageBox, NULL, NULL, NULL, MB_OK
    ; check return value
    ; esi still contains 3


Register preservation is IMPORTANT in windows callback procs, if you use esi, edi or ebx in a callback and forget to save it, your app will bomb at some point. Luckily, MASM makes it easy to save regs with the word USES.
If you are writing a proc that does not interact with anybody else then you don't have to preserve these regs, BUT if you are writing code that some other developer is going to call, then the standard is to follow the
Register Preservation rules.

This tut, we will create a window with a button. That button when clicked, will grab a copy of your screen and "slide" it to the right (didn't know what else to do with the button, I DID NOT want the button to say hello)
.386
.model flat, stdcall
option casemap:none

include windows.inc
include masm32.inc
include kernel32.inc
include user32.inc
include comctl32.inc
include gdi32.inc
include \masm32\macros\macros.asm

includelib masm32.lib
includelib kernel32.lib
includelib user32.lib
includelib comctl32.lib
includelib gdi32.lib


Windows.inc has a bunch of equates and structures that we need. Masm32.inc has protos for functions we will call. The Windows API incs, contain protos and equates for calls.
For instance, we use CreateWindowEx but there is no such API CreateWindowEx it is known as CreateWindowExA or CreateWindowExW (for the Unicode version) So, in user32.inc if you open it, you will see
CreateWindowEx equ <CreateWindowExA>
If you wanted to, you could do MakeMeAWindow equ <CreateWindowExA> and use MakeMeAWindow instead of CreateWindowEx.

The SDK/MSDN will tell you where the API you need is located, then you just include the inc and lib for it.

next are the includelib. True, lib files can contain code that can be "statically" linked into your program, but in the API case the lib files gives the linker hints as to where the functions are at runtime.

SetControlFonts		PROTO	:DWORD,:DWORD

BTN_DONT_CLICK_ME   equ 1001


For any procedure you call with "invoke", you need a prototype to tell ML how many parameters there are and the data type so it can do some checks for you. It makes sure in our case SetControlFonts has 2 parameters, no more and no less
or it will throw an error.

The BTN_DONT_CLICK_ME equate is what I will use in our message handler to see if the button was clicked

.data
szFontName          BYTE    "Verdana", 0
szWndClsMain        BYTE    "DIC_FIRST_ASM_APP", 0
szWndClsButton      BYTE    "BUTTON", 0
szDontClickMe       BYTE    "Don't Click Me :-)", 0


Ooooh, our INITIALIZED data section. This is where we put all of the strings for the app, any initialized structures, arrays, numbers all go here. The string can be enclosed between either ' or " and is NULL terminated (0)
if you forget to NULL terminated your strings, windows will throw a hissy fit at random times in you app and you will go crazy as to why. So don't forget that 0 at the end of your strings.
.data?
hInst               DWORD   ?
hHeap               DWORD   ?
hFont               DWORD   ?
hMain               DWORD   ?
swid                DWORD   ?
shgt                DWORD   ?
hDC                 DWORD   ?
cDC                 DWORD   ?
dwid                DWORD   ?
hScr                DWORD   ?
hBmp                DWORD   ?
hOld                DWORD   ?
msg                 MSG <?>


This is where we put anything we don't need initialized (when your app starts, they are "zeroed" out), handles to controls, windows, fonts, things we need for the life of the app. They are "Global"

Explanations inline...
.code ; this is were all our code will go
FirstWindow: ; this is our entry point. 


    ; Initialize common controls
    xor     edi,edi                                     
    push    edi
    push    sizeof INITCOMMONCONTROLSEX
    invoke  InitCommonControlsEx, esp
    
comment #
    The return value from Windows API calls
    get returned in the eax register #
    invoke  GetModuleHandle, NULL
    
    ; With Intel Assembly syntax it is mov destination, source
    mov     hInst, eax

    ; Get the handle to our heap
    invoke  GetProcessHeap
    mov     hHeap, eax

    ; Create a font
    ; We could of easily used a LOGFONT structure in the .data? section
    ; But why use a global when we don't have to..
    
    ; First we will alloc some memory for our LOGFONT stucture
    ; the handle to our heap is still in eax, so we use it
    invoke	HeapAlloc, eax, HEAP_ZERO_MEMORY, sizeof LOGFONT
    
    ; esi is preserved across API calls so we will save the pointer there
    ; http://en.wikipedia.org/wiki/X86_calling_conventions
    mov     esi, eax    
    ; Next we will fill the LOGFONT structure with some info
    mov     (LOGFONT PTR [esi]).lfHeight, -11
    mov     (LOGFONT PTR [esi]).lfWeight, FW_NORMAL
    lea     edi, (LOGFONT PTR [esi]).lfFaceName
    
    ; szCatStr is a function in the MASM32 library that appends the second string to the end of the first.
    invoke  szCatStr, edi, offset szFontName
    
    ; now we pass the pointer to the LOGFONT structure to CreateFontIndirect
    invoke  CreateFontIndirect, esi
    
    ; save the handle to the font
    mov     hFont, eax
    
    ; Free the pointer to our LOGFONT structure    
    invoke  HeapFree, hHeap, 0, esi

    ; Register our window class
    ; I personally don't want to use a global var just for this one WNDCLASSEX Structure
    ; so I will alloc from our heap
    invoke  HeapAlloc, hHeap, HEAP_ZERO_MEMORY, sizeof WNDCLASSEX
    mov     esi, eax
    mov     (WNDCLASSEX PTR [esi]).cbSize, sizeof WNDCLASSEX
    mov     (WNDCLASSEX PTR [esi]).style, CS_HREDRAW or CS_VREDRAW 
    mov     eax, hInst
    mov     (WNDCLASSEX PTR [esi]).hInstance, eax
;   The above 2 lines is 1 byte smaller than the following 2 lines
;    push    hInst
;    pop     (WNDCLASSEX PTR [esi]).hInstance
    mov     (WNDCLASSEX PTR [esi]).lpfnWndProc, offset WndProcMain   
    mov     (WNDCLASSEX PTR [esi]).hbrBackground, COLOR_3DFACE + 1 
    mov     (WNDCLASSEX PTR [esi]).lpszClassName, offset szWndClsMain
    xor     eax, eax
    mov     (WNDCLASSEX PTR [esi]).hIcon, eax  
    mov     (WNDCLASSEX PTR [esi]).hIconSm, eax    
;   The above 3 lines are 8 bytes VS:   
;   The following 2 lines are 14 bytes 
;    mov     (WNDCLASSEX PTR [esi]).hIcon, 0  
;    mov     (WNDCLASSEX PTR [esi]).hIconSm, 0
    invoke  LoadCursor, NULL, IDC_ARROW
    mov     (WNDCLASSEX PTR [esi]).hCursor, eax 
    invoke  RegisterClassEx, esi
    invoke  HeapFree, hHeap, 0, esi
    
    ; Are you ready?  Lets create and display our window!
    ; First we will get the coords to center our window
		invoke	GetSystemMetrics, SM_CXSCREEN ; screen width
		
		; Here we subtract our app window width from the screen width
		sub		eax, 755
		
		; instead of dividing by 2, we shift eax right by 1
		shr		eax, 1
	
		; save the value to ebx
		xchg 	eax, ebx 
	
		invoke	GetSystemMetrics, SM_CYSCREEN ; screen height
		; subtract our window height from screen height and "divide by 2"
		sub		eax, 485
		shr		eax, 1
	
		; ##### Create main window
		; we don't need ShowWindow or UpdateWindow since we use the WS_VISIBLE style
		; ALSO, we don't "add" styles togeter, we "OR" them together
		
		invoke	CreateWindowEx, WS_EX_APPWINDOW or WS_EX_CONTROLPARENT, \
							offset szWndClsMain, \
							NULL , \
							WS_OVERLAPPED or WS_SYSMENU or WS_MINIMIZEBOX or WS_CLIPCHILDREN or WS_VISIBLE, \
							ebx, eax, \
							755, 485, \
							HWND_DESKTOP, NULL, \
							hInst, NULL
		mov		hMain, eax

    ; the infamous "Message Loop"
    ; IsDialogMessage is in here so we can nav with tab key
    ; This will dispatch all messages to the correct handler
	.while TRUE
		invoke  GetMessage, ADDR msg, NULL, 0, 0
		.break .if (!eax)
		invoke  IsDialogMessage, hMain, ADDR msg
		.if eax == FALSE
			invoke  TranslateMessage, ADDR msg
			invoke  DispatchMessage, ADDR msg
		.endif
	.endw
	
		; We made it here, so user closed app
    invoke  ExitProcess, msg.message


You don't have to use .if/.else/.elseif/.endif if you don't want to, you could use labels, cmps and jmps. For beginners, I strongly recommend .if/.endif ML won't create bad code.

This is our Window Procedure (Windows Callback) that will handle all messages sent to our window. We have 1 window so one proc, for a bigger program with multiple windows or subclasses, you will have many more.
WndProcMain proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

	; it is faster to compare a register vs memory, so we will save the message to eax and compare that.
	mov		eax, uMsg
	.if eax == WM_CREATE
		; when we called CreateWindowEx, it sent the message WM_CREATE to our window, this is where you create any controls, set fonts, any initialization.
	    ;#####  Our little button
		invoke  CreateWindowEx,
					NULL,
					offset szWndClsButton,
					offset szDontClickMe,
					BS_CENTER or BS_TEXT or BS_VCENTER or\
					WS_VISIBLE or WS_CHILD or WS_TABSTOP,
					317, 232,
					120,20, 
					hWin,
					BTN_DONT_CLICK_ME,
					hInst,
					NULL

        ; easy way to set the fonts for all controls in our window
        ; we will use the callback EnumChildWindows to set all control fonts to the font we created earlier.
        ; When you have many controls, this will enumerate all controls and pass the handle to our Proc SetControlFonts
        ; where we set the fonts.
	    	invoke	EnumChildWindows, hWin, addr SetControlFonts, NULL
				
				; If  you need to have a control in another font like bold or underline
				; this is where you set those fonts AFTER the call to EnumChildWindow (or your control won't get your new font) with SendMessage and WM_SETFONT
				
				; Many messages fall under WM_COMMAND, we only want BN_CLICKED
	.elseif eax == WM_COMMAND
		
		; The high word of wParam is the notification code.
		; move wParam into edx register
		mov		edx, wParam
		
		; the control ID is in the Low Word of wParam (edx) the low word of edx is dx
		; zero extend dx and move to eax
		movzx	eax, dx
		
		; the notification code is in the high word of edx, so we shift edx right by 16 to grab it.
		shr		edx, 16
		
		.if edx == BN_CLICKED
			.if eax == BTN_DONT_CLICK_ME
			
				;  oooh, someone clicked our button, call our proc to move the screen
                call    Magic        
			.endif
		.endif
		
	.elseif eax == WM_CLOSE 
			; our app is closing, call our proc to clean up any memory, handles, objects the destroy the window
	    call    CleanUp
		invoke  DestroyWindow, hWin
		
	.elseif eax == WM_DESTROY
		invoke  PostQuitMessage, NULL
		
	.else
		; this is for any messages we are not handling, pass them on to the default proc for the window/control
		invoke  DefWindowProc, hWin, uMsg, wParam, lParam
		ret
	.endif
	
	; for the messages we process, we have to return 0
	xor    eax,eax
	ret

WndProcMain endp

CleanUp proc
    ; in our case, we only have 1 handle to clean up
    ; in a larger app, we would clean up a lot more here.
    invoke  DeleteObject, hFont
    ret

CleanUp endp


this is the proc the callback EnumChildWindow will call with the handle of the enumerated controls
we set the fonts here.
SetControlFonts proc hWin:DWORD, lParam:DWORD

 	invoke SendMessage, hWin, WM_SETFONT, hFont, FALSE
 	
 	mov		eax, TRUE
 	ret
SetControlFonts endp


this proc is called when someone clicks our button
just makes a copy of the screen and slides it to the right.
it uses a macro from the MASM32 package rv (return value) to call the proc, and get the return value on one line.
Magic proc

    mov hScr, 0                                         ; screen handle is zero
    mov hDC,  rv(GetDC,hScr)                            ; get its DC
    mov swid, rv(GetSystemMetrics,SM_CXSCREEN)          ; get screen width
    add eax,  eax                                       ; double it
    mov dwid, eax                                       ; store it in a variable
    mov shgt, rv(GetSystemMetrics,SM_CYSCREEN)          ; get the screen height
    mov hBmp, rv(CreateCompatibleBitmap,hDC,dwid,shgt)  ; make double width bitmap
    mov cDC,  rv(CreateCompatibleDC,hDC)                ; create a DC for it
    mov hOld, rv(SelectObject,cDC,hBmp)                 ; select compatible bitmap into compatible DC

  ; ------------------------------------------------
  ; blit 2 copies of the current screen side by side
  ; ------------------------------------------------
    invoke BitBlt,cDC,0,0,swid,shgt,hDC,0,0,SRCCOPY
    invoke BitBlt,cDC,swid,0,swid,shgt,hDC,0,0,SRCCOPY

  ; --------------------------------------------------------
  ; repeatedly blit the shifting image to the current screen
  ; --------------------------------------------------------
    push esi
    mov esi, swid
  @@:
    invoke BitBlt,hDC,0,0,swid,shgt,cDC,esi,0,SRCCOPY
    ;invoke Sleep, 25                                    ; slow it up a bit
    sub esi, 8
    jns @B

    pop esi

    invoke SendMessage,0,WM_PAINT,hDC,0                 ; clean up the mess after

    invoke DeleteObject,hBmp                            ; delete the compatible bitmap
    invoke SelectObject,cDC,hOld                        ; reselect the old one
    invoke DeleteDC,cDC                                 ; delete the compatible DC
    invoke ReleaseDC,hScr,hDC                           ; release the screen DC
    
    
    ret

Magic endp


the end of our code / entry point
end FirstWindow


Next up, Our first window using a dialog instead of CreateWindowEx

This app: http://www.gunnerinc...iles/shrest.zip includes source using jmps/cmps/calls and no invoke/.if/.endif if you want to see an alternate way of coding in MASM

Attached File(s)



Is This A Good Question/Topic? 1
  • +

Replies To: MASM - Our first window

#2 masitecno  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 14-March 12

Posted 14 March 2012 - 10:29 PM

Hi,
Itīs a great post. Very usefull.
I have a question:
What changes are required to scroll to the left instead of scrolling to the right?

Thank you very much.
Was This Post Helpful? 0
  • +
  • -

#3 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,276
  • Joined: 28-March 11

Posted 15 March 2012 - 04:35 PM

Scrolling takes place in the magic proc, play around with it!!!
Was This Post Helpful? 0
  • +
  • -

#4 don57  Icon User is offline

  • D.I.C Head

Reputation: 0
  • View blog
  • Posts: 56
  • Joined: 15-March 12

Posted 15 March 2012 - 04:51 PM

If your interested in assembly i suggest the masm forum.
Was This Post Helpful? 0
  • +
  • -

#5 masitecno  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 14-March 12

Posted 16 March 2012 - 10:31 AM

Thanks! I have made some changes and it can go left but it never stops. Any idea?


Add Esi, 1
Cmp Esi, swid
Jne @B
Was This Post Helpful? 1
  • +
  • -

#6 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,276
  • Joined: 28-March 11

Posted 16 March 2012 - 06:07 PM

I will +1 you for giving it a shot and not say give me the code! :-) You are on the right track though.
comment out line 229 and add xor esi, esi instead. The add esi, 1 will make it scroll really, really slow! Here is the moded code to scroll left.

  ; --------------------------------------------------------
  ; repeatedly blit the shifting image to the current screen
  ; --------------------------------------------------------
    push esi
    ;mov esi, swid
    xor     esi, esi
  @@:
    invoke BitBlt,hDC,0,0,swid,shgt,cDC,esi,0,SRCCOPY
    ;PrintDec esi                                ; slow it up a bit
    add     esi, 8
    cmp     esi, swid
    jne @B
    ;jns @B

    pop esi


Or to keep the same code using sub esi, just change the pos of esi in BitBlt

    push    esi
    mov     esi, swid
  @@:
    invoke  BitBlt,hDC,esi,0,swid,shgt,cDC,0,0,SRCCOPY
    sub     esi, 8
    jns     @B

    pop     esi

That is the original code with esi as a different param of BitBlt, see it?

Now, how would you make it scroll top to bottom, or bottom to top?

This post has been edited by GunnerInc: 16 March 2012 - 06:07 PM

Was This Post Helpful? 0
  • +
  • -

#7 masitecno  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 14-March 12

Posted 12 April 2012 - 06:44 PM

After almost a month...

We should start by declaring new variables at the .data? section:

dhgt DWord ? ;save double height
vBmp DWord ? ;save double bitmap 2

Then we should double screen height at the begining of the Magic proc instead of

doubling screen width.

    mov shgt, rv(GetSystemMetrics,SM_CYSCREEN)          ; get the screen height
    add eax,  eax                                       ; double it
    mov dhgt, eax                                       ; store it in a variable


Then make double height bitmap, but in this time set screen width with double height:
    Mov vBmp, rv(CreateCompatibleBitmap, hDC, swid, dhgt)  ; make double height bitmap



Then select compatible bitmap into compatible DC with the new bitmap:
    Mov hOld, rv(SelectObject, cDC, vBmp)  ;select compatible bitmap into compatible DC


Then:
	; blit 2 copies of the current screen side by side vertical
    Invoke BitBlt, cDC, 0, 0, swid, shgt, hDC, 0, 0, SRCCOPY
    Invoke BitBlt, cDC, 0, shgt, swid, shgt, hDC, 0, 0, SRCCOPY


Then apply some changes to move vertical instead of landscape, the reste keeps the same.
; --------------------------------------------------------
  ; repeatedly blit the shifting image to the current screen
  ; --------------------------------------------------------
    push esi
    Xor Esi, Esi
  @@:
    Invoke BitBlt, hDC, 0, 0, swid, shgt, cDC, 0, Esi, SRCCOPY ; move vertical index

	Add Esi, 8
	Cmp Esi, shgt	;compare vertical
	Jne @B
    
    Pop Esi


At the end delete also the new bitmap:
    Invoke DeleteObject, vBmp                     ; delete the compatible bitmap


I think that's all.

Hope it works.

Well I already tested it, it worked!
But the same issue as in the begining...
Was This Post Helpful? 0
  • +
  • -

#8 masitecno  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 4
  • Joined: 14-March 12

Posted 12 April 2012 - 06:51 PM

Dear Gunner:

How could I, instead of an static bitmap, could generate that circleing effect but with a bitmap than could change its content in runtime.

I mean, i need to hava a bitmap memory changing the last column at the right side and then updating that column many times with incoming data to setpixels and scrolling the image to the left keeping that kind of speed you showed me?

I would appreciate some guidance.

Thanks.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1