News:

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

Main Menu

Win32 My First Window

Started by tda0626, April 24, 2024, 11:05:19 AM

Previous topic - Next topic

tda0626

Hello,

I programmed my first Win32 Window but it immediately closes once it runs. I was wondering if someone could look over my code and see what I have done wrong or even make some suggestions. I went by a template in C on Microsoft's website so not sure what I did wrong.

.386
.model flat, stdcall
option    casemap:none

include    E:\masm32\include\windows.inc
include    E:\masm32\include\user32.inc
include    E:\masm32\include\kernel32.inc
include    E:\masm32\include\gdi32.inc

includelib    E:\masm32\lib\user32.lib
includelib    E:\masm32\lib\kernel32.lib
includelib    E:\masm32\lib\gdi32.lib

WindowHeight    equ    640
WindowWidth        equ 480



.data


szWindowClass    db "Desktop App",0
szTitle            db "My Window Title",0
reg_failed        db "Call to CreateWindow failed!",0
mywindow        db "This is my window!",0
greeting        db "Hello There!"
wc    WNDCLASSEX    {0}        ;Allocate Space for Window Class Structure
ps    PAINTSTRUCT    {0}        ;Allocate Space for Paint Structure -- For WM_PAINT


.code

start:
   

WinMain Proc hinstance:HINSTANCE, hprevinstance:HINSTANCE, lpCmdLine:LPSTR, nCmdShow:dword
   
    ;;;;;;;;;;;;;;;;;;;;;;
    ;Extra Local Variables
    ;;;;;;;;;;;;;;;;;;;;;;
   
    local msg:MSG
    local hwnd:HWND
   
    ;;;;;;;;;;;;;;;;;;;;;;;
    ;Window Class Structure
    ;;;;;;;;;;;;;;;;;;;;;;;
   
    mov wc.cbSize, sizeof WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc, offset WndProc
    mov  wc.cbClsExtra, 0
    mov  wc.cbWndExtra, 0
    push hinstance
    pop wc.hInstance
    mov wc.hIcon, 0
    mov wc.hCursor, 0
    mov wc.hbrBackground, COLOR_WINDOW+1
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset szWindowClass
    mov wc.hIconSm, 0
   
    ;;;;;;;;;;;;;;;;;;;;;;
    ;Register Window Class
    ;;;;;;;;;;;;;;;;;;;;;;
   
        lea eax, wc
        push eax
        call RegisterClassEx
        cmp eax, 0
        jnz RegisterSuccess
   
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;Check for Registration Error
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   
    RegisterFailed:
        push 0
        push offset szTitle
        push offset reg_failed
        push 0
        call MessageBox
        ret
   
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;If Registration is a success continue
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   
    RegisterSuccess:
   
    ;;;;;;;;;;;;;;;
    ;Create Window
    ;;;;;;;;;;;;;;
   
    push 0
    push hinstance
    push 0
    push 0
    push WindowWidth
    push WindowHeight
    push CW_USEDEFAULT
    push CW_USEDEFAULT
    push WS_OVERLAPPEDWINDOW + WS_VISIBLE
    lea    eax, mywindow
    push eax
    lea  eax, szWindowClass
    push eax
    push WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE
    call CreateWindowEx
   
    ;Return Value of CreateWindowEx is a handle to a window -- Move return value into hwnd
   
    mov hwnd, eax
   
    ;Check to see if Create Window Failed
   
    cmp eax, 0
    jz RegisterFailed
   
    ;Update the Window
   
    push hwnd
    call UpdateWindow
    push nCmdShow
    push hwnd
    call ShowWindow
   
    ;;;;;;;;;;;;;
    ;Message Loop
    ;;;;;;;;;;;;;   
   
    .while eax !=0
    push 0
    push 0
    push hwnd
    lea eax, msg
    push eax
    call GetMessage   
    lea eax, msg
    push eax
    call TranslateMessage
    lea eax, msg
    push eax
    call DispatchMessage
    .endw
   
   
    ret
   
WinMain endp


