Ściągnij przykładowy plik tutaj.
.386
.model flat, stdcall
.data
.code
start:
end start
Wykonywanie programu rozpoczyna się od pierwszej instrukcji zaraz pod etykietą określoną po dyrektywie end. W powyższym szkielecie, wykonywanie programu rozpoczyna się zaraz pod etykietą start. Proces ten będzie podążać instrukcja po instrukcji, aż pojawią się jakieś instrukcje kontrolujące bieg procesu. Instrukcjami tymi mogą być jmp, jne, je, ret itd. Te instrukcje przekierowują bieg procesu to jakiś innych instrukcji. Kiedy program musi wyjść do Windows'a, powinien wywołać funkcję API - ExitProcess.
ExitProcess proto uExitCode:DWORD
Powyższa linia to tzw. prototyp funkcji. Prototyp funkcji definiuje atrybuty tej funkcji assembler'owi/linker'owi więc może on wykonać dla ciebie weryfikację typów zmiennych. Format prototypu funkcji wygląda następująco:
NazwaFunkcji PROTO [NazwaParametru]:TypDanych,[NazwaParametru]:TypDanych,...
Krótko, nazwa funkcji, następnie słowo PROTO, a potem lista typów danych i parametrów oddzielonych przecinakmi. W powyższym przykładzie ExitProcess jest zdefiniowane jako funkcja, która przybiera tylko jeden parametr typu DWORD (z ang. Double Word - podwójne słowo). Prototypy funkcji są bardzo przydatne kiedy używasz składnię wysokiego poziomu - invoke. Invoke to po prostu call ze sprawdzaniem typów zmiennych. Na przykład jeżeli użyjesz:
call ExitProcess
bez włożenia na stos parametru DWORD to assembler/linker nie będzie w stanie poinformować cię o błędzie. Zauważysz to później, gdy program się zwiesi :) Ale kiedy użyjesz:
invoke ExitProcess
Linker poinformuje cię, że zapomniełeś ułożyć na stosie parametr DWORD w celu uniknięcia błędu. Polecam ci używanie invoke zamiast prostego call. Składnia invoke jest następująca:
INVOKE wyrażenie [,argumenty]
Wyrażeniem może być nazwa funkcji lub wskaźnik funkcji. Parametry funkcji są oddzielone przecinkami.
Większość protoypów funkcji API jest zawartych w plikach include.
Jeżeli używasz MASM'a hutch'a będą one w folderze MASM/include/ .
Pliki include mają rozszerzenie .inc, a prtotypy funkcji zawartych w
poszczególnych DLL-ach są zawarte w pliku .inc o nazwie tej samej co DLL.
Na przykład ExitProcess jest ekspotowany przez kernel32.dll więc prototyp
ExitProcess jest zawarty w kernel32.inc.
Możesz też tworzyć prototypy dla twoich własnych funkcji.
W moim przykładzie użyję windows.inc hutch'a, które możesz ściągnąć z
http://win32asm.cjb.net
teraz z powrotem do ExitProcess, parametr uExitCode jest wartością, która powoduje, że program powróci do Windows po zakończeniu. Możesz wywołać ExitProcess w ten sposób:
invoke ExitProcess, 0
Umieść tą linijkę zaraz pod etykietą startową, a uzyskasz program pod Win32, który natychmiast się zamyka, ale jest on poprawny...
.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
Opcja casemap:none mówi MASM'owi, żeby "uważał na zmianę wielkości liter"
tzn. że ExitProcess i exitprocess to nie to samo.
Zwróć uwagę na nową dyrektywę, include.
Po tej dyrektywie występuje nazwa pliku którą chciałbyś wstawić w
miejsce tej dyrektywy. W powyższym przykładzie kiedy MASM zacznie
przetwarzać linię include \masm32\include\windows.inc,
otworzy plik windows.inc, który znajduje się w folderze \MASM32\include i przetworzy
zawartość windows.inc tak jakbyś ją tam (do swojego programu) wkleił.
Plik windows.inc hutch'a zawiera definicje stałych i struktur, które będą
ci potrzebne w programowaniu pod Win32. Nie zawiera on żadnego prototypu funkcji.
Hutch i ja próbujemy umieścić tam tyle definicji stałych i struktur ile się da, ale
jeszcze wiele zostało do wliczenia. Plik windows.inc jest cały czas update'owany.
Sprawdź strony hutch'a i moje dla nowszych wersji.
Program l2inca.exe
wyciągnie informacje z bibliotek importów i utworzy pliki include pełne
prototypów funkcji.
Teraz zapisz przykład jako msgbox.asm.
Przypuszczając, że ml.exe jest w twojej ścieżce, zassembluj msgbox.asm wyrażeniem:
Teraz możesz uruchomić linkera:
Teraz masz msgbox.exe. Nie bój się, uruchom go :) Dojdziesz do tego, że nic nie robi.
Hmmm... nie umieściliśmy jeszcze w nim nic ciekawego. Ale to wciąż program Win32.
A popatrz na jego rozmiar ! Na moim PC to 1,536 bajtów.
Teraz umieścimy tam okno z wiadomością. Jego prototyp funkcji to:
MessageBox
PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
.386
.data
.code
Zassembluj to
i uruchom. Powinieneś zobaczyć okienko z informacją "Win32 Assembly
is Great!".
Przyjżyjmy się jeszcze
kodowi żródłowemu.
Z windows.inc twój program ma definicje stałych i struktur. Dla prototypów funkcji
musicz wliczyć inne pliki include. Możesz wygenerować takie pliki, które
będą zawierały tylko prototypy funkcji z bibliotek importów. Aby
wygenerować takie pliki musisz postępować następująco:
W naszym przykładzie wywołujemy funkcję eksportowaną przez kernel32.dll,
więc musimy wliczyć prototypy funkcji zawartych w kernel32.dll.
Ten plik to kernel32.inc. Jeżeli otworzysz ten plik edytorem tekstu,
to zobaczysz że jest on pełny prototypów funkcji dla kernel32.dll.
Jeżeli nie wliczysz kernel32.inc, nadal możesz wywołać ExitProcess,
ale tylko prostą składnią call. Nie będziesz mógł użyć składni
invoke.
Wniosek jest następujący: aby móc użyc invoke, musisz wliczyć
prototyp użytej funkcji gdzieś w kodzie źródłowym. W powyższym
przykładzie, jeżeli nie wliczysz kernel32.inc, możesz zdefiniować
prototyp funkcji dla ExitProcess gdziekolwiek w kodzie, ale
wcześniej niż komenda invoke, a invoke będzie działać. Pliki
include są po to aby zaoszczędzić ci pracy z wpisywaniem
wszystkich prototypów funkcji samemu, więc używaj ich kiedy
tylko chcesz.
l2inca /M
*.lib
Teraz poznamy nową dyrektywę, includelib.
includelib nie robi tego samego co include.
To tylko sposób na poinformowanie assemblera jaką bibliotekę importów
twój program używa. Kiedy assembler zauważy dyrektywę includelib,
umieszcza komendę łączącą do pliku obiektu, więc linker będzie
wiedział z jakimi bibliotekami importów trzeba połączć twój program.
Nie musisz jednak używać includelib. Możesz okreslić biblioteki
importów w linii poleceń linkera, ale uwierzcie mi, jest to nudne, a
linia poleceń może pomieścić tylko 128 znaków.
ml /c
/coff /Cp msgbox.asm
Po zassemblowaniu msgbox.asm, dostaniesz msgbox.obj. msgbox.obj jest plikiem obiektu.
Plik obiektu jest tylko o krok od pliku wykonywalnego. Zawiera on instrukcje/dane
w formie binarnej. Plikowi temu brakuje tylko kilku poprawek adresów,
które to zrobi linker.
z ang. - Common Object File Format), który jest używany
pod Unix'em jako jego własny obiekt i format pliku wykonywanlego.link /SUBSYSTEM:WINDOWS
/LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS
informuje linker'a jakim typem programu wykonywalnego jest ten program
Linker czyta plik obiektu i poprawia go o adresy bibliotek importów.
Kiedy proces ten zostanie zakończony dostaniesz msgbox.exe.
/LIBPATH:<ścieżka
do biblioteki importów> mówi linker'owi, gdzie znajdują
się biblioteki importów. Jeżeli używasz MASM32, będą one w folderze MASM32\lib.hwnd
jest zaczepieniem do okna macierzystego.
Możesz myśleć o tym zaczepieniu jako o numerze, który reprezentuje
okno, do którego się odnosisz. Jego wartość nie jest dla ciebie ważna.
Tylko pamiętasz, że reprezentuje ono okno. Kiedy chcesz zrobić coś
z tym oknem, musisz się do niego odnieść poprzez punkt zaczepienia.
Zmodyfikujmy msgbox.asm,
aby dodać tam okienko z naszą informacją.
lpText
jest wskaźnikiem do tekstu, który chcesz wyświetlić w okienku.
Wskaźnik jest tak naprawdę adresem czegoś. Wskaźnik do tekstu=adres
linii z tym tekstem.
lpCaption
jst wskaźnikiem do nagłówka naszego okienka
uType
określa ikonę oraz typ i ilość przycisków na okienku
.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
MsgBoxCaption
db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0
start:
invoke MessageBox,
NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,
NULL
end start
W sekcji .data definiujemy dwa zakończone zerem linie. Pamiętaj, że każda
linia ANSI w Windows musi być zakończona przez NULL (heksdecymalnie 0).
Używamy dwóch stałych, NULL i MB_OK. Stałe te są udokumentowane w pliku
windows.inc. Więc możesz się odnosić do nich po nazwie, a nie po wartości.
Poprawia to czytalność twojego kodu.
Operator addr
jest użyty do podania adresu etykiedy do funkcji.
Jest on poprawny tylko przy wywołaniu invoke. Możesz go używać do
przyporządkowania adresu etykiety do rejestru/zmiennej, na przykład.
Możesz użyć offset
zamiast addr w powyższym przykładzie. Aczkolwiek jest kilka różnic pomiędzy
tymi dwoma dyrektywami:
invoke
MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
MASM poinformuje cię
o błędzie. Jeżeli użyjesz offset
zamiast addr
w powyższym wycinku kodu, MASM zassembluje to bez
problemu.
......
MsgBoxCaption
db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0lea
eax, LocalVar
push eax
Kiedy tylko lea może zdeterminować adres etykiety podczas pracy programu, to działa to dobrze.