Download the example file here.
.386.model
flat, stdcall
.data
.code
start:
end start
The execution starts from the first instruction immediately below the label specified after end directive. In the above skeleton, the execution will start at the first instruction immediately below start label. The execution will proceed instruction by instruction until some flow-control instructions such as jmp, jne, je, ret etc is found. Those instructions redirect the flow of execution to some other instructions. When the program needs to exit to Windows, it should call an API function, ExitProcess.
ExitProcess proto uExitCode:DWORD
The above line is called a function prototype. A function prototype defines the attributes of a function to the assembler/linker so it can do type-checking for you. The format of a function prototype is like this:
FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
In short, the name of the function followed by the keyword PROTO and then by the list of data types of the parameters,separated by commas. In the ExitProcess example above, it defines ExitProcess as a function which takes only one parameter of type DWORD. Functions prototypes are very useful when you use the high-level call syntax, invoke. You can think of invoke as a simple call with type-checking. For example, if you do:
call ExitProcess
without pushing a dword onto the stack, the assembler/linker will not be able to catch that error for you. You'll notice it later when your program crashes. But if you use:
invoke ExitProcess
The linker will inform you that you forgot to push a dword on the stack thus avoiding error. I recommend you use invoke instead of simple call. The syntax of invoke is as follows:
INVOKE expression [,arguments]
expression can be the name of a function or it can be a function pointer. The function parameters are separated by commas.
Most of function
prototypes for API functions are kept in include files. If you use hutch's
MASM32, they will be in MASM32/include folder. The include files have .inc
extension and the function prototypes for functions in a DLL is stored
in .inc file with the same name as the DLL. For example, ExitProcess is
exported by kernel32.lib so the function prototype for ExitProcess is stored
in kernel32.inc.
You can also
create function prototypes for your own functions.
Throughout my
examples, I'll use hutch's windows.inc which you can download from http://win32asm.cjb.net
Now back to ExitProcess, uExitCode parameter is the value you want the program to return to Windows after the program terminates. You can call ExitProcess like this:
invoke ExitProcess, 0
Put that line immediately below start label, you will get a win32 program which immediately exits to Windows, but it's a valid program nonetheless.
.386
.model flat,
stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib
\masm32\lib\kernel32.lib
.data
.code
invoke ExitProcess,0
start:
end start
option casemap:none tells MASM to make labels case-sensitive so ExitProcess
and exitprocess are different. Note
a new directive, include.
This directive is followed by the name of a file you want to insert at
the place the directive is. In the above example, when MASM processes the
line include \masm32\include\windows.inc,
it will open windows.inc which is in \MASM32\include folder and process
the content of windows.inc as if you paste the content of windows.inc there.
hutch's windows.inc contains definitions of constants and structures you
need in win32 programming. It doesn't contain any function prototype. windows.inc
is by no means comprehensive. hutch and I try to put as many constants
and structures into it as possible but there are still many left to be
included. It'll be constantly updated. Check out hutch's and my homepage
for updates.
From windows.inc,
your program got constant and structure definitions. Now for function prototypes,
you need to include other include files. You can generate those include
files which contain only function prototypes from import libraries. I'll
outline the steps to generate those include files below:
l2inca /M
*.lib
l2inca.exe will extract information from the import libraries and create include files full of function prototypes.
Now save the example under the name msgbox.asm. Assuming that ml.exe is in your path, assemble msgbox.asm with:
Then go on with link:
/SUBSYSTEM:WINDOWS informs Link what sort of executable this program isLink reads in the object file and fixes it with addresses from the import libraries. When the process is finished you get msgbox.exe.
/LIBPATH:<path to import library> tells Link where the import libraries are. If you use MASM32, they will be in MASM32\lib folder.
Now you get msgbox.exe. Go on, run it. You'll find that it does nothing. Well, we haven't put anything interesting into it yet. But it's a Windows program nonetheless. And look at its size! In my PC, it is 1,536 bytes.
Next we're going to put in a message box. Its function prototype is:
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd is the handle to parent window. You can think of a handle as a number that represents the window you're referrring to. Its value is not important to you. You only remember that it represents the window. When you want to do anything with the window, you must refer to it by its handle.Let's modify msgbox.asm to include the message box.
lpText is a pointer to the text you want to display in the client area of the message box. A pointer is really an address of something. A pointer to text string==The address of that string.
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
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption
db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0
.code
start:
invoke MessageBox,
NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,
NULL
end start
Assemble and run it. You will 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 every ANSI string
in Windows must be terminated by NULL (0 hexadecimal).
We use two constants,
NULL and MB_OK. Those constants are documented in windows.inc. So you can
refer to them by name instead of the values. This improves readability
of your source code.
The addr
operator is used to pass the address of a label to the function.
It's valid only in the context of invoke directive.
You can't use it to assign the address of a label to a register/variable,
for example. You can use offset
instead of addr in the above example. However, there are some differences
between the two:
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OKMASM will report error. If you use offset instead of addr in the above code snippet, MASM will assemble it happily.
......
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
lea eax, LocalVar
push eax
Since lea
can determine the address of a label at runtime, this works fine.