WndProc Proc hWnd:HWND, umsg:dword, wParam:WPARAM, lParam:LPARAM
   
    ; Define a local variable to a handle to device context
   
    local hdc:HDC
   
    ;;;;;;;;;;;;;;;;;;;;;;;;
    ;Process Window Messages
    ;;;;;;;;;;;;;;;;;;;;;;;;
   
    .if umsg==WM_PAINT
        push offset ps
        push hWnd
        call BeginPaint
        mov hdc, eax
        push lengthof greeting
        push offset greeting
        push 5
        push 5
        push hdc
        call TextOut            ;Print String Inside Window
        push offset ps
        push hWnd
        call EndPaint
        ret
    .elseif umsg==WM_DESTROY
        push 0
        call PostQuitMessage
        ret
    .else   
        push lParam
        push wParam
        push umsg
        push hWnd
        call DefWindowProc
        ret
    .endif
   
 WndProc endp
   



end start


NoCforMe

Just a few things at first:

1. As long as you create your window with the WS_VISIBLE style, no need to call UpdateWindow() or ShowWindow(); the window will appear as soon as it's created.

2. Please, please don't use the old-school "push-push-call" style! It's not only ugly but error prone (especially when using a function that takes lots of parameters like CreateWindowEx(). Use INVOKE instead:

INVOKE CreateWindowEx,
Exstyles, ;EXstyles
className, ;class name
windowName, ;window title/text
styles, ;styles
x, ;X-pos
y, ;Y-pos
width, ;width
height, ;height
parent, ;parent
menu, ;menu/ID
instance, ;instance handle
param ;param

Sorry, can't tell why the window closes immediately, but I'll keep looking over your code.
Assembly language programming should be fun. That's why I do it.

tda0626

Quote1. As long as you create your window with the WS_VISIBLE style, no need to call UpdateWindow() or ShowWindow(); the window will appear as soon as it's created.

2. Please, please don't use the old-school "push-push-call" style! It's not only ugly but error prone (especially when using a function that takes lots of parameters like CreateWindowEx(). Use INVOKE instead:

Thanks for the tips!

Tim

Greenhorn

.if umsg==WM_PAINT
    ...
    xor  eax, eax    ; return zero !
    ret
.elseif umsg==WM_DESTROY
    ...
    xor  eax, eax    ; return zero !
    ret

You can also put the ret instruction at the end of the WndProc only once, instead of putting it after each message.

.if umsg==WM_PAINT
    ...
    xor  eax, eax    ; return zero !
.elseif umsg==WM_DESTROY
    ...
    xor  eax, eax    ; return zero !
.else
push lParam
push wParam
push umsg
push hWnd
call DefWindowProc    ; return result of call
.endif
ret
WndProc endp
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

zedd151

I don't see a call to ExitProcess anywhere in your code..
That's not making it 'not run' though, just mentioning it.

In your Masm32 directory, there is a folder "examples" that will help you get started.
You might also want to get a debugger. I recommend ollydbg version 1.10 https://www.ollydbg.de/odbg110.zip

This will help you debug your code.

sinsi

call ShowWindow

;;;;;;;;;;;;;
;Message Loop
;;;;;;;;;;;;;

.while eax !=0
Have a guess what ShowWindow returns...

NoCforMe

Yes; to explain Greenhorn's reply, you need to realize that in Windows programming, it's (usually) important what you return when you handle a message (or don't handle it) in a window procedure. In most cases, returning zero means "I handed this message", which tells Windows that it can remove your WM_PAINT request from the message queue.

If you're not already familiar with it, you should be referring to Microsoft's extensive Windows documentation repository. It's huge, and can be confusing. Here's one way to access it: Windows functions in alphabetical order.

Here's the documentation on WM_PAINT.
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: sinsi on April 24, 2024, 12:07:31 PM    call ShowWindow
   
    ;;;;;;;;;;;;;
    ;Message Loop
    ;;;;;;;;;;;;;   
   
    .while eax !=0
Have a guess what ShowWindow returns...

True.

And check for errors.

    .while (TRUE)
        invoke GetMessage, addr msg, 0, 0, 0
        .break .if (!eax)
        .if (eax == -1)
            ; Check for error
           
            invoke MessageBox,
                    NULL,
                    addr szErrorMsg,
                    addr szAppName,
                    MB_ICONERROR or MB_OK
            mov eax, -1
            ret
        .endif
        invoke TranslateAccelerator, g_hwndMain, haccel, addr msg
        .if (!eax)
            invoke TranslateMessage, addr msg
            invoke DispatchMessage,  addr msg
        .endif
    .endw

    mov eax, msg.wParam
    ret
WinMain endp

Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

sinsi

Your message loop logic is wrong, you need to check the return value of GetMessage.
Here's a snippet from a MASM32 example
    StartLoop:
      invoke GetMessage,ADDR msg,NULL,0,0         ; get each message
      cmp eax, 0                                  ; exit if GetMessage()
      je ExitLoop                                 ; returns zero
      invoke TranslateMessage, ADDR msg           ; translate it
      invoke DispatchMessage,  ADDR msg           ; send it to message proc
      jmp StartLoop
    ExitLoop:

jj2007

Quote from: sinsi on April 24, 2024, 12:07:31 PMHave a guess what ShowWindow returns...

Sinsi nailed it, as usual ;-)

Hi Tim,

Read carefully what the others wrote, they are all right. Re push-push-pop style: that's not the immediate reason why your code fails, but try (after fixing the bug found by Sinsi)

WindowHeight    equ    140 ; <<<
WindowWidth    equ    480

You will be surprised ;-)

The built-in invoke macro prevents you from committing such blunders, and even barks at you if you didn't count correctly to 12. Besides, it is easier to translate C to ASM because it looks almost like Microsoft's example code:
  invoke CreateWindowEx, WS_EX_ACCEPTFILES, addr szWindowClass, addr mywindow,
     WS_OVERLAPPEDWINDOW or WS_VISIBLE,
     CW_USEDEFAULT, CW_USEDEFAULT, WindowWidth, WindowHeight,
     0, 0, wc.hInstance, NULL

Regarding the message loop, have a look at this template (93 lines, with a menu, an edit control and a WM_PAINT handler) or at a loop that is foolproof.

Two minor problems:
1. Or, not add:
push WS_OVERLAPPEDWINDOW + WS_VISIBLEWhen combining two flags, never use the plus sign. Use or instead. It's not a problem in this specific case, but Windows does have constants composed of more than one value, and adding instead of oring will lead to bugs that are difficult to chase.

2. include    E:\masm32\include\windows.inc
That will not work for most of us here who don't have their Masm32 SDK on drive E: :cool:
Use include \masm32\include\windows.inc, i.e. no drive letter, just a backslash.

Even better, use include \masm32\include\masm32rt.inc (open that file in Notepad, and you'll understand why it's better).

P.S., reply #7: The error handler inside the message loop is not a good idea.

Vortex

Hi tda0626,

You would like to visit Iczelion's article :

QuoteTutorial 3: A Simple Window

http://www.interq.or.jp/chubu/r6/masm32/tute/tute003.html

The example on the same page :

http://www.interq.or.jp/chubu/r6/masm32/tute/zips/tut03.zip

tda0626

Hey everyone! Thank you all for your help!

OK, I got it to work after fixing the loop logic and applied some of the suggested code edits. It displays fine now and it maximizes, minimizes, resize, ect. However, after closing the window, it is still showing up in my process list and I have to force close it. I added ExitProcess to the end of WinMain but it appears it doesn't close the process. Any ideas why the program is still running?


Tim

zedd151

#12
attach a zip file of what you now have. :smiley:

NoCforMe

Quote from: tda0626 on April 25, 2024, 08:59:14 AMHey everyone! Thank you all for your help!

Something that might help in your beginning assembly-language programming efforts:
Once you get the hang of it you're not going to want to create each new program from scratch.
This is my "skeleton" that I use to start a new Windows GUI program with a main window. Fill it in with your stuff and it's guaranteed to work. It creates an empty window, centered on the desktop.

Not trying to force you into my particular programming style. We each have our preferred style; you'll find yours. Feel free to slice, dice and hack this any way you like.

;============================================
; -- Skeleton --
; -- [project title here] --
;============================================

.nolist
include \masm32\include\masm32rt.inc
.list

;============================================
; Defines, prototypes, etc.
;============================================

$mainWinWidth EQU 600
$mainWinHeight EQU 500

;===== Window styles: =====
; Change 1st 2 styles to "WS_OVERLAPPEDWINDOW" if you want a resizeable window:
$mainWinStyles EQU WS_CAPTION or WS_SYSMENU or WS_CLIPCHILDREN or WS_VISIBLE

;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199

$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)

