News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Adding two numbers with MASM32 ?

Started by sunshine33, August 03, 2018, 08:01:30 AM

Previous topic - Next topic

deeR44

Quote from: NoCforMe on August 03, 2022, 04:30:55 PM
Your program worked for me. A mystery why Hutch can't seem to get it to run. Not a great program, but it does work.
It probably isn't a great program, but you didn't have to say so.

NoCforMe

We expect great things of you in the future.
Assembly language programming should be fun. That's why I do it.

hutch--

It was more a case of knowing how to use it. Once I worked that out, it runs fine and does the job.

deeR44


NoCforMe

Yes, the "royal we". Just a way of sending good wishes your way.
Assembly language programming should be fun. That's why I do it.

NoCforMe

OK, so moving on from your "first" program (congrats again, BTW), I have a challenge for you. Your mission, should you choose to accept it, is to take this GUI (graphical user interface) program and complete it so it adds two numbers. Almost all of the work has been done for you.

The program presents the user with a window that has 2 input fields (which are Windows edit controls) that they can use to enter the two numbers, and a button they can click to compute the sum of the numbers. Everything has been done for you except for calculating the sum and displaying it. Hopefully this will be a fun challenge.

The source code is well-commented to guide you. There's a lot of stuff here to absorb, so you might want to just focus on that small part of the code you need to add. (It should take less than a dozen lines of code to finish this program.) You'll need to look up one function on MSDN (link provided both in the source and below). You'll need to access MSDN anyhow if you plan on doing any Win32 programming, so you might as well get used to it.

If you get stumped don't hesitate to ask questions.

Here's the source (included in the .zip here):


;===================================================================
; -- DeeR44test.asm --
;
; A teachable moment or two ...
;
; A note on names here:
; This code uses my conventions for naming stuff:
;
; o Global variables (those defined in the .data or .data? sections)
;   are capitalized (e.g., "MainTitleText")
; o Local variables (those defined by LOCAL within subroutines)
;   are lowercase (e.g., "num1")
; o Symbolic constants (defined with EQU) start with "$"
;   (e.g., "$staticStyles")
;
; THIS IS BY NO MEANS A MASM REQUIREMENT! It's just the way I like to
; name things. I find it very useful to know what kind of item
; I'm dealing with by looking at its name.
; If you have a system that works better for you,
; by all means use it.
;===================================================================


; Use ".nolist" here to turn off listing:
; otherwise you'll get a ton of crap in
; the listing (.lst) file!

.nolist
include \masm32\include\masm32rt.inc
.list
; (be sure to turn the listing back on)


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

WinMain PROTO :DWORD

; This defines the dimensions of our main window:
$mainWinWidth EQU 600
$mainWinHeight EQU 200

;===== Window styles: =====
; Change 1st 2 styles to "WS_OVERLAPPEDWINDOW" if you want a resizeable window:
; This window will only have a "close" button and will not be resizeable.

$mainWinStyles EQU WS_CAPTION or WS_SYSMENU or WS_CLIPCHILDREN or WS_VISIBLE

; All these styles must include the "WS_CHILD" value since these controls (windows)
; are being created as children of the "parent" window (our main window):

$staticStyles EQU WS_CHILD or WS_VISIBLE

; The ES_NUMBER style makes sure that the user can only type numeric digits into the
; edit control. See how Windows makes life easier for us? No need to validate their input.

$editStyles EQU WS_CHILD or WS_VISIBLE or ES_LEFT or WS_BORDER or ES_NUMBER
$buttonStyles EQU WS_CHILD or WS_VISIBLE or BS_CENTER


; These are the locations and dimensions of our controls (static, edit & button)
; given as symbolic constants using EQU. EQU does *not* allocate storage or define
; a variable: it simply associates a symbolic name (like "$ST1X") with a numeric
; (or other) value. They make a programmer's job a whole lot easier! Instead of
; having to hunt through the code to change, say, the X-position of the button.
; you can easily find it here near the top of your source file.

; 1st static control:  "Enter one number:"
$ST1X EQU 40 ;X-position
$ST1Y EQU 40 ;Y-position
$ST1W EQU 130 ;width
$ST1H EQU 16 ;height
$ST1ID EQU 100 ;ID # (arbitrary but must be unique)

