A Minimal Win32 GUI Program--WINDOW01.ASM
This program displays a window using very little information. It
illustrates the minimal requirements for creating a custom
window.
This particular program is almost the smallest stoppable GUI
program with a custom window. To get it much smaller would
require using a predefined window class (message box, dialog, or
control).
You can move the window. If you select some other window, you
can click on the window to reactivate (reselect) our program. You
can reactivate it with Alt-Tab, as well. Use Alt-F4, when it is
selected, to terminate the program.
You can also create a shortcut and use it to start the
program with the window in minimized or maximized form. Double
click the caption/title bar to change the window to "normal"
size.
[Download source code.]
More programming information
Back to Win32 ASM Page
Program startup
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, the null
selector. CS and DS map to the same linear address. The direction
flag, DF, is cleared.
[More on program startup.]
To link with Borland's linker, the startup address is
specified in the END directive.
To link with Microsoft's linker, the startup address must be
PUBLIC and specified by the linker's /ENTRY:
switch. The linker will automatically prepend an underscore,
"_".
.386
model flat
public _start
; ...
.code
_start:
; ...
end _start
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 name. Associated with the window
class is a callback function known as a
window procedure. The callback defines the behavior
for all windows created with this class.
Before we can create a window, its window class must be
registered. Windows preregisters a few window classes, but these
are usually used for creating controls.
There are two ways to register a custom window class: the
old way by calling RegisterClass with a WNDCLASS structure, or
the new way by calling RegisterClassEx with a WNDCLASSEX
structure. The new way allows specifying the new Win95
small icon. The old way is required if you want to
be compatible with older (pre-4.0) NT systems.
WNDCLASSEX struc ; comments show the C/C++ name
wcxSize dd ? ; cbSize, size of WNDCLASSEX
wcxStyle dd ? ; style
wcxWndProc dd ? ; lpfnWndProc
wcxClsExtra dd ? ; cbClsExtra
wcxWndExtra dd ? ; cbWndExtra
wcxInstance dd ? ; hInstance
wcxIcon dd ? ; hIcon
wcxCursor dd ? ; hCursor
wcxBkgndBrush dd ? ; hbrBackground
wcxMenuName dd ? ; lpszMenuName
wcxClassName dd ? ; lpszClassName
wcxSmallIcon dd ? ; hIconSm
WNDCLASSEX ends
Wow! that's a lot of fields. Four fields are the minimum needed
to create a window: cbSize, lpfnWndProc, hInstance, and
lpszClassName. Unused fields must be zeroed out.
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, but in
Win32, 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
"instance", or process.
The lpszClassName field contains the address of a
zero-terminated (C-style) string which names the window class.
Although Win32, in general, allows strings with either ANSI or Unicode characters, Win95, in most cases,
handles only ANSI. So we encode the window class name in ANSI.
extrn GetModuleHandle:near,RegisterClassEx:near
.data
wc WNDCLASSEX <size WNDCLASSEX,0,WndProc,0,0, 0, 0,0,0, 0,wndclsname, 0>
wndclsname db 'window01',0
.code
push large 0 ; NULL string pointer means
call GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
mov [wc.wcxInstance],eax
push offset wc
call RegisterClassEx
[See linking.]
All versions of Windows use the structure to create their own
internal window class "object", so after the window class is
registered, we can discard our "window class structure" with no
ill effects.
Window creation
After the window class name is registered, we can create the
window using CreateWindowEx.
Although there are a lot of individual arguments, we're
basically providing four (and nulling out the others): 1) the
class name, 2) window location, 3) window size, and 4) an option
to start with a visible window. The last three can be nulled out
provided you're willing to perform the equivalent display
functions elsewhere.
The module instance handle must be an "owner" of the given
window class, so together they uniquely identify the window
class. This ownership is established by RegisterClassEx via the
hInstance field in the WNDCLASSEX data structure.
WS_VISIBLE equ 10000000h
extrn CreateWindowEx:near
.code
push large 0 ; lpParam
push [wc.wcxInstance] ; hInstance
push large 0 ; menu hmenu
push large 0 ; parent hwnd
push large 200 ; height
push large 200 ; width
push large 100 ; y
push large 100 ; x
push large WS_VISIBLE ; Style
push large 0 ; Window text (caption)
push offset wndclsname ; Class name
push large 0 ; extended style
call CreateWindowEx
[See linking.]
The message loop
After the window is created and displayed, we can then deal with
any message sent to our program. Our minimal 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 (one of the GetMessage parameters), 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
using DispatchMessage.
MSG struc ; comments show the C/C++ names
msgHwnd dd ? ; hwnd
msgMessage dd ? ; message
msgWparam dd ? ; wParam
msgLparam dd ? ; lParam
msgTime dd ? ; time, message posting time
msgPtX dd ? ; pt.x, cursor position
msgPtY dd ? ; pt.y, cursor position
MSG ends
extrn GetMessage:near,DispatchMessage:near
.data
msgbuf MSG <>
.code
msg_loop:
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
or eax,eax
jz end_loop
push offset msgbuf
call DispatchMessage
jmp msg_loop
end_loop:
[See linking.]
Terminating the program
We exit the program with ExitProcess.
extrn ExitProcess:near
.code
push large 0 ; (error) return code
call ExitProcess
[See linking.]
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., when
a window is first created), or it may be invoked via
DispatchMessage.
When a window is closed (e.g., with Alt-F4), the default
action, handled by DefWindowProc, is to destroy it. The window is
removed from the screen, and a WM_DESTROY message is sent to the
window procedure. If we want to terminate our program by closing
some specific window, 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, calling PostQuitMessage as a response. After the window
procedure is exited, a WM_QUIT message will be picked up by
GetMessage in our message loop.
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).
extrn DefWindowProc:near,PostQuitMessage:near
WM_DESTROY equ 2
.code
WndProc:
cmp dword ptr [esp+4+4],WM_DESTROY
jne DefWindowProc
start_destroy:
push large 0
call PostQuitMessage
xor eax,eax
ret 16
[See linking.]
Strings, ANSI and Unicode
Character data can be in either ANSI (8-bit) or Unicode (16-bit).
Consequently, most (not all!) Win32 functions which handle
strings, directly or indirectly, have two versions--one for ANSI
and one for Unicode. The functions are distinguished by appending
A or W (wide) to the function name.
Unicode is more efficient on NT, but Win95 has only a limited
Unicode capability. So, except for a few cases, Win95 programs
use ANSI characters and strings.
Most Windows strings follow the C standard of terminating
with a zero-valued (NUL) character.
Linking
If you're a DOS programmer, you might be wondering what INT's are
being used to call API routines such as ExitProcess. The answer
is none. The API functions being called are located in
DLL (dynamic link library) files. (How the program
switches between the "application" and OS "kernel" modes requires
knowledge of how the Intel 386 and later chips implement
protected mode and memory paging.)
The linker doesn't add the DLL code into the EXE file.
Instead, it produces references to DLL entry points in the EXE
files. This is done via associated import
libraries. You must tell the linker what import libraries are
needed. Unfortunately, there isn't a standard import library
format. Below are examples for using the Borland and Microsoft
libraries..
An important feature needed in your assembler is preservation
of letter case, because Win32 API names are
case-sensitive. On many assemblers this is an option which
is normally disabled.
Borland uses one library, import32.lib, for the core set of
Win32 functions. The import link names are the same as the entry
point names in the DLL.
CreateWindowEx equ <CreateWindowExA>
DefWindowProc equ <DefWindowProcA>
DispatchMessage equ <DispatchMessageA>
GetMessage equ <GetMessageA>
GetModuleHandle equ <GetModuleHandleA>
RegisterClassEx equ <RegisterClassExA>
Microsoft uses several libraries, one per DLL. The link names for
Win32 functions are "decorated" names. The rule is simple:
prepend an underscore (_) and append an at-sign
(@) and the number of argument bytes in decimal. So the
ANSI version of GetMessage, which has four DWORD arguments, is
linked as _GetMessageA@16 (_ +
GetMessage + A + @ + 16 [=
4*4]).
This decoration scheme is primarily for the convenience of
Microsoft compilers. The link name needn't have this relationship
to the entry point name, but this scheme is used by the Microsoft
Win32 libraries.
CreateWindowEx equ <_CreateWindowExA@48>
DefWindowProc equ <_DefWindowProcA@16>
DispatchMessage equ <_DispatchMessageA@4>
ExitProcess equ <_ExitProcess@4>
GetMessage equ <_GetMessageA@16>
GetModuleHandle equ <_GetModuleHandleA@4>
PostQuitMessage equ <_PostQuitMessage@4>
RegisterClassEx equ <_RegisterClassExA@4>