$SBID EQU 1111
$SBX EQU 0
$SBY EQU $mainWinHeight - $SBHeight
$SBWidth EQU $mainWinWidth
$SBHeight EQU 40
$numStatusParts EQU 4

$SB_styles EQU WS_CHILD OR WS_VISIBLE

;============================================
; HERE BE DATA
;============================================
.data

WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm


StatusParts DD 50, 150, 250, -1

NullString DB 0

MainClassName DB "[class name here]", 0

MainTitleText DB "[window title here]", 0

;============================================
; UNINITIALIZED DATA
;============================================
.data?

MainWinHandle HWND ?

InstanceHandle HINSTANCE ?

StatusHandle HWND ?
SBheight DD ?

;============================================
; CODE LIVES HERE
;============================================
.code

start: INVOKE GetModuleHandle, NULL
MOV InstanceHandle, EAX

CALL WinMain
INVOKE ExitProcess, EAX

;====================================================================
; Mainline proc
;====================================================================

WinMain PROC
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT

; Create  brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX

; Register class for parent window:
MOV EAX, InstanceHandle
MOV WC.hInstance, EAX
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
; INVOKE LoadIcon, InstanceHandle, 500    ; icon ID
; MOV WC.hIcon, EAX
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC

INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX

; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, InstanceHandle, NULL
MOV MainWinHandle, EAX

