News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

In the gui path...

Started by felipe, October 31, 2017, 05:48:44 AM

Previous topic - Next topic

felipe

I decided to work with the petzold book ( ::) yes, you can say i'm an old fashion dude  :bgrin:) to enter more deeply in the windows gui programming.  :exclaim: But as a close reference to the win api functions or windows related topics, no intention to imitate the c programs in the book  :badgrin:.

Here it is the first program based on the first example of that book (.exe attached):

**Wait! the program is in that way writed just for clarity, no big intention i did to do an optimized program**   :redface:

Ok, now have a look:
:biggrin:


; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Started by Felipe the 2017-10-29
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

.386
.model      flat,stdcall
option      casemap:none
include     \masm32\include\windows.inc
include     \masm32\include\kernel32.inc
include     \masm32\include\user32.inc
includelib  \masm32\lib\kernel32.lib
includelib  \masm32\lib\user32.lib

.data
align 1
widbuff     byte        4 dup(' ')
heibuff     byte        4 dup(' ')
errotit     byte        'ERROR DETECTED',0
msgerro     byte        'There was an error with the program.',0ah,0dh,\
                        'You can try to use a debugger to watch it.',0
scretit     byte        'SCREEN SIZE IN PIXELS',0
scremsg     byte        'WIDTH: ',4 dup(' '),0ah,0dh,'HEIGHT: ',4 dup(' '),0

.data?
align 4
widthx      dword       ?
heighty     dword       ?

.code
align 4
start:

            push        SM_CXSCREEN                                             ; Get the screeen width in pixels.
            call        GetSystemMetrics                                       

            cmp         eax,0           
            je          erro                                                    ; In case of error show a message and exit.

            mov         widthx,eax                                              ; Store the width here.

            push        SM_CYSCREEN                                             ; Get the screen height in pixels.
            call        GetSystemMetrics

            cmp         eax,0       
            je          erro                                                    ; In case of error show a message and exit.

            mov         heighty,eax                                             ; Store the height here.

            mov         eax,widthx                                              ; Lets process the width first.
            mov         esi,offset widbuff+3                                    ; From the less significant digit.
            mov         ecx,10                                                  ; We divide by 10 to convert the binary
                                                                                ;   into unpacked bcd.

align 4
getbcd:
            xor         edx,edx                                                 ; Cleaning the unpacked bcd temporal storage.
            div         ecx                                                     ; Dividing the width number by 10.   
            mov         [esi],dl                                                ; Moving to the width buffer the less significant digit (u_bcd).   
            dec         esi                                                     ; Next digit (from right to left).
            cmp         eax,ecx                                                 ; If the dividend is greater or equal
            jae         getbcd                                                  ;   to 10 we continue.
            mov         [esi],al                                                ; Moving the most significand digit into the width buffer.

            mov         esi,offset widbuff                                      ; From the most significand digit now.
            mov         edx,4                                                   ; 4 digits cycle.

align 4
getascii:
            or          byte ptr[esi],30h                                       ; Making an unpacked bcd an ascii number.
            inc         esi                                                     ; Next one (from left to right).
            dec         edx                                                     ; Decrement the counter.
            jnz         getascii

            mov         eax,heighty                                             ; Lets process now the height.
            mov         esi,offset heibuff+3                                    ; From the less significant digit.
            mov         ecx,10                                                  ; We divivde by 10 to convert the binary
                                                                                ;   into unpacked bcd.

align 4
getbcd2:
            xor         edx,edx                                                 ; Cleaning the unpacked bcd temporal storage.
            div         ecx                                                     ; Dividing the height number by 10.   
            mov         [esi],dl                                                ; Moving to the height buffer the less significant digit (u_bcd).   
            dec         esi                                                     ; Next digit (from right to left).
            cmp         eax,ecx                                                 ; If the dividend is greater or equal
            jae         getbcd2                                                 ;   to 10 we continue.
            mov         [esi],al                                                ; Moving the most significand digit into the height buffer.

            mov         esi,offset heibuff                                      ; From the most significand digit now.
            mov         edx,4                                                   ; 4 digits cycle.

align 4
getascii2:
            or          byte ptr[esi],30h                                       ; Making an unpacked bcd an ascii number.
            inc         esi                                                     ; Next one (from left to right).
            dec         edx                                                     ; Decrement the counter.
            jnz         getascii2

            mov         esi,offset widbuff                                      ; The most significand digit.   
            cmp         byte ptr[esi],30h                                       ; Is a zero? (like in 800x600: only 3 digits for the width).
            jne         heispace                                                ; If not we now check the height string.
            sub         byte ptr[esi],10h                                       ; Convert that 30h (ascii zero number) into a 20h (ascii space).