; 2nd static control: "Enter another number:"
$ST2X EQU 40
$ST2Y EQU 80
$ST2W EQU 150
$ST2H EQU 16
$ST2ID EQU 101

; 3rd static control: "Result:"
$ST3X EQU 300
$ST3Y EQU 80
$ST3W EQU 50
$ST3H EQU 16
$ST3ID EQU 102

; 4th static control (output field)
$ST4X EQU 360
$ST4Y EQU 80
$ST4W EQU 150
$ST4H EQU 16
$ST4ID EQU 1200

; 1st edit control:
$ED1X EQU 200
$ED1Y EQU 40
$ED1W EQU 80
$ED1H EQU 20
$ED1ID EQU 1201

; 2nd edit control:
$ED2X EQU 200
$ED2Y EQU 80
$ED2W EQU 80
$ED2H EQU 20
$ED2ID EQU 1202

; Button:
$Btn1X EQU 350
$Btn1Y EQU 40
$Btn1W EQU 120
$Btn1H EQU 24
$Btn1ID EQU 1203


;============================================
; HERE BE DATA
;
; Here's where data items actually get
; defined and allocated.
;============================================
.data

; This is used by the RegisterClass() function:
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


; Class names here. All Windows windows belong to a class. Here are
; their names. The first one (our window) can be named anything we like:

MainClassName DB "DeeR44", 0

; The following are predefined Windows class names for control windows:
StaticClassName DB "static", 0
EditClassName DB "edit", 0
ButtonClassName DB "button", 0

; Change the following item to whatever you want the window title to be:
MainTitleText DB "DeeR44 Testbed", 0

; Text for static controls. This is the text that will appear on screen:
Text1 DB "Enter one number:", 0
Text2 DB "Enter another number:", 0
Text3 DB "Result:", 0
ButtonText DB "Add the Numbers", 0


;============================================
; UNINITIALIZED DATA
;
; This is for data which is assigned no initial value.
; It saves space in the executable file: none of these
; items are actually in the .EXE file. They are created
; when the program is run and loaded into memory.
;============================================
.data?

; This is the "handle" to our main window. Handles are what Windows
; uses to keep track of windows (and other things, like pens,
; brushes, bitmaps, icons, etc., etc.).

MainWinHandle HWND ?

; These are the handles to our 2 edit controls so we can
; read their contents:
Edit1Handle HWND ?
Edit2Handle HWND ?

; Handle to the 3rd static text control,
; which is our output field:
Static3Handle HWND ?


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


start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX

INVOKE WinMain, EAX
INVOKE ExitProcess, EAX


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

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


; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
INVOKE GetStockObject, LTGRAY_BRUSH
MOV WC.hbrBackground, NULL
MOV WC.lpszClassName, OFFSET MainClassName

; If you wanted an icon to show @ upper-left corner of your window,
; you would load it here:

; INVOKE LoadIcon, hInst, 500    ; icon ID
; MOV WC.hIcon, EAX

MOV WC.hIcon, NULL ;No icon for us.
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC

; The following code centers the window on the desktop:
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim ;Call our function (see below).
MOV wY, EAX

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

; Create our control windows:
; First, the static (text) controls:
; 1st static text control ("Enter one number:"):
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET StaticClassName, ;class name
OFFSET Text1, ;window title/text
$staticStyles, ;styles
$ST1X, ;X-pos
$ST1Y, ;Y-pos
$ST1W, ;width
$ST1H, ;height
MainWinHandle, ;parent window
$ST1ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

; 2nd static text control ("Enter another number:":
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET StaticClassName, ;class name
OFFSET Text2, ;window title/text
$staticStyles, ;styles
$ST2X, ;X-pos
$ST2Y, ;Y-pos
$ST2W, ;width
$ST2H, ;height
MainWinHandle, ;parent window
$ST2ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

; 3rd static text control ("Result:):
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET StaticClassName, ;class name
OFFSET Text3, ;window title/text
$staticStyles, ;styles
$ST3X, ;X-pos
$ST3Y, ;Y-pos
$ST3W, ;width
$ST3H, ;height
MainWinHandle, ;parent window
$ST3ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

; 4th static text control (our output field):
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET StaticClassName, ;class name
NULL, ;window text (NULL since we'll be setting the text here)
$staticStyles, ;styles
$ST4X, ;X-pos
$ST4Y, ;Y-pos
$ST4W, ;width
$ST4H, ;height
MainWinHandle, ;parent window
$ST4ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

MOV Static3Handle, EAX

; Now two edit controls. These are where the user types their input
; (the two numbers in this case):
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET EditClassName, ;class name
NULL, ;window title/text (nothing here)
$editStyles, ;styles
$ED1X, ;X-pos
$ED1Y, ;Y-pos
$ED1W, ;width
$ED1H, ;height
MainWinHandle, ;parent window
$ED1ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

; When CreateWindowEx() returns (assuming it succeeds), it gives us the
; handle to the newly-created window (all Win32 return values go into the
; EAX register). So we must store it to be able to use it later:
MOV Edit1Handle, EAX

INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET EditClassName, ;class name
NULL, ;window title/text (nothing here)
$editStyles, ;styles
$ED2X, ;X-pos
$ED2Y, ;Y-pos
$ED2W, ;width
$ED2H, ;height
MainWinHandle, ;parent window
$ED2ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

MOV Edit2Handle, EAX

; Last, create our button:
INVOKE CreateWindowEx,
0, ;EXstyles (nothing here)
OFFSET ButtonClassName, ;class name
OFFSET ButtonText, ;window title/text
$buttonStyles, ;styles
$Btn1X, ;X-pos
$Btn1Y, ;Y-pos
$Btn1W, ;width
$Btn1H, ;height
MainWinHandle, ;parent window
$Btn1ID, ;menu/ID
hInst, ;instance handle
NULL ;param (nothing here)

; We don't need to store this control's handle since we look for its messages
; by ID ("Btn1ID"). See the MainWindowProc() below for more info.


; This is the Windows "message loop". Since this type of program is "event-driven"
; and not linear like old-school programs, we continuously monitor the message
; stream (using GetMessage() ) to look for our messages. Anytime the user
; interacts with our program, say by clicking on one of our controls, we get
; notified through these messages. See MainWindowProc() below to see how we
; handle these messages.

;============= 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

; This code is reached when the program is about to close (because the user
; clicked on the "close" button on our window):

exit99: MOV EAX, msg.wParam
RET

; The program now closes, the window is destroyed, and life goes on ...

WinMain ENDP


;====================================================================
; Main Window Proc
;
; This is the "window proc" for our main window. Unlike the linear
; programming that you're used to, this is different, because
;
; WE NEVER CALL THIS CODE!
;
; Instead, Windows calls it. This is what's known as a "callback"
; function, meaning that the operating system (Windows) calls it for us.
; Specifically, it gets called anytime there's a Windows message in
; the message queue. We look at the messages that we're interested in
; and respond to them. The other ones we just ignore.
;
; The parameters to this type of function are always the same:
; o hWin is the handle to the window (MainWinHandle in this case)
; o uMsg is the Windows message
; o wParam and lParam are additional data items
; For instance, the WM_COMMAND message puts the ID of the
; control that generated the message in wParam
; (see the code for WM_COMMAND below at "do_command")\;
;
; Note that the parameters are always the same, but not their names: we can name them anything we
; want to, so long as they're the correct type (they're all DWORDs here) and in the correct order.
;====================================================================

MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

; LOCAL variables are variables that MASM creates on the stack, so we don't
; need to allocate space for them ourselves (using DB, DW, DD or whatever).
; Since they're LOCAL, that means that they can only be used within the
; "scope" of the subroutine they're in (MainWindowProc() here). They cannot
; be accessed outside of this procedure, unlike "global" variables (like
; any of the variables we define in our .data or .data? sections).

LOCAL num1:DWORD, num2:DWORD

; These are the messages that our program responds to. There are literally
; hundreds of possible Windows messages; these are the only ones we're
; interested in here:

MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_CLOSE
JE do_close

dodefault:
; This is where we go when one of those hundreds of other messages comes
; through our code. This goes to the default Windows handling code, which
; takes care of everything from drawing the borders of our window to
; "painting" it:

INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET

; This code is reached when we get a WM_COMMAND message, for instance when
; the user clicks on our "Add the Numbers" button:

do_command:
; The command code is a WORD in half of a DWORD, so we get only that part:
MOV AX, WORD PTR wParam
CMP AX, $Btn1ID ;Is this our button sending that message?
JNE dodefault ;  No, so just do the default thing.

; It's our button all right, so go add the numbers:

;***************************************************************
; Here's where you get to do some coding! What I'm giving you is
; reading both of the edit fields and putting their values into
; 2 local variables (num1 and num2). Your job:
;
; o Add these two numbers together
; o Display the result in the output field provided.
;
; For that last thing I'll give you a clue: Look up the Win32 function
; SetDlgItemInt(). This will display the number in a control (window)
; as text, so you don't need to convert from binary to ASCII.
; HINT: Pretend that this window is a dialog window (it really isn't).
; Use the ID of the control (the 4th static text control in this case)
; as the control ID to that function.
;
; HINT HINT: The SetDlgItemInt() function is very similar to GetDlgItemInt()
; (but not identical--be careful!).
;
; Look up this function on MSDN at
; https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setdlgitemint
;****************************************************

INVOKE GetDlgItemInt, hWin, $ED1ID, NULL, FALSE
MOV num1, EAX
INVOKE GetDlgItemInt, hWin, $ED2ID, NULL, FALSE
MOV num2, EAX

;****************************************************
; Insert your code here!
;****************************************************

; When we're done, we return 0 (EAX = 0) to indicate that we
; handled this message:

XOR EAX, EAX
RET


; This code closes the window and causes the program to exit:
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET


MainWindowProc ENDP


; This routine is used to center our window on the desktop window.
; It's not necessary, just some nice icing on the cake.

;====================================================================
; 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



It may look complicated--it's certainly more complex than the simple "console" program you wrote--but it is definitely within your abilities to code in this style. As pointed out in comments in the code, this style of programming is completely different from the "linear" style you're used to (like those good old MS-DOS programs of yore). This is because Windows GUI programs are "event driven", which means that instead of going straight through a line of execution, the program path ends in a "message loop". This loop constantly receives messages from the operating system--say when the user moves the mouse, clicks on a control, types at the keyboard, or any of a hundred other actions--and dispatches them to your code. Your program looks for a certain subset of these messages and responds to them. In this example, the one important message is one called WM_COMMAND, which is sent when the user clicks on the Add the Numbers button. This lets us branch to the code that does this task, reading the two numbers, adding them and displaying them. Does that make sense?

You can run the attached executable to see what it looks like. It just won't add the numbers and display them until you complete it. There's also a "makefile" attached, which is just a MS-DOS batch file to assemble and link the program. Should work on your system, assuming you have MASM32 installed.

Here's the link to the MSDN article on SetDlgItemInt() which you'll need to complete the program.

Couple comments on this program: It works, but it's uglier than crap. Once you finish making it work, we can talk about how to make its appearance nicer.

I call this type of Windows program the "poor man's dialog". What I've created here is what I call a "fake dialog", since it behaves like a Windows dialog but it's totally created manually. If you eventually get more into GUI programming like this you'll probably want to look into using what's called a "resource editor" to create actual Windows dialogs; this makes it much easier to arrange things on-screen, line things up, size them correctly, etc. I had to tweak this by changing numbers to get it to look even this good (which is still crappy). But at this point in your career, that's just icing on the cake ...

Have fun!
Assembly language programming should be fun. That's why I do it.

jj2007

Great work, NoCforMe :thumbsup:

(I wouldn't have the necessary patience to write such detailed code, so well commented - compliments!)

NoCforMe

Yes, and not to toot my own horn here, but comments are so important to good programming practice.

The way I look at it, comments are not even really for some outsider looking at the code as much as they're for me, the poor slob who wrote that mess of code and is looking at it 2-3 years later trying to figure out what the hell it does!
Assembly language programming should be fun. That's why I do it.

hutch--

 :nie:

Helping people is a good thing, inflicting your own "good programming practice" on others is not, especially when its 1990 style MS-DOS "good programming practice".

Can we have a little less pontification ?

deeR44

Quote from: NoCforMe on August 05, 2022, 08:15:47 AM
OK, so moving on from your "first" program (congrats again, BTW), I have a challenge for you. Your mission, should you choose to accept it, is to take this GUI (graphical user interface) program and complete it so it adds two numbers. Almost all of the work has been done for you.

A. I tried to download your 'zip' file and Windows Defender said "Deer44test.zip Couldn't download - Virus detected"

B. I have never done a Windows program with graphical "windows" in it. I played around with a window someone
    else did some time ago, but that's about it.

C. I looked at your windows program in reply #95 and noted that it has 530 lines in it. I don't think that I understood
    50 lines in it.

jj2007

Quote from: NoCforMe on August 05, 2022, 09:28:26 AMThe way I look at it, comments are not even really for some outsider looking at the code as much as they're for me, the poor slob who wrote that mess of code and is looking at it 2-3 years later trying to figure out what the hell it does!

I know the problem - today I was fighting like hell with some old code that I had to fix :biggrin:

Very good explanation of the message loop :thumbsup:
; Main Window Proc
;
; This is the "window proc" for our main window. Unlike the linear
; programming that you're used to, this is different, because
;
; WE NEVER CALL THIS CODE!
;
; Instead, Windows calls it. This is what's known as a "callback"
; function, meaning that the operating system (Windows) calls it for us.
; Specifically, it gets called anytime there's a Windows message in
; the message queue. We look at the messages that we're interested in
; and respond to them. The other ones we just ignore.

NoCforMe

Quote from: deeR44 on August 05, 2022, 10:35:05 AM
Quote from: NoCforMe on August 05, 2022, 08:15:47 AM
OK, so moving on from your "first" program (congrats again, BTW), I have a challenge for you. Your mission, should you choose to accept it, is to take this GUI (graphical user interface) program and complete it so it adds two numbers. Almost all of the work has been done for you.

A. I tried to download your 'zip' file and Windows Defender said "Deer44test.zip Couldn't download - Virus detected"

B. I have never done a Windows program with graphical "windows" in it. I played around with a window someone
    else did some time ago, but that's about it.

Sorry 'bout that (though not my fault; it's the goddamn AV programs, which give us false positives on the code we write here all the time. Well-known problem, apparently with no good solution. I have similar problems with AVG.)

Fortunately you don't need the .zip archive. Copy that source to an .asm file. Then copy the source below to a batch (.bat) file which will assemble and link the program. Then you can go to town programming your first GUI app, if you're so inclined. Run the .exe as-is to see what you have.

This only requires a few lines of code on your part, so not as painful as it might look.

@echo off

\masm32\bin\ml /c /coff /Fl /Sg /Sn DeeR44test.asm
if errorlevel 1 goto errasm

\masm32\bin\link /SUBSYSTEM:WINDOWS DeeR44test.obj
if errorlevel 1 goto errlink
goto TheEnd

:errlink
echo _
echo Link error
goto TheEnd

:errasm
echo _
echo Assembly Error
goto TheEnd

:TheEnd

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

deeR44


So what's " the goddamn AV programs,", I wonder?

I'm afraid to copy the .asm file after the scary message I got. Besides, I know less that nothing about doing
that sort of thing (with windows).

NoCforMe

Don't worry, assembling that source won't hurt anything.

Antivirus programs (like Windows Defender in your case, AVG in mine) can be hypersensitive and interpret some of the constructs found in executables made with MASM32 as malicious viruses when they're not. Assembling and running that program won't hurt your computer, I promise. (Windows 7 here, I run stuff like this all the time, no harm whatsoever.) My code, which anyone can see for themselves, contains nothing but valid, documented Win32 functions, no undocumented calls or other sneaky stuff.

You could turn your antivirus off, but I wouldn't advise that.

Just out of curiosity, what flavor of Windows are you running?
Assembly language programming should be fun. That's why I do it.

deeR44