; Create the status bar:
INVOKE CreateStatusWindow, $SB_styles, OFFSET NullString, MainWinHandle, $SBID
MOV StatusHandle, EAX
MOV EDX, EAX
; Get the height of the status bar:
INVOKE GetWindowRect, EDX, ADDR gpRect
MOV EAX, gpRect.bottom
SUB EAX, gpRect.top
MOV SBheight, EAX

; Divide status bar into parts:
INVOKE SendMessage, StatusHandle, SB_SETPARTS, $numStatusParts, OFFSET StatusParts

;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
TEST EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop

exit99: MOV EAX, msg.wParam
RET

WinMain ENDP

;====================================================================
; Main Window Proc
;====================================================================

MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL gpRect:RECT

MOV EAX, uMsg
; CMP EAX, WM_COMMAND
; JE do_command
CMP EAX, WM_CREATE
JE do_create
CMP EAX, WM_CLOSE
JE do_close
CMP EAX, WM_SIZE
JE dosize

dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET

;====================================
; WM_COMMAND handler
;====================================
do_command:
; MOV AX, WORD PTR wParam
; CMP AX, ---- ;Check command code here.
; JE ---

do_create:

; Window creation code here

XOR EAX, EAX
RET

;====================================
; WM_SIZE handler
;====================================
dosize:
; Get new size from message:
MOVZX EAX, WORD PTR lParam
MOVZX EDX, WORD PTR lParam + 2
SUB EDX, SBheight
INVOKE MoveWindow, StatusHandle, 0,  EDX, EAX, SBheight, TRUE

XOR EAX, EAX
RET

;====================================
; WM_CLOSE handler
;====================================
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET


MainWindowProc ENDP

;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================

CenterDim PROC

SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX

CenterDim ENDP

END start
Assembly language programming should be fun. That's why I do it.

tda0626

@sudoku Updated source file added to original post

@NoCforMe

Exactly. Once I get the basics of creating a window out of the way, I can just make a default window and pass parameters to it.