We start our GUI app in the same manner as the Console App.
.386
model flat
; If using TLINK32, don't include vclib.inc
include vclib.inc ; Microsoft VC++ .lib link names
include win32hst.inc ; constants, structures, and entry names
public _start
.code
_start:
WNDCLASSEX STRUC 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 ? WNDCLASSEX ENDSWow! that's a lot of fields. Three fields are the minimum needed to create a window: lpfnWndProc, hInstance, and lpszClassName. We will also define four other fields: style, hIcon, hCursor, and hbrBackground. Unused fields must be zeroed out.
.data
align 4
wcx dd size WNDCLASSEX ; cbSize
dd CS_VREDRAW or CS_HREDRAW ; style
dd WndProc ; lpfnWndProc
dd 0,0 ; cbClsExtra, cbWndExtra
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',0
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. Now for the fields that must be filled in at runtime.
extrn GetModuleHandle:near
.code
push large 0 ; NULL string pointer means
call GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
mov [wcx.wcx_hInstance],eax
Calling GetModuleHandle with a NULL pointer gives us the instance
handle of the EXE file. This is the module that will "own" the
window class.
extrn LoadIcon:near,LoadCursor:near
.code
push large IDI_WINLOGO
push large 0 ; hInstance, 0 = stock icon
call LoadIcon
mov [wcx.wcx_hIcon],eax
push large IDC_ARROW
push large 0 ; hInstance, 0 = stock cursor
call LoadCursor
mov [wcx.wcx_hCursor],eax
The hIcon field gives us an "application" icon. And the hCursor
field gives us the cursor image that is used when the cursor is
"over" the window we will create.
extrn RegisterClassEx:near
.code
push offset wcx
call RegisterClassEx
And thus the window class is registered.
.data
align 4
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',0
extrn CreateWindowEx:near
.code
sub esp,48 ; allocate argument list
mov esi,offset cwargs ; set block move source
mov edi,esp ; set block move destination
mov ecx,12 ; number of arguments
rep movsd
mov eax,[wcx.wcx_hInstance]
mov [esp+40],eax ; set hInstance argument in stack
call CreateWindowEx
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:
extrn ExitProcess:near
.code
push large 0 ; (error) return code
call ExitProcess
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
.code
WndProc:
cmp dword ptr [esp+4+4],WM_DESTROY
jne DefWindowProc
on_destroy:
push large 0
call PostQuitMessage
xor eax,eax
ret 16
end _start
link winbasic kernel32.lib user32.lib /entry:start /subsystem:windowsWith 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,,,import32.lib