Chapter 11
|
Modified: |
Download
- ConsoleHelloWorld - Console example
- HelloWorld - Message box project in Assembler
- HelloWorld2 - Graphic Windows project in Assembler
- HelloWorldCPP - C++ project
- HelloWorldCPPandAssembler - C++ and Assembler project
- TicTacToe - C++ project
- Windows include files and libraries
Contents
Overview
Windows programming generally involves developing a GUI, many high-level 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 in understanding the basic concepts in graphical operating system function calls.
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.
local definition defines a dynamic variable that is accessible only within the procedure, allocation/deallocation code is generated by the Assembler.
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
|
c = f(4, a, b); |
invoke f, 4, offset a, offset b
mov c, eAx
|
"Hello World" Windows Console Programming in C++
Windows provides a simple API for console (text) applications requiring
reading and writing.
The following is from Microsoft's documentation of the WriteConsole
function.
BOOL WINAPI WriteConsole(
__in HANDLE hConsoleOutput,
__in const VOID *lpBuffer,
__in DWORD nNumberOfCharsToWrite,
__out LPDWORD lpNumberOfCharsWritten,
__reserved LPVOID lpReserved
);
Parameters
- hConsoleOutput [in]
- A handle to the console screen buffer. The handle must have the
GENERIC_WRITE access right. For more information, see
Console Buffer Security and Access Rights.
- lpBuffer [in]
- A pointer to a buffer that contains characters to be written to the
console screen buffer. The total size must be less than 64K.
- nNumberOfCharsToWrite [in]
- The number of TCHARs to write. If the total size of
the specified number of characters exceeds 64 KB, the function fails
with ERROR_NOT_ENOUGH_MEMORY.
- lpNumberOfCharsWritten [out]
- A pointer to a variable that receives the number of TCHARs
actually written.
- lpReserved
- Reserved; must be NULL.
Return Value
If the function succeeds, the return value is nonzero.
The following writes Hello World to the console and reads a string
of up to 80 characters and echoes to the console.
#include <windows.h>
int main() {
HANDLE stdOutHandle, stdInHandle;
char *helloWorld = "Hello world\n\r";
char buffer[82];
unsigned long bytesWritten=0, bytesRead=0;
stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(stdOutHandle, helloWorld, 13, &bytesWritten, NULL);
stdInHandle = GetStdHandle(STD_INPUT_HANDLE);
ReadConsole(stdInHandle, buffer, 80, &bytesRead, NULL);
WriteConsole(stdOutHandle, buffer, bytesRead, &bytesWritten, NULL);
return 0;
}
"Hello World" Windows Console Programming in Assembly
Windows provides a simple API for console (text) applications.
The following, similar to Console1.asm example of the text, prints
"Hello World" to the console.
There are two Windows calls, from Table 11-2 of the text. The function
prototypes are defined in SmallWin.inc file for:
- INVOKE GetStdHandle, STD_OUTPUT_HANDLE
returns a handle to
the standard output, the console.
- INVOKE WriteConsole,
consoleHandle,
ADDR message,
messageSize,
ADDR bytesWritten,
0
writes the message to the handle, standard output.
INCLUDE SmallWin.inc
.data
message BYTE "Hello World", 0dh,0ah
messageSize DWORD ($-message)
consoleHandle HANDLE 0 ; handle to standard output device
bytesWritten DWORD ? ; number of bytes written
.code
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov consoleHandle,eax
INVOKE WriteConsole,
consoleHandle, ; console output handle
ADDR message, ; string pointer
messageSize, ; string length, 13
ADDR bytesWritten, ; returns num bytes written
0 ; not used
INVOKE ExitProcess,0
main ENDP
END main
Read console and echo in Assembly
ConsoleHelloWorld
The following, combines reading and writing the console.
There are two Windows calls, from Table 11-2 of the text. The function
prototypes are defined in SmallWin.inc file for:
- INVOKE GetStdHandle, STD_INPUT_HANDLE
returns a handle to
the standard input, the console.
- INVOKE ReadConsole,
stdInHandle,
ADDR buffer,
78,
ADDR bytesRead,
0
reads the input to the handle, standard input.
INCLUDE SmallWin.inc
.data
buffer BYTE 80 DUP(?),0,0
stdInHandle HANDLE ?
stdOutHandle HANDLE 0
bytesRead DWORD ?
.code
main PROC
INVOKE GetStdHandle, STD_INPUT_HANDLE
mov stdInHandle,eax
INVOKE ReadConsole,
stdInHandle, ; console input handle
ADDR buffer, ; string buffer pointer
78, ; maximum + 2 for CR/LF
ADDR bytesRead, ; actual read
0
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov stdOutHandle,eax
INVOKE WriteConsole,
stdOutHandle,
ADDR buffer,
bytesRead,
ADDR bytesWritten,
0
INVOKE ExitProcess,0
main ENDP
END main
Minimal Windows Graphical Program using C++ and Assembler HelloWorld
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 and Executing
Though Visual Studio comes with Masm32 it does not have the necessary
include files for Windows programming in Assembler. Also, the text Irvine32.inc file is not consistent
with standard Window's definitions and is woefully incomplete.
To be compatible with Window's documentation, we will use include files
defined specifically for Windows programming.
- Download
Windows include files and libraries
- Make adjustments to include and includelib below
to correspond to where you installed the files.
#include "stdafx.h"
char MsgCaption[]="Message Box Caption";
char MsgBoxText[]="Message Box Text";
int __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MessageBox(0,
MsgBoxText,
MsgCaption,
MB_OK);
return 0;
}
.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 byte "Message Box Caption",0
MsgBoxText byte "Message Box Text",0
.code
WinMain proc near
invoke MessageBox, 0,
addr MsgBoxText,
addr MsgCaption,
MB_OK
invoke ExitProcess, 0
WinMain Endp
End WinMain
"Hello World" Windows Graphical Programming using C++ and Assembler HelloWorld2
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
- Called when program starts.
- Constructs and initializes the window.
- Iterates a message receive and dispatch loop until the window is
closed.
- Indirectly calls WndProc by dispatching messages to operating
system which calls WinProc when appropriate.
WndProc
Handles messages passed from operating system callback.
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 (messages) such as mouse movements, key presses,
etc. WinMain defines WinProc to be the function to handle
messages.
A Windows program executes a loop to get messages (mouse-clicks,
window refresh, etc.) and dispatch the messages to the Windows OS.
The OS schedules a call to the program's WinProc function,
which then handles any message it chooses. Any messages not handled by
WinProc are passed on to the default handler.
Commonly, the messages to be
handled are defined in a switch statement labeled by constants defined
within the <windows.h> file.
For example, WM_PAINT is the message label
for a paint message.

In our simple example only two events are handled:
- WM_PAINT event to display the Hello World text
- WM_DESTROY event to destroy or close the window process.
#include "stdafx.h"
char szAppName[]="Hello World 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:
// Alloc paint
hdc = BeginPaint(hWnd, &ps);
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 byte "HelloWorld Window",0
ClassName byte "SimpleWinClass",0
helloWorld byte "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
@@paint: ; WM_PAINT
invoke BeginPaint,hWnd, ADDR ps ; Alloc paint
invoke TextOut,eAx,0,0,ADDR helloWorld, 11
invoke EndPaint,hWnd, ADDR ps ; Deallocate
jmp @@endswitch
@@destroy: ; WM_DESTROY
invoke PostQuitMessage,0 ; Quit
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 ; 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
; Main message loop
@@while:
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
Handling more Windows events - Key Presses
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 byte 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 ; Close
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 ; Alloc 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
Handling even more Windows events - Mouse
Moves - Sketch
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.
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

Following C++ displays a TicTacToe board, accepts and displays O and X moves when a numbered button is pressed.
Play starts with X and alternates between the two players.
Logic is implemented to prevent cheating (e.g. changing an X to an O).
No other logic is implemented (e.g. game over, winner, etc.)
The underlying structure of a graphic Windows program remains the same. However, the need to send a message to the OS to update the user interface requires a relatively complex call, for example:
SendMessage((HWND) lParam, (UINT) WM_SETTEXT, (WPARAM) wParam,(LPARAM) "X");
Although we could translate into Assembler, we have reached the point of diminishing returns, its probably better to stick with C++ or some other higher-level language.
#include <windows.h>
#include <stdio.h>
#define BUTTON0 100
HINSTANCE app_hInst;
char szTitle[]="X turn"; // Window title text
char szWindowClass[]="Window Class";
char player = 'X';
int WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
HWND hwndButton;
switch (message) {
case WM_DESTROY:
PostQuitMessage(0); // Close window and process
break;
case WM_CREATE:
for(int button=0; button<9; button++) {
char buffer[2];
sprintf( buffer, "%d\0", button ); /* Create button caption */
CreateWindowEx(0, /* more or ''extended'' styles */
TEXT("BUTTON"), /* GUI ''class'' to create */
TEXT(buffer), /* GUI caption */
WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON, /* control styles separated by | */
50*(button%3), /* LEFT POSITION */
50*(button/3), /* TOP POSITION */
50, /* WIDTH OF CONTROL */
50, /* HEIGHT OF CONTROL */
hWnd, /* Parent window handle */
(HMENU)BUTTON0+button, /* control''s ID for WM_COMMAND */
app_hInst, /* application instance */
NULL);
}
break;
case WM_COMMAND:
if(((HWND)lParam) && (HIWORD(wParam) == BN_CLICKED))
if(player == 'X') {
SendMessage((HWND) lParam, (UINT) WM_SETTEXT, (WPARAM) wParam,(LPARAM) "X");
SendMessage((HWND) hWnd, (UINT) WM_SETTEXT, (WPARAM) wParam,(LPARAM) "O turn");
player = 'O';
}
else {
SendMessage((HWND) lParam, (UINT) WM_SETTEXT, (WPARAM) wParam,(LPARAM) "O");
SendMessage((HWND) hWnd, (UINT) WM_SETTEXT, (WPARAM) wParam,(LPARAM) "X turn");
player = 'X';
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASSEX wcex;
HWND hWnd;
app_hInst = hInstance;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = 0;
wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = 0;
RegisterClassEx(&wcex);
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 170, 190, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0)) // Main message loop:
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
|
The standard library for the C language contains many useful functions for file handling, running other process, math, etc.
These functions can be accessed by defining the PROTO for the C function and then INVOKE the function.
The following Command Prompt example illustrates using the library by copying a file to the screen.
The C program copies file.C while the Assembler program copies file.asm to the console screen. The commands necessary for Assembler are:
- Copy the Assembler program and save as file.asm.
- C:"\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"
- ml /coff /Zi file.asm /link MSVCRT.LIB /subsystem:console
- file
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 small, c
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 byte "file.asm",0
mode byte "r",0
.code
main proc public
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:
ret
main endp
end
|