align 4
heispace:
            mov         esi,offset heibuff                                      ; The most significand digit.
            cmp         byte ptr[esi],30h                                       ; Is a zero? (like in 800x600: only 3 digits for the height).
            jne         movvar                                                  ; If not we are almost ready to write the string.
            sub         byte ptr[esi],10h                                       ; Convert that 30h (ascii zero number) into a 20h (ascii space).


align 4
movvar:
            mov         edi,offset scremsg+7                                    ; We will put here (in the message box)
            mov         esi,offset widbuff                                      ;  what's here.
            movsd

            mov         edi,offset scremsg+21                                   ; We will put here (in the message box)
            mov         esi,offset heibuff                                      ;  what's here.
            movsd

   
            push        MB_OK                                                   ; Just one button.
            push        offset scretit
            push        offset scremsg
            push        NULL
            call        MessageBox                                              ; Show the screen size in pixels.
            jmp         normal                                                  ; Finish the program.

align 4
erro:
            push        MB_OK
            push        offset errotit
            push        offset msgerro
            push        NULL
            call        MessageBox

align 4
normal:
            push        0
            call        ExitProcess

            end         start

felipe

And here it is the next program (based in the next example of the book) The .exe is attached:
:idea:


; ¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤
; This program was started by Felipe in 2017-11-03.
; ¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤


.386
.model      flat,stdcall
option      casemap:none
include     \masm32\include\windows.inc
include     \masm32\include\kernel32.inc
include     \masm32\include\user32.inc
include     \masm32\include\gdi32.inc
includelib  \masm32\lib\kernel32.lib
includelib  \masm32\lib\user32.lib
includelib  \masm32\lib\gdi32.lib

.data
align 1
wndclaname  byte        "EXAMPLE WINDOW CLASS",0                                            ; Name of the window class.
wndname     byte        "WINDOW EXAMPLE",0                                                  ; Title bar of the window.                                                 
msghello    byte        "Well, here it is a way of how to do a window in Windows. ",\
                        "There are many others by the way.",0                               ; Message drawed in the window client area.

.data?
align 4
insthan     dword       ?                                                                   ; Instance handle.
wndclaex    dword       12 dup(?)                                                           ; WNDCLASSEX structure.
wndhand     dword       ?                                                                   ; Window handle.
msgstru     dword       7 dup(?)                                                            ; MSG structure.
hdc         dword       ?                                                                           
painstru    dword       12 dup(?)                                                           ; PAINTSTRUCT structure.
rect        dword       4 dup(?)                                                            ; RECT structure.

.code
align 4
start:
            push        NULL
            call        GetModuleHandle           
            mov         insthan,eax

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Fill the WNDCLASSEX structure:

            mov         dword ptr wndclaex[0],sizeof wndclaex                               ; Size of structure.
            mov         dword ptr wndclaex[4],CS_HREDRAW or CS_VREDRAW                      ; Style of class.
            mov         wndclaex[8],wndproc                                                 ; Address of wndproc (callback proc).
            mov         dword ptr wndclaex[12],NULL
            mov         dword ptr wndclaex[16],NULL
            mov         eax,insthan                                                     
            mov         wndclaex[20],eax
            push        500
            push        insthan
            call        LoadIcon
            mov         wndclaex[24],eax
            push        IDC_ARROW
            push        NULL
            call        LoadCursor
            mov         wndclaex[28],eax
            push        WHITE_BRUSH
            call        GetStockObject
            mov         wndclaex[32],eax
            mov         dword ptr wndclaex[36],NULL
            mov         wndclaex[40],offset wndclaname                                      ; Address of the class name.
            mov         dword ptr wndclaex[44],NULL

            push        offset wndclaex                                                     ; Address of the WNDCLASSEX structure.
            call        RegisterClassEx
           
            push        NULL
            push        insthan
            push        NULL
            push        NULL
            push        CW_USEDEFAULT
            push        CW_USEDEFAULT
            push        CW_USEDEFAULT
            push        CW_USEDEFAULT
            push        WS_OVERLAPPEDWINDOW
            push        offset wndname                                                      ; Address of the string displayed in the title bar.
            push        offset wndclaname                                                   ; Address of the WNDCLASSEX structure.
            push        WS_EX_OVERLAPPEDWINDOW
            call        CreateWindowEx
            mov         wndhand,eax                                                         ; Store the window handle.

            push        SW_SHOWNORMAL
            push        eax
            call        ShowWindow

            push        wndhand
            call        UpdateWindow

