Thursday, March 28, 2024 | Toby Opferman
 

WIN32 Programming for TASM Part 2

Toby Opferman
http://www.opferman.net
programming@opferman.net



                        Win32 Assembly Intermediate Tutorial
                                    (TASM v5.0)
                      
                     "How to make a Win32 Program in Assembly"

        This program is for those who know assembly, know Win32 API and know
how to declare and call functions and define variable types & structures for
Win32.  A good idea is to read my Intro to Win32 Assembly Tutor before reading
this tutor.


        In this tutor, I will tell you how to set up your assembly program and
even help you make a generic template so that it's very simple to create your
Win32 Assembly programs with absurd ease!


First thing you need it the processor.  386, 486 or better is a must.


.486 

or

.486p



I go with 486, but you can do what you want.  The processor you define only limits you
in what instructions you can use and there aren't that many new instructions on the 486,
or even that many on a pentium.  But, you can still define those processors but it 
doesn't really matter too much. The 386 instruction set defines the most instructions
for pmode operations, the 486 and 586 just picked up a few tricks (And CPU cycles!).


Next, you want to define your model.


.MODEL FLAT, STDCALL

You want to define FLAT mode!  That is just wonderful because FLAT mode is Soooooo easy
to use. Also define Standard Calls, that is just how windows will react with your functions,
there's nothing to think about there, it really isn't nesscary to know why you declare it
with STDCALL.  I'm not so sure myself why you don't use PASCAL like in Win16 assembly, I haven't
gone that far in depth but it doesn't really matter, you can get by!



Now, you can include your WIN32.INC file!

INCLUDE WIN32.INC


That's it. That will include all your functions and structures.  I will talk a little more
about the defitions later, as since you define at lot but don't use them all, there's a little
note about that later in this tutor.

Then, just define your code and data.




Here is what your file should look like so far:


.486p
.MODEL FLAT, STDCALL
INCLUDE WIN32.INC


.DATA
 
  ;  Define your variablesHere

.CODE
 START:   ; This is where your code exeuction begins.
          ; You may also call this WinMain if you want.


    ; Put Code Here


END START ; This goes at the very end of the Text File, exactly like a DOS program.


That is a shell.  We will fill it in now and we should come up with a template.


For Data, all we need are:

 TitleName   db  'First Windows Program', 0
 ClassName   db  'FirstWindow', 0
 Msg  MSGSTRUCT <?>   ; The Message Struct
 WC   WNDCLASS  <?>   ; The Windows Class
 hwnd       dd ?      ; Handle to Window
 hInstance  dd ?      ; Handle to Instance



We begin our code with a call to GetModuleHandle.  It returns a Module, which is the
same thing as a HINSTANCE.


PUSH L 0
CALL GetModuleHandle
MOV [hInstance], EAX    ; Get The Instance


Now, we have our hInstance, and now we can register the class.


CALL RegClass           ; Register The Class


We can make this a seperate procedure just to make our WinMain look cleaner.




With this procedure, you can populate the Windows Class To What you want.

RegClass PROC
  MOV EAX, [hInstance]
  MOV [WC.clsHInstance], EAX              ; Populate the Windows Class Structure  
  MOV [WC.clsStyle], 0
  MOV [WC.clsLpfnWndProc], OFFSET WndProc ; Your Windows CallBack Function (Get Into This Later)
  MOV [WC.clsCbClsExtra], 0
  MOV [WC.clsCbWndExtra], 0

  PUSH L IDI_APPLICATION                  ; Load The Icon
  PUSH EAX
  CALL LoadIcon

  MOV [WC.clsHIcon], EAX
  MOV [WC.hIconSm], EAX

  PUSH L IDC_ARROW                        ; Load Cursor
  PUSH L 0 
  CALL LoadCursor

  MOV [WC.clsHCursor], EAX

  PUSH L BLACK_BRUSH
  CALL GetStockObject                     ; Black Background

  MOV [WC.clsHbrBackground], EAX
  MOV [WC.clsLpszMenuName], 0 
  MOV [WC.clsLpszClassName], OFFSET ClassName
 

 
  PUSH OFFSET WC
  CALL RegisterClass                      ; Register the Window
   
  RET
ENDP




Then, we must Create The Window.


CALL CreateWind            ; Create The Window


We can make this also a seperate procedure just to clean up WinMain.


CreateWind PROC
  
  PUSH L 0
  PUSH [hInst]
  PUSH L 0
  PUSH L 0
  PUSH L CW_USEDEFAULT
  PUSH L CW_USEDEFAULT 
  PUSH L CW_USEDEFAULT
  PUSH L CW_USEDEFAULT
  PUSH L WS_OVERLAPPEDWINDOW
  PUSH OFFSET TitleName
  PUSH OFFSET ClassName
  PUSH L 0                 ; These are the parameters to CreateWindowEx, Just loaded
                           ; BackWards.
  
  CALL CreateWindowEx      ; Create The Window

  MOV [hwnd], EAX          ; The Create Window Returns the Handle to the window.
  RET
ENDP



Now, we call ShowWindow and UpDate Window


PUSH L SW_SHOW
PUSH [hwnd]
CALL ShowWindow


PUSH [hwnd]
CALL UpdateWindow


This will create our window and display it on the screen.


Last thing we need to do in the WinMain is the Windows Message Loop.

This will poll for it's messages, when it finds a message for it's window, it will
translate it into a code, then dispatch it to it's Window Message Handler function.

In Pseudo Code:


WAIT FOR MESSAGE:
  IF MESSAGE GET MESSAGE
  IF MESSAGE IS QUIT THEN EXIT
  TRANSLATE THE MESSAGE
  SEND MESSAGE TO WINDOW ROUTINE
  GO BACK TO WAIT FOR MESSAGE
EXIT

You have already seen the message loop in the beginner's tut, so I will just paste it here
with some explianations for it.


MessageLoop:
  PUSH L 0
  PUSH L 0
  PUSH L 0
  PUSH OFFSET Msg 
  CALL GetMessage       ; Call To GetMessage

  TEST EAX, EAX         ; Quit Loop?
  JZ SHORT EndProgram

  PUSH OFFSET Msg
  CALL TranslateMessage ; Translate Message

  PUSH OFFSET Msg
  CALL DispatchMessage  ; Dispatch Message

  JMP SHORT MessageLoop  

EndProgram:
  
  PUSH [Msg.wParam]
  CALL ExitProcess      ; End Program



The above will do exactly what the Pseudo code says.  Gets the windows message, translates it
and sends it out.  For most windows programs, the above will not change.  There is a variation
to the loop that is more advanced and there are sometimes things you want to do between
translateing message and dispatching or before.  But, those are not in general applications.
Many applications are just fine with the above and changing the above is not likely in the
interest of a novice windows programmer.  When the time comes that you may need to change
it for a speical case program, you would most likely be in expert level and already
know exactly what to do and how to do it.



So, this is what your program should look like so far:

.486p
.MODEL FLAT, STDCALL
INCLUDE WIN32.INC


.DATA
 
 Msg  MSGSTRUCT <?>   ; The Message Struct
 WC   WNDCLASS  <?>   ; The Windows Class
 hwnd       dd ?      ; Handle to Window
 hInstance  dd ?      ; Handle to Instance



.CODE
 START:   ; This is where your code exeuction begins.

  PUSH L 0  
  CALL GetModule
  MOV [hInstance], EAX    ; Get The Instance

  CALL RegClass           ; Register The Class
  CALL CreateWind         ; Create The Window

  PUSH L SW_SHOWNORMAL
  PUSH [hwnd]
  CALL ShowWindow         ; Show Window

  PUSH [hwnd]
  CALL UpdateWindow       ; Update The Window

  MessageLoop:
    PUSH L 0
    PUSH L 0
    PUSH L 0
    PUSH OFFSET Msg 
    CALL GetMessage       ; Call To GetMessage

    TEST EAX, EAX         ; Quit Loop?
    JZ SHORT EndProgram

    PUSH OFFSET Msg
    CALL TranslateMessage ; Translate Message

    PUSH OFFSET Msg
    CALL DispatchMessage        ; Dispatch Message

    JMP SHORT MessageLoop  

  EndProgram:
  
  PUSH [Msg.wParam]
  CALL ExitProcess      ; End Program

; END OF WinMain.

 ; Your Proc's Go Here (I left them out for space reasons, so you can view the top part in peace.


END START  ; End of File.


ExitProcess will terminate the program and it will not return.

Next, we will add error checking to the WinMain.

  CALL RegClass           ; Register The Class
  CALL CreateWind         ; Create The Window


These two calls can return an error if the window does not register.  All you have to do is
check EAX.  If EAX is 0, then call ExitProcess with a 0 in the parameter.

  CALL RegClass           ; Register The Class

  TEST EAX, EAX           ; Check Window Register Error
  JNZ  SHORT  NO_ERROR    

  PUSH L 0
  CALL ExitProcess        ; Exit

NO_ERROR:
  CALL CreateWind         ; Create The Window

  TEST EAX, EAX           ; Check Window Creation Error
  JNZ SHORT WINDOW_CREATED

  PUSH L 0
  CALL ExitProcess        ; Exit

WINDOW_CREATED:


Now, in C you may have seen:


 return FALSE; and not return 0;


Well, do this in your WIN32.INC


 FALSE equ <0>
 TRUE  equ <1>


Then, in your programs, just do

 PUSH L FALSE
or
 PUSH L TRUE

for those parameters.

Yes, defining 0 to mean a bunch of differnt things (i.e. NULL, FALSE, etc) can be
redundent, but it does not add any code to your program.  It will only add tokens to your
assembler and will not effect your assembled code. It just just more readable to see NULL's
and FALSE's.  Where you can tell a NULL you can replace with a pointer and a FALSE you can
replace with a value or TRUE.


Now, you basically have your template.  You can change the RegClass proc and CreateWind
proc to customize the window.  The rest of it can basically stay in place.  

** As a side note, you may see something like a call to FindWindow in the Tasm examples.
The logic there is just to make sure that if there is another instance of the program, it 
will put a space in it's own name to make it unique.  You can do that if you want, there is
no _MAJOR_ need to.  You may also change the code to instead of putting a space in, to exit
if it is already in memory, to allow only one of your programs to run at a time.  Force them
not to be able to have multiple sessions of your program loaded.  Depending on the type of
program you have, this may be a good idea.  For the most part, you can really ignore that 
though.  Your general applications probably don't matter, especially for a beginner. The programs
you write probably won't be as complex as to want to force 1 instance of itself for device
sharing reasons, or memory reasons.



Now, we will define our windows call back function.  This is the function that windows calls
when it wants to tell our window to do something, or that the user wants to do something to
the window.  An event has occured pertaining to that window.



WndProc PROC USES EBX EDI ESI, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD

 ; Code Here

ENDP



Once you define the above, you are ready to insert your code.
You would want to make sure any WM_messages are defined in your WIN32.INC


For intermediate level, I would hope you can figure out the rest. It's just plain old windows
code, if you've programmed windows in C, you should have no problem.  Here is a quick look:



 MOV EAX, [Msg]

 CMP EAX, WM_CREATE     ; Check Messages  
 JE W_CREATE

 PUSH [lParam]
 PUSH [wParam]
 PUSH [Msg]
 PUSH [hwnd]
 CALL DefWindowProc     ; Default
 RET

W_CREATE:               ; WM_CREATE Message

   ; Code

XOR EAX, EAX
RET


Now you should be ready to move onto advanced Win32 Assembly Tutor.

You should know how to:

1. Create a WinMain with all your window defines
2. Create the outline of a Call back Function.
3. Have your own template for Win32.



In Advanced Win32 we will go into detail on WndProc.  We will also learn how to compile
this program!  And we will also cover resources breifly and how to include them
in your programs!


 
About Toby Opferman

Professional software engineer with over 15 years...

Learn more »
Codeproject Articles

Programming related articles...

Articles »
Resume

Resume »
Contact

Email: codeproject(at)opferman(dot)com