Tutorial 2: MessageBox
In this tutorial, we will create a fully functional
Windows program that displays a message box saying "Win32
assembly is great!".
Preliminary:
Windows prepare a wealth of resource for Windows
programs. Central to this is the Windows API
(Application Programming Interface). Windows API is a huge collection of
very useful functions that resides in Windows itself, ready to be used
by any Windows programs. These functions are in dynamic-linked libraries
(DLLs) such as kernel32.dll, user32.dll and gdi32.dll. Kernel32.dll contains
API functions that deals with memory and process management. User32.dll
controls the user interface aspects of your program. Gdi32.dll is responsible
for graphics operation. Other than "the main three", there're other DLLs
that your program can use, provided you have enough information about the
desired API function.
Windows programs dynamically link to these DLLs,
ie. the codes of API functions are not included in the Windows program
executable file. In order for your program to know where to find the desired
API functions at runtime, you have to embed that information into the executable
file. The information is in import libraries. You must link your program
with the correct import libraries or it will not be able to locate API
functions.
There's two types of API functions: One for ANSI
and the other for Unicode. The name of API functions for ANSI are postfixed
with "A", eg. MessageBoxA. Those for Unicode are postfixed with "W" (for
Wide Char, I think). Windows 95 natively supports ANSI and Windows NT Unicode.
But most of the time, you will use an include
file which can determine and select the appropriate API functions for your
platform. Just refer to the API function name without the postfix.
Content:
I'll present the bare program skeleton below. We
will fill it out later.
.386
.model flat, stdcall
.data
.code
Main:
end Main
Every Windows program must call an API function,
ExitProcess,
when it wants to quit to Windows.
In this respect, ExitProcess
is equivalent to int 21h, ah=4Ch in DOS.
Here's the function prototype of ExitProcess
from winbase.h:
void WINAPI ExitProcess(UINT
uExitCode);
void means the function
does not return any value to the caller.
WINAPI is an alias
of STDCALL calling convention.
UINT is a data type,
"unsigned integer", which is a 32-bit value under Win32 (it's a 16-bit
value under Win16)
uExitCode is the
32-bit return code to Windows. This value is not used by Windows as of
now.
In order to call ExitProcess
from an assembly program, you must first declare the function prototype
for ExitProcess.
.386
.model flat, stdcall
ExitProcess
PROTO ,:DWORD
.data
.code
Main:
INVOKE
ExitProcess, 0
end Main
That's it. Your first working Win32 program. Save
it under msgbox.asm.
Assuming ml.exe is in your path, assemble msgbox.asm
with:
ml /c /coff /Cp msgbox.asm
/c tells MASM to assemble
only. Do not invoke Link.
/coff tells MASM
to create .obj file in COFF format
/Cp tells MASM to
preserve case of user identifiers
Then go on with link:
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm611\lib
msgbox.obj kernel32.lib
/SUBSYSTEM:WINDOWS
informs Link what sort of executable this program is
/LIBPATH:<path to import library>
tells Link where the import libraries are. In my PC, they're located in
c:\masm611\lib
Now you get msgbox.exe. Go on, run it. You'll
find that it does nothing. Well, we haven't put anything interesting in
it yet. But it's a Windows program nonetheless. And look at its size! In
my PC, it is 1,536 bytes.
The line:
ExitProcess
PROTO ,:DWORD
is a function prototype. You declare the function
name followed by the keyword "PROTO", a comma, and lists of data types
of the parameters. MASM uses function prototype to check the number and
type of parameters to function.
The best place for function prototypes is in
an include file. You can create an include file full of frequently used
function prototypes and data structures and include it at the beginning
of your asm source code.
You call the API function by using INVOKE keyword:
INVOKE is really a kind of specialized call. It checks
number and type of parameters and pushes parameters on the stack according
to the default calling convention (in this case, stdcall). By using INVOKE
instead of normal call, you can prevent stack errors from incorrect parameter
passing. Very useful. The syntax is:
INVOKE expression [,arguments]
where expression is a label or function name.
Next we're going to put in a message box. Its
function declaration is:
int WINAPI MessageBoxA(HWND hwnd,
LPCSTR lpText, LPCSTR lpCaption, UINT uType);
where hwnd is the handle to parent window
lpText is a pointer to the text you want to display
in the client area of the message box
lpCaption is a pointer to the caption of the
message box
uType specifies the icon and the number and type
of buttons on the message box
Under Win32 platform, HWND, LPCSTR, and UINT are
all 32 bits in size.
Let's modify msgbox.asm to include the message
box.
.386
.model flat, stdcall
ExitProcess PROTO
,:DWORD
MessageBoxA PROTO
,:DWORD, :DWORD, :DWORD, :DWORD
.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is
Great!",0
.const
NULL equ 0
MB_OK equ 0
.code
Main:
INVOKE
MessageBoxA, NULL, ADDR MsgBoxText, ADDR MsgBoxCaption, MB_OK
INVOKE
ExitProcess, NULL
end Main
Assemble and run it.(You have to include user32.lib
in your Link parameter, since link info of MessageBoxA is in user32.lib)
You'll see a message box displaying the text
"Win32 Assembly is Great!".
Let's look again at the source code.
We define two zero-terminated strings in .data
section. Remember that all strings in Windows must be terminated with zero.
We define two constants in .const section. We
use constants to improve clarity of the source code.
Look at the parameters of MessageBoxA. The first
parameter is NULL. This means that there's no window that *own* this message
box.
The operator "ADDR" is used to pass the address
of the label to the function.
|