align 4
msgloop:
            push        0
            push        0
            push        NULL
            push        offset msgstru                                                      ; Address of the MSG structure.
            call        GetMessage
            cmp         eax,0
            je          msgloend

            push        offset msgstru
            call        TranslateMessage                                                    ; For some keyboard processing.

            push        offset msgstru
            call        DispatchMessage                                                     ; Sends a message to a wndproc.
            jmp         msgloop               

align 4
msgloend:                       
            push        dword ptr msgstru[8]
            call        ExitProcess                                                         ; Terminates the program.

align 4
wndproc:
            push        ebp
            mov         ebp,esp
           
            cmp         dword ptr[ebp+12],WM_CREATE
            jne         paint
           
            xor         eax,eax
            mov         esp,ebp
            pop         ebp
            ret         16

align 4
paint:
            cmp         dword ptr[ebp+12],WM_PAINT
            jne         destwnd

            push        offset painstru
            push        wndhand
            call        BeginPaint                                                         

            mov         hdc,eax

            push        offset rect
            push        wndhand
            call        GetClientRect                                                       ; Get the dimensions of the client area.

            push        DT_SINGLELINE or DT_CENTER or DT_VCENTER
            push        offset rect
            push        -1
            push        offset msghello
            push        hdc
            call        DrawText                                                            ; Write a string in the client area.

            push        offset painstru
            push        wndhand
            call        EndPaint

            xor         eax,eax           

            mov         esp,ebp
            pop         ebp
            ret         16

align 4
destwnd:
            cmp         dword ptr[ebp+12],WM_DESTROY
            jne         msgdefau
           
            push        0
            call        PostQuitMessage
           
            xor         eax,eax
            mov         esp,ebp
            pop         ebp
            ret         16

align 4
msgdefau:
            push        dword ptr[ebp+20]
            push        dword ptr[ebp+16]
            push        dword ptr[ebp+12]
            push        dword ptr[ebp+8]
            call        DefWindowProc                                                       ; Default message processing.

            mov         esp,ebp
            pop         ebp
            ret         16
           
            end         start



It's guided in the book's example, is not a copy  :icon_exclaim:. The comments aren't too good because i'm still learning the details of the functions.  :P

Mikl__

¡Hola, Felipe!
If you will use "push WS_OVERLAPPEDWINDOW or WS_VISIBLE" then you do not need
            push        SW_SHOWNORMAL
            push        eax
            call        ShowWindow

            push        wndhand
            call        UpdateWindow
try to do so            push        NULL
            push        insthan
            push        NULL
            push        NULL
            mov         eax,CW_USEDEFAULT
            push        eax
            push        eax
            push        eax
            push        eax
            push        WS_OVERLAPPEDWINDOW or WS_VISIBLE
            push        offset wndname                                                      ; Address of the string displayed in the title bar.
            push        offset wndclaname                                                   ; Address of the WNDCLASSEX structure.
            push        WS_EX_OVERLAPPEDWINDOW
            call        CreateWindowEx
            mov         wndhand,eax                                                         ; Store the window handle.         
            mov         edi,offset msgstru
msgloop:
            push        0
            push        0
            push        NULL
            push        edi                                                      ; Address of the MSG structure.
            call        GetMessage
            or         eax,eax
            je          msgloend

            push        edi
            call        TranslateMessage                                                    ; For some keyboard processing.

            push        edi
            call        DispatchMessage                                                     ; Sends a message to a wndproc.
            jmp         msgloop
P.S. Hi, jj2007! Grazie per l'emendamento!

felipe

Thanks Mikl_! I did it and all worked better. I liked those improvements. I certainly want to reach a good optimized assembly programming level.  8)

:icon14: :icon14: :icon14:

Mikl__

#4
Bueno, Philipe, sabía que te gustaría.
invoke GetModuleHandle,NULL
mov x1,eax
invoke LoadIcon,NULL,IDI_APPLICATION
mov x2,eax
        invoke LoadCursor,NULL,IDC_ARROW
mov x3,eax
push x1
push x2
push x3
        push offset fmt 
        push offset buffer     
call wsprintf
        add esp,20
        invoke MessageBox,NULL,addr buffer,addr MsgCaption,MB_OK
        ....
.data
fmt db "hCursor = %04Xh",0Ah,"hIcon = %04Xh",0Ah,"hInstance =%04Xh",0
buffer db 30 dup(?)
x1 dd ?
x2 dd ?
x3 dd ?
Una vez que escriba tal programa, observe qué valores devuelven las funciones GetModuleHandle, LoadIcon y LoadCursor, y no tiene que llamarlas una y otra vez cuando cree un programa de ventana. En tu versión de Windows, estos valores no cambian. Intenta hacer esto ...

felipe

The values are:
hCursor:10003h
hIcon:10027h
hInstance:400000h

I don't undestand. Maybe this values are always the same values and i don't have to call the GetModuleHandle function, but if i don't call the other two functions how i will load the icon and the cursor? Or you mean i just have to call them once in each program independently of the number of windows that i create?  :redface:


Mikl__

¡Hola, Felipe!
example for your program; ¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤
; This program was started by Felipe in 2017-11-03.
; ¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤=÷=¤


.386
.model      flat,stdcall
option      casemap:none
include     \masm32\include\windows.inc
include     \masm32\include\kernel32.inc
include     \masm32\include\user32.inc
include     \masm32\include\gdi32.inc
includelib  \masm32\lib\kernel32.lib
includelib  \masm32\lib\user32.lib
includelib  \masm32\lib\gdi32.lib

.data
wndname     byte        "WINDOW EXAMPLE",0                                                  ; Title bar of the window.                                                 
msghello    byte        "Well, here it is a way of how to do a window in Windows. ",\
                        "There are many others by the way.",0                               ; Message drawed in the window client area.

.data?                                                         
wndclaex    WNDCLASSEX <>
msgstru     MSG <>
hdc         dword       ?                                                                           
painstru    PAINTSTRUCT <>
rect        RECT <>
wndhand     dword       ?                                                                   ; Window handle
.code         
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Fill the WNDCLASSEX structure:
start:
            mov         wndclaex.cbSize,sizeof wndclaex                        ; Size of structure.
            mov         wndclaex.style,CS_HREDRAW or CS_VREDRAW; Style of class.
            mov         wndclaex.lpfnWndProc,offset wndproc                                                 ; Address of wndproc (callback proc).
            mov         wndclaex.cbClsExtra,NULL
            mov         wndclaex.cbWndExtra,NULL
            mov         wndclaex.hInstance,400000h                                               ; Instance handle
            mov         wndclaex.hIcon,10003h ; icon
            mov         wndclaex.hCursor,10027h ; cursor
            push        WHITE_BRUSH
            call          GetStockObject
            mov         wndclaex.hbrBackground,eax
            mov         wndclaex.lpszMenuName,NULL
            mov         wndclaex.lpszClassName,offset wndname                                      ; Address of the class name.
            mov         wndclaex.hIconSm,10003h

            push        offset wndclaex                                                     ; Address of the WNDCLASSEX structure.
            call          RegisterClassEx
           
            push        NULL
            push        400000h                                                              ; Instance handle
            push        NULL
            push        NULL
            mov         eax,CW_USEDEFAULT
            push        eax
            push        eax
            push        eax
            push        eax
            push        WS_OVERLAPPEDWINDOW or WS_VISION
            push        offset wndname                                                      ; Address of the string displayed in the title bar.
            push        offset wndname                                                   ; Address of the WNDCLASSEX structure.
            push        WS_EX_OVERLAPPEDWINDOW
            call           CreateWindowEx
            mov         wndhand,eax                                                         ; Store the window handle
            mov         edi,offset msgstru                                                      ; Address of the MSG structure
msgloop:
            push        0
            push        0
            push        NULL
            push        edi                                                      ; Address of the MSG structure.
            call        GetMessage
            push        edi
            call        TranslateMessage                                                    ; For some keyboard processing
            push        edi
            call        DispatchMessage                                                     ; Sends a message to a wndproc.
            jmp         msgloop

wndproc:
            push        ebp
            mov         ebp,esp
           
            cmp         dword ptr [ebp+12],WM_CREATE
            je         create
            cmp         dword ptr[ebp+12],WM_PAINT
            je         paint
            cmp         dword ptr[ebp+12],WM_DESTROY
            je         destwnd
msgdefau:
            leave
            jmp        DefWindowProc                                                       ; Default message processing
destwnd:                     
            push        0
            call        ExitProcess                                                         ; Terminates the program
paint:         
            push        offset painstru
            push        wndhand
            call        BeginPaint                                                         
            mov         hdc,eax

            push        offset rect
            push        wndhand
            call        GetClientRect                                                       ; Get the dimensions of the client area.

            push        DT_SINGLELINE or DT_CENTER or DT_VCENTER
            push        offset rect
            push        -1
            push        offset msghello
            push        hdc
            call        DrawText                                                            ; Write a string in the client area.

            push        offset painstru
            push        wndhand
            call        EndPaint
create:
            xor         eax,eax         
            leave
            ret         16                     
            end         start
For your version of Windows, the values for the cursor and the icon will always be the same, so calling the functions of the LoadIcon and LoadCoursor is superfluous. To change the value that the function GetModuleHandle returns, when building the program, if you use the option /BASE:0xNumber  If this option is not used, the default value of Number is 400000h always.

felipe

I see. I guess loadicon and loadcursor is just to get those constants. But what if i want a custom icon (like that in the program above, version 1)?

PD: cool stuff, if i store the icon in the cursor location in wndclass and viceversa i get in the client area of the window the icon for the program as the cursor.  :lol:

Also i suppose that to run the program in another version of windows i will have to use those functions, right?

Mikl__

You will use those functions for to run the program in another version of windows, it is right. The version of Windows is like fashion. Most users (90%) have the same version of Windows. About icons and cursors
Quoteif i store the icon in the cursor location in wndclass and viceversa i get in the client area of the window the icon for the program as the cursor
you look in Chapter #11. Brer Rabbit studies icons and cursors

hutch--

There are some things that I always make GLOBAL, the instance handle, main window handle, icon and cursor handles and any of those things that only need to be loaded once. If you are calling either external files like a DLL or a library module, you pass any of them as required. Things like this are determined at the scope level, things that you need as LOCAL within a procedure are easily dealt with as locals where data that is required across many different parts of an application are best dealt with as GLOBAL scope variables.

It was fashion in the early to middle 1990s to desperately avoid using GLOBAL variables as a prelude to OOP coding but it also produced some terrible code with multiple duplications of simple things like getting the instance handle "invoke GetModuleHandle,0" when you only ever needed to do this once and store it at GLOBAL scope. Also be careful about using the constant as the instance handle as this can be altered by the linker, one API call will not blow out the file size and it is the safe way to do it.

Mikl__

Good morning, hutch--!
you are right, but usually assembler programs are written for author-self, and not for commercial distribution, therefore I gave these advice to Felipe

felipe

Indeed i think is a very nice information about the windows system (great page).
But yes, for doing programs for others it's better to use the api calls that do the jobs.

:icon14: :icon14:


jj2007

Quote from: felipe on November 05, 2017, 01:17:10 PMfor doing programs for others it's better to use the api calls that do the jobs.

Just imagine that in a year or two, your computer gets stolen and your new machine has Windows 11 installed. Do you want to rewrite all your code?

Mikl__

Buona mattina, jj2007!
I'm not Michelangelo di Lodovico di Leonardo di Buonarroti Simoni and my programs will become obsolete in a year and I will not write any more as I would write a year ago. And there will be commands that will no longer support new processors that were two or five years ago. Programming in assembler is fast ... and I do not need to rewrite ALL the code I wrote a year ago

hutch--

I tend to walk a hard line here, there will always be hobbyist code that people use locally to get a job done and I have a reasonable amount of "Write once, Run once" code that I use a basic compiler for which I can pelt out in a few minutes to do things like write tables and similar messy things to write manually but there are only 2 classes of code that gets released, professional class high reliability binaries and TRASH.

The vast number of people who have written MASM code over the years need it for something serious and it must be professional quality and this means using properly documented functions (API and some VC runtime code), code that fully complies with the ABI for the platform (Intel for win32 and Microsoft for Win 64) and algorithm code that has had the guts kicked out of it to ensure that it is reliable across multiple versions of Windows. With 64 bit you can safely use most of the later instructions but you need to do a CPUID test if you want to use the latest SSE4.2 and AVX and later instructions.

Stay away from later OS versions of specific API code if you can do it with an earlier API as it will run on more processors and OS versions and write tricks and undocumented functions at your own risk as there is no reason to think they will be supported in later OS versions. The other thing that cannot be overstressed is understand and stick to the published ABI register usages as this has been reliable since the start of Win32. Same with Win 64, you have a Microsoft ABI on which registers to use and which to protect and if you get it wrong, it will explode in your face and make you look like a jerk who doesn't understand the OS specifications.