We will create a fully functional Windows program that displays a message box
saying "Win32 assembly is great!".
Windows prepares a wealth of resources for use by 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 stored in several dynamic-linked libraries (DLLs) such as
kernel32.dll, user32.dll and gdi32.dll, to name a few. Kernel32.dll contains
API functions that deal with memory and process management. User32.dll controls
the user interface aspects of your programs. Gdi32.dll is responsible for
graphics operation. Other than "the main three", there are other DLLs that your
program can make use of, provided you have enough information about the desired
API functions stored in them.
Windows programs dynamically link to these DLLs, i.e. the codes of API
functions are not included in the executable file. This is very different from
what's called static linking in which actual codes from software libraries are
included in the executable files. In order for programs to know where to find
the desired API functions at runtime, enough information must be embedded into
the executable file for it to be able to select the correct DLLs and correct
functions. That information is in import libraries. You must link your
programs with the correct import libraries or it will not be able to locate
the desired API functions.
There are 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.
I'll present the bare program skeleton below. We will fill it out later.
.386
.model flat, stdcall
.data
.code
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 the name msgbox.asm.
Assuming ml.exe is in your path, assemble msgbox.asm with:
ml /c /coff /Cp msgbox.asm
/c tells MASM to assemble the source file into an object file only. Do not
invoke Link.exe automatically.
/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:\masm\lib msgbox.obj
kernel32.lib
/SUBSYSTEM:WINDOWS informs Link.exe on which platform the executable is
intended to run
/LIBPATH:<path to import library> tells Link where the import libraries
are. In my PC, they're located in c:\masm\lib.
Now that 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 create one by declaring the function name followed
by the keyword "PROTO" and lists of data types of the parameters prefixed by
colons. MASM uses function prototypes to type checking which will prevent nasty
stack errors that may pass unnoticed otherwise.
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 ExitProcess, 0
INVOKE is really a kind of high-level call. It checks number and types of
parameters and pushes parameters on the stack according to the specified
calling convention (in this case, stdcall). By using INVOKE instead of a 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 a message box in our program. Its function declaration
is:
int WINAPI MessageBoxA(HWND hwnd, LPCSTR lpText, LPCSTR lpCaption, UINT
uType);
-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 , 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 "Our First Program",0
MsgBoxText db "Win32 Assembly is Great!",0
.const
.code
Main:
INVOKE MessageBoxA, NULL, ADDR MsgBoxText, ADDR MsgBoxCaption, MB_OK
INVOKE ExitProcess, NULL
end Main
Assemble it by:
ml /c /coff /Cp msgbox.asm
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib msgbox kernl32.lib
user32.lib
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 (ASCIIZ).
We define two constants in .const section. We use constants to improve the
clarity of the source code.
Look at the parameters of MessageBoxA. The first parameter is NULL. This
means that there's no window that owns this message box.
The operator "ADDR" is used to pass the address of the label to the function.
This operator is specific to MASM. No TASM-equivalent exists. It functions like
"OFFSET" operator but with some differences:
- It doesn't accept forward reference. If you want to use "ADDR foo",
you have to declare "foo" before using ADDR operator.
- It can be used with a local variable. A local variable is the
variable that is created on the stack. OFFSET operator cannot be
used in this situation because the assembler doesn't know the true
address of the local variable at assemble time.
|