A Basic Win32 GUI Program--WINBASIC.ASM
This program produces a basic application window. You can move
the window around. You can change the window's size
and all the
usual buttons appear and are functional. The only things missing
are scroll bars and a menu bar.
[ More programming information ]
[ Back to Win32 ASM Page ]
For single-threaded programs
the initial values of the registers
are almost of no importance. But just in case you're interested:
CS:EIP = start of program; SS:ESP = start of stack; DS = ES = SS;
FS = TIB
the thread information block; GS = 0
selector. CS and DS map to the same linear address. The direction
[More on program startup.]
We start our GUI app in the same manner as the Console
; If using TLINK32
don't include vclib.inc
include vclib.inc ; Microsoft VC++ .lib link names
include win32hst.inc ; constants
and entry names
The window class
Every window is described by a run-time data structure known as a
window class. (This "class" is not a C++
class.) Every window class has a callback function
known as a window procedure
that defines most of
the behavior of all windows created with this class.
Before we can create a window
its window class
defined by a
must be given a name and registered
calling RegisterClassEx. Windows preregisters a few window
but these are usually used for creating controls.
let's look at the WNDCLASSEX structure defined in
WIN32HST.INC. For ease in translating to other assemblers
prefixed each field with what I call the structure's "signature".
These signatures are shown as comments in the .h header files for
C. The remainder of the field name
after the underscore
exactly the same as in the C header files.
wcx_cbSize dd ?
wcx_style dd ?
wcx_lpfnWndProc dd ?
wcx_cbClsExtra dd ?
wcx_cbWndExtra dd ?
wcx_hInstance dd ?
wcx_hIcon dd ?
wcx_hCursor dd ?
wcx_hbrBackground dd ?
wcx_lpszMenuName dd ?
wcx_lpszClassName dd ?
wcx_hIconSm dd ?
Wow! that's a lot of fields. Three fields are the minimum needed
to create a window: lpfnWndProc
and lpszClassName. We
will also define four other fields: style
hbrBackground. Unused fields must be zeroed out.
We use the names defined in the API docs
but we prefix the
field names with a class "signature" which is shown in comments
in the .h header files for C.
The lpfnWndProc field contains the address of the window procedure that defines how the
windows of this class will behave.
The hInstance field contains a handle for the
module that "owns" the window class. Win16 made a
distinction between an instance and a module handle
they are one and the same. These module instance handles
identify loaded modules (EXE and DLL files)
somewhat like file
handles identifying opened files. Like file handles
module/instance handles are unique only within a running program
The lpszClassName field contains the address of a
zero-terminated (C-style) string which names the window class.
Instead of defining the structure with WNDCLASSEX
expand it and comment on what is in the WNDCLASSEX structure.
wcx dd size WNDCLASSEX ; cbSize
dd CS_VREDRAW or CS_HREDRAW ; style
dd WndProc ; lpfnWndProc
0 ; cbClsExtra
dd 0 ; hInstance
dd 0 ; hIcon
dd 0 ; hCursor
dd COLOR_WINDOW+1 ; hbrBackground
dd 0 ; lpszMenuName
dd wndclsname ; lpszClassName
dd 0 ; hIconSm
wndclsname db 'winmain'
The field cbSize has the total size of the WNDCLASSEX structure.
A number of structures have a structure size as the first field.
This makes it easy to extend structures. Later releases of
Windows can use this field to determine which version of the
structure is being used
and act accordingly. It's important to
set this field when its structure is used to receive data.
CS_VREDRAW and CS_HREDRAW in the style field force the normal
behavior of redrawing the window whenever it changes size.
WndProc is the window procedure we will define later in the
The hbrBackground field specifies the default
coloring of the framed area
known as the client
area. The value (COLOR_WINDOW + 1) makes it the same as
the other window client areas.
The label "wndclsname" defines the location of the
zero-terminated window class name.
Now for the fields that must be filled in at runtime.
push large 0 ; NULL string pointer means
call GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
Calling GetModuleHandle with a NULL pointer gives us the instance
handle of the EXE file. This is the module that will "own" the
push large IDI_WINLOGO
push large 0 ; hInstance
0 = stock icon
push large IDC_ARROW
push large 0 ; hInstance
0 = stock cursor
The hIcon field gives us an "application" icon. And the hCursor
field gives us the cursor image that is used when the cursor is
the window we will create.
push offset wcx
And thus the window class is registered.
All versions of Windows use the structure to create their own
internal window class "object"
so after the window class is
we can discard our "window class structure" with no
After the window class name is registered
we can create the
window using CreateWindowEx.
Although there are a lot of individual arguments
basically providing five (and nulling out the others): 1) the
window class name
2) a window caption
3) the window location
4) the window size
and 5) some standard "style" options.
The window class name is qualified by the module instance
handle that owns it. The owner is established by RegisterClassEx
via the hInstance field in the WNDCLASSEX data structure.
In the following code
we use a different approach to passing
parameters. We can treat the argument list as a data structure.
Instead of pushing each argument individually
we create a
structure to hold all the arguments (in proper order)
push the entire structure onto the stack. We can modify any
portion before stacking the structure
or modify the stacked
structure. The following code chooses the latter approach.
cwargs dd 0 ; dwExStyle
dd wndclsname ; lpszClass
dd wnd_title ; lpszName
dd WS_VISIBLE or WS_OVERLAPPED or WS_SYSMENU or WS_THICKFRAME \
or WS_MINIMIZEBOX or WS_MAXIMIZEBOX ; style
dd 100 ; x
dd 100 ; y
dd 200 ; cx (width)
dd 200 ; cy (height)
dd 0 ; hwndParent
dd 0 ; hMenu
dd 0 ; hInstance
dd 0 ; lpCreateParams
wnd_title db 'Application'
48 ; allocate argument list
offset cwargs ; set block move source
esp ; set block move destination
12 ; number of arguments
eax ; set hInstance argument in stack
The message loop
After the window is created and displayed
we can then deal with
any message sent to our program. Our basic GUI program repeatedly
fetches messages from the message queue with GetMessage. We do
this with a message loop. This loop is sometimes
called the message pump.
If there are any messages originating from one of the various
forms of SendMessage
GetMessage will invoke the appropriate
window procedure directly. Otherwise the message is copied into a
local buffer (the lpMsg argument)
and control returns to
the caller of GetMessage. A zero (FALSE) value is returned if
GetMessage retrieves a WM_QUIT message.
In our program
the message loop quits if a WM_QUIT message
is retrieved. Otherwise it invokes the proper window procedure
msgbuf MSG <>
push large 0 ; uMsgFilterMax
push large 0 ; uMsgFilterMin
push large 0 ; hWnd (filter)
0 = all windows
push offset msgbuf ; lpMsg
call GetMessage ; returns FALSE if WM_QUIT
push offset msgbuf
Terminating the program
We exit the program with ExitProcess.
push large 0 ; (error) return code
The window procedure
Except for some initialization and some cleanup
a GUI program is
expected to do everything within the various window procedures.
The window procedure may be called by Windows itself (e.g.
a window is first created)
or it may be invoked via
DispatchMessage. The window procedure receives four DWORD
in this order: window handle (hWnd)
first message parameter (wParam)
parameter (lParam). The argument wParam gets its original name
where it was a WORD argument.
When a window is closed
the default action
is to destroy it. The window is removed from the
and a WM_DESTROY message is sent to the window procedure.
If we want to terminate our program upon closing a specific
then that window must respond to a message associated
with closing a window. The recommended message is WM_DESTROY. Our
window procedure does this for the one and only window
PostQuitMessage as a response. After the window procedure is
a WM_QUIT message will be picked up by GetMessage in our
All other messages get processed by DefWindowProc.
One of the advantages of going from 16-bit code to 32-bit code
is the extra addressing modes available. All eight primary 32-bit
registers can be used in complex addressing modes.
WndProc takes advantage of this by using ESP to access its
arguments. We use the decomposed form [ESP+4+4] to show that part
of the offset is for skipping the stacked EIP and the other part
is the offset to the second argument (the message ID).
cmp dword ptr [esp+4+4]
push large 0
Linking a standard application is similar to linking a Console App.
With the Microsoft linker
we specify a different
link winbasic kernel32.lib user32.lib /entry:start /subsystem:windows
With the Borland linker
we specify a standard Windows app with /aa
instead of /ap. The /V option tells the linker to set the
subsystem version to 4.0.
(Warning: The linker that came with TASM 4.0 does not support /V.)
tlink32 /Tpe /aa /c /V4.0 winbasic