Document last modified:
Windows programming generally involves developing a GUI, VB and other languages are well suited for GUI development by hiding much of the programming complexity using object oriented techniques. Assembly language is not often used for Windows GUI programming but is useful to examine the basic concepts in graphical operating system function calls.
The invoke and proc statements can replace the Call, pushing and removing parameters, and accessing parameters by referring to the eBp register explicitly type checking of parameters is performed. The local definition defines a dynamic variable that is accessible only within the procedure, allocation/deallocation code is generated by the Assembler. The addr operator is equivalent to the offset operator. Consider the C++ and corresponding prototype and call in Assembly:
int f(int x, int &y, char &z)
{
int i;
i = x+y;
return i;
}
|
f proc x : dword, y : ptr dword, z : ptr byte
local i : dword
mov eAx, x
mov eBx, y ; y : ptr
add eAx, [eBx]
mov i, eAx
ret
f endp
|
f proc near
push eBp
mov eBp, eSp
sub eSp, 4 ; local i
mov eAx, [eBp+4] ; x
mov eBx, [eBp+8] ; y : ptr
add eAx, [eBx]
mov [eBp-4], eAx
mov eSp, eBp
pop eBp
ret
f endp
|
c = f(4, a, b); |
invoke f, 4, offset a, offset b
mov c, eAx
|
push offset b
push offset a
push 4
call f
add eSp, 12
mov c, eAx
|
The following C++ and Assembler programs display a message box on the screen which you can download and run, about the simplest Windows program possible. The message box is produced when calling the MessageBox function with the parameters for text and style to display:
invoke MessageBox, 0, addr MsgBoxText, addr MsgCaption, MB_OK
Assembling/Executing - Though Visual Studio comes with Masm32 it does not seem to have the necessary include files.
#include "stdafx.h" |
.386 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data MsgCaption db "Message Box Caption",0 MsgBoxText db "Message Box Text",0 .code WinMain proc near invoke MessageBox, 0, \ addr MsgBoxText, \ addr MsgCaption, \ MB_OK invoke ExitProcess, 0 WinMain Endp end WinMain |
The minimal Windows program to print the familiar "Hello World" in both C++ and Assembler is more complex due to opening a standard window and handling some of the user events, download and run to observe program behavior. A cursory examination of either program gives the justified impression that Windows programs consist mainly of calls to Windows operating system functions.
The programs are nearly identical in implementation but the C++ executable is over ten times larger than the Assembler. The key functions in each are:
WinMain
WndProc
WinMain is invoked first, similar to the C++ main function. Its primary function is to initialize the process, open a window, and execute a receive and dispatch message iteration. The messages are sent to the operating system which in turn calls the WndProc function to handle the messages.
WndProc is the heart of a Windows program where the user interface is implemented by handling events such as mouse movements, key presses, etc. In our simple example only two events are handled, the WM_PAINT event to display the Hello World text, and the WM_DESTROY event to destroy or close the window process.
#include "stdafx.h"
char szAppName[]="Hello World Window"; // Window title
char szClassName[]="Window Class";
char szHelloWorld[]="Hello World";
int WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); // Allocate paint
TextOut(hdc,0,0,szHelloWorld,11);
EndPaint(hWnd, &ps); // Deallocate
break;
case WM_DESTROY:
PostQuitMessage(0); // Close
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow){
WNDCLASSEX wcex;
MSG msg;
HWND hWnd;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = szClassName;
wcex.hIcon = 0;
wcex.hIconSm = 0;
wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
RegisterClassEx(&wcex);
hWnd = CreateWindow(szClassName, szAppName,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
0, CW_USEDEFAULT, 0, NULL, NULL,
hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// Main message loop
while (GetMessage(&msg, NULL, 0, 0))
{ TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
|
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
AppName db "HelloWorld Window",0
ClassName db "SimpleWinClass",0
helloWorld db "Hello World"
.code
WndProc proc hWnd:HWND, uMsg:UINT, \
wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
@@switch:
cmp uMsg,WM_DESTROY
je @@destroy
cmp uMsg, WM_PAINT
je @@paint
jmp @@default
@@destroy: ; WM_DESTROY event
invoke PostQuitMessage,0 ; Quit
jmp @@endswitch
@@paint: ; WM_PAINT event
invoke BeginPaint,hWnd, ADDR ps ; Allocate paint
invoke TextOut,eAx,0,0,ADDR helloWorld, 11
invoke EndPaint,hWnd, ADDR ps ; Deallocate
jmp @@endswitch
@@default:
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
@@endswitch:
Mov eAx, 0
ret
WndProc endp
WinMain proc hInstance:HINSTANCE,\
hPrevInst:HINSTANCE,\
CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc ; Event Handler
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
push hInstance
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, 0
mov wc.lpszClassName, OFFSET ClassName
mov wc.hIcon, 0 ; Default icons
mov wc.hIconSm, 0
invoke LoadCursor, 0, IDC_HAND ; Use hand cursor
mov wc.hCursor, eAx
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, 0,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,\
hInstance,0
mov hwnd, eAx
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
@@while: ; Main message loop
invoke GetMessage, ADDR msg,0,0,0
cmp eAx, 0
Jne @@do
Jmp @@endwhile
@@do:
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
Jmp @@while
@@endwhile:
mov eax,msg.wParam
ret
WinMain endp
main proc near ; Windows Startup code
invoke GetModuleHandle, NULL
invoke WinMain, eAx, 0, 0, 0
invoke ExitProcess, 0
main endp
end main
|
The WndProc is invoked by the Windows operating system in response to events related to the window in which the event occurred. For example, pressing a key in a window sends a key pressed message to the WndProc for that window (remember that you may have several windows open at one time).
The following example adds handling of key press events and display of the characters to the window. Only the parts of the program changed from above are included, you'll notice that the WinMain does not change and the WndProc merely adds the code necessary buffering the key pressed in response to WM_CHAR message and displaying the characters buffered in response to the WM_PAINT message. Download and run to observe the program behavior when keys are pressed.
char buffer[1000];
int count=0;
int WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message) {
case WM_DESTROY:
PostQuitMessage(0); // Close
break;
case WM_CHAR:
buffer[count] = (char) wParam;
InvalidateRect(hWnd, 0, TRUE);
count++;
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); // Allocate paint
TextOut(hdc,0,0,buffer,count);
EndPaint(hWnd, &ps); // Deallocate
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
|
.data
buffer db 1000 dup(?)
count dd 0
.code
WndProc proc hWnd:HWND, uMsg:UINT, \
wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
@@switch:
cmp uMsg,WM_DESTROY
je @@destroy
cmp uMsg, WM_CHAR
je @@char
cmp uMsg, WM_PAINT
je @@paint
jmp @@default
@@destroy: ; WM_DESTROY event
invoke PostQuitMessage,0 ; Quit
jmp @@endswitch
@@char:
push wParam
pop eAx
mov eBx, count
mov buffer[eBx], Al ; Save key press
invoke InvalidateRect, hWnd, 0,TRUE
inc count
jmp @@endswitch
@@paint: ; WM_PAINT event
invoke BeginPaint,hWnd, ADDR ps ; Allocate paint
invoke TextOut,eAx,0,0,ADDR buffer, count
invoke EndPaint,hWnd, ADDR ps ; Deallocate
jmp @@endswitch
@@default:
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
@@endswitch:
Mov eAx, 0
ret
WndProc endp
|
The WndProc is invoked by the Windows operating system in response to events related to the window in which the event occurred. For example, moving the mouse in a window sends a mouse message to the WndProc for that window.
The following example adds handling of mouse click event to implement a sketch program. Only the parts of the program changed from above are included, you'll notice that again the WinMain does not change and the WndProc merely adds the code to store the <x,y> points locating the mouse movement across the screen and displaying lines through the array of <x,y> points in response to the WM_PAINT message. Download and run to observe the program behavior when the mouse is moved.
.data
ClassName db "SimpleSketchClass",0
AppName db "Sketch",0
points POINT 1000 dup (<0,0>)
npoints dd 0
.code
WndProc proc hWnd:HWND, uMsg:UINT, \
wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
@@switch: cmp uMsg,WM_DESTROY
je @@destroy
cmp uMsg, WM_MOUSEMOVE
je @@mousemove
cmp uMsg, WM_PAINT
je @@paint
jmp @@default
@@destroy: ; WM_DESTROY event
invoke PostQuitMessage,0
jmp @@endswitch
@@mousemove: ; WM_MOUSEMOVE event
mov eAx, size POINT ; Left mouse button
mul npoints
mov eSi, eAx
inc npoints
mov eAx,lParam ; Extract <x,y> mouse
and eAx,0ffffh ; location from lParam
mov points[eSi].x, eAx ; and save in points
mov eAx,lParam ; array
shr eAx,16
mov points[eSi].y, eAx
invoke InvalidateRect,hWnd,NULL,TRUE
jmp @@endswitch
@@paint: ; WM_PAINT event
invoke BeginPaint,hWnd, ADDR ps
invoke Polyline, eAx, addr points, npoints
invoke EndPaint,hWnd, ADDR ps
jmp @@endswitch
@@default:
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
@@endswitch:
Mov eAx, 0
ret
WndProc endp
|
The standard library for the C language contains many useful functions for file handling, running other process, math, etc. These functions can be easily accessed by defining the PROTO for the C function and then INVOKE the function. The following example illustrates by copying a file to the screen.
If testing, the C program should be named file.C or some name with a C extension.
Copy the MSVCRT.LIB, located at:
The Assembly program below can be assembled with the MSVCRT.LIB in the same directory by:
The C program copies file.C while the Assembler program copies file.asm to the console screen. The commands necessary at IUS are:
int *fopen( const char *filename,
const char *mode );
int getc( int *stream );
int putchar( int c );
char filename[7] = "file.c";
char mode[2] = "r";
int main(void) {
int *fp = fopen(filename, mode);
int c;
while ((c=getc(fp)) != -1)
putchar(c);
return 0;
}
|
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
fopen proto near C,
filename:near ptr dword, mode:near ptr dword
getc proto near C, fileptr : dword
putchar proto near C, char : dword
.data
filename db "file.asm",0
mode db "r",0
.code
main proc near
local fp : dword
invoke fopen, addr filename, addr mode
mov fp, eAx
@@while:
invoke getc, fp
cmp Al, -1
jne @@do
jmp @@endwhile
@@do:
invoke putchar, eAx
jmp @@while
@@endwhile:
invoke ExitProcess, 0
main endp
end main
|