Tutorial 2: MessageBox
Toto je tutorial,
kde vytvoríme plne funkčný program pod Windows, ktorý zobrazí okno so správou (messagebox) s textom „Win32 assembler je najlepší!“.
Teória:
Windows nám poskytuje veľké množtvo zdrojov pre vytváranie programov. Základom je tzv. Windows
API (Application Programming Interface – aplikačné programové rozhranie). Windows API je množstvo užitočných funkcií, ktoré sú pripravené na použitie akýmikoľvek prorgamami pre Windows. Tieto funkcie sú uložené v tzv. dynamických knižniciach (DLL): kernel32.dll, user32.dll a gdi32.dll. Kernel32.dll
obsahuje API funkcie pre správu pamäte a riadenie procesov. User32.dll
obsahuje užívateľské rozhranie pre Vaše programy. Gdi32.dll je zodpovedná za grafické operácie. Okrem týchto hlavných troch knižníc DLL Windows obsahuje množstvo iných knižníc DLL, ktore môžete používať, samozrejme za predpokladu, že poznáte podrobný popis ich API funkcií.
Windowsovské programy používajú dynamické prepojenie na API funkcie v týchto knižniciach, teda tieto API funkcie nie sú obsiahnuté vo Vašom spustiteľnom súbore. Do spustiteľného súboru musíte vložiť len informácie, kde má program tieto API funkcie hľadať. Tieto informácie sú v importovaných knižniciach. Vy musíte prilinkovať k Vášmu programu správne knižnice, inak by program nevedel kde má hľadať Vami požadované API funkcie.
Keď sa Windowsovský program načíta do pamäte, Windows si prečíta informácie uložené v programe. V tých informáciách sú uvedené mená importovaných funkcií a mená knižníc DLL kde sú tie funkcie uložené. Keď Windows nájde takéto informácie, načíta potrebné knižnice DLL a upraví volacie adresy požadovaných API funkcií na správne.
Poznáme dve kategórie API funkcií: funkcie pre kódovanie ANSI a pre Unicode. Mená API funkcií pre ANSI používajú príponu (postfix) „A“, napr. MessageBoxA.
Pre Unicode majú príponu (postfix) „W“ (Wide Char). Windows
9x používa ANSI a Windows NT Unicode.
Obyčajne poznáme ANSI reťazce ukončené nulou. ANSI znak má veľkosť 1 bajt (byte). ANSI kód stačí na väčšinu európskych jazykov, avšak nestačí hlavne na orientálne jazyky, jazyky ďalekého východu, ktoré obsahujú niekoľko tisíc jedinečných znakov. Preto sa vytvorilo kódovanie UNICODE.
UNICODE znak má veľkosť 2 bajty, umožňuje použitie až 65536
jedinečných znakov v reťazcoch.
Väčšinou však butete vytvárať súbory, ktoré si vyberú vhodnú API funkciu pre Vašu platformu Windows. Teda budete používať mená API funkcií bez prípony (postfixu).
Príklad:
Tu je kostra programu. Neskôr ju samozrejme bližšie vysvetlím.
.386
.model flat, stdcall
.data
.code
start:
end start
Náš program teda bude vykonávať kód uvedený medzi návestiami start: a end start. Začneme tým, aby program vrátil riadenie Windows-u, aby sa korektne ukončil.
Teda mal by volať API funkciu,
ExitProcess.
ExitProcess
proto uExitCode:DWORD
Riadok nad je uvedený prototyp funkcie ExitProcess. Prototyp funkcie definuje vlastnosti funkcie pre zostavujúci program/linker a pre typovú kontrolu.
Formát prototypu funkcie je uvedený o riadok nižšie:
FunctionName
PROTO [ParameterName]:DataType,[ParameterName]:DataType,…
V skratke, meno funkcie nasledované slovom PROTO a zoznamom dátových typov oddelených čiarkami.Funkcia ExitProcess má len jeden parameter typu DWORD. Prototypy funkcií sú potrebné ak používate vysokoúrovňový syntax volania príkazom invoke. Ak použijete napríklad jednoduché volanie call s typovou kontrolou:
call ExitProcess
bez pushovania dwordu na haldu, zostavovací program/linker Vás neupozorní na chybu. Vy si to všimnete až vtedy, keď Váš program havaruje. Ale ak použijete:
invoke ExitProcess
Linker Vás pri preklade programu upozorní na chybu pushovania. Doporučujem Vám používať
invoke
miesto obyčajného volania call. Syntak použitia je nasledovný:
INVOKE
expression [,arguments]
expression (výraz) môže byť meno funkcie alebo tiež ukazovateľ na funkciu. Parametre funkcie sú oddelené čiarkami.
Väčšinou sú prototypy funkcií uložené v include súboroch (vložené súbory s príponou *.inc). Ak používate
MASM32, tak si porzite adresár MASM32/include. Vložené (include) súbory majú príponu .inc a prototypy funkcií sú uložené v súboroch s príponou .inc a menom totožným ako je meno knižnice DLL. Napríklad, ExitProcess je
exportovaná z kernel32.lib, tak prototyp funkcie pre ExitProcess je uložený v súbore kernel32.inc.
Vy si môžete vytvoriť prototypy funkcií pre Vaše vlastné funkcie.
V mojich príkladoch budem používať súbor "windows.inc". Nájdete ho na adrese http://win32asm.cjb.net
Teraz ideme späť k funkcii ExitProcess:
uExitCode parameter je hodnota, ktorú program vracia, ak skončí. Vy môžete teda funkciu ExitProcess volať nasledovne:
invoke ExitProcess,
0
Napíšte teda volanie funkcie ExitProcess medzi návestia start: a end start, vytvoríte tak program, ktorý hneď po spustení skončí. Nie je to veľmi užitočný progam, ale je to platný program pre Windows.
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke
ExitProcess,0
end
start
option casemap:none hovorí MASM, že program je tzv.case-sensitive (rozlišuje veľkosť písmen), teda ExitProcess a exitprocess sú rôzne reťazce. Všimnite si nový príkaz include.
Za týmto príkazom nasleduje meno include súboru. Teda napríklad, ak MASM príde na riadok s príkazom include
\masm32\include\windows.inc, otvorí súbor windows.inc z adresára \MASM32\include a spracuje obsah tohto súboru. Súbor windows.inc obsahuje definície konštánt a štruktúr potrebných pre programovanie Win32 programov. Neobsahuje prototypy všetkých funkcií. Súbor windows.inc nie je v žiadnom prípade úplný. Pokúsil som sa do tohto súboru zahrnúť čo najviac funkcií. Súbor je neustále aktualizovaný. Pozrite sa na moju stránku, či tam nie je nejaká aktualizácia..
Zo súboru windows.inc dostal Váš program definície konštánt a štruktúr. Prototypy funkcií dostane z iných .inc súborov . Všetky sú uložené v adresári \masm32\include.
V príklade uvedenom vyššie voláme funkcie z kernel32.dll, teda potrebujeme include súbor s prototypmi funkcií z kernel32.dll. Tento súbor je kernel32.inc. Ak ho otvoríte v textovom editore,
uvidíte veľké množstvo prototypov funkcií z knižnice kernel32.dll. Ak nepoužijete súbor kernel32.inc, môžete zavolať funkciu ExitProcess, ale len klasickým volaním call. Nemôžete však použiť príkaz volania funkcie invoke. Dôvod: na použitie volania funkcie pomocou invoke musíte niekde do Vášho kódu zahrnúť prototypy použitých funkcií. Ak v použitom príklade nepoužijete include súbor kernel32.inc, vy môžete definovať prototyp funkcie pre ExitProcess kdekoľvek v zdrojovom kóde, aj tak bude volanie pomocou invoke fungovať. Použitie include súborov Vám však veľmi uľahčí prácu s písaním prototypov funkcií.
Ďalej nasleduje ďalší nový príkaz, includelib.
includelib nie je to isté ako include.
Toto je spôsob ako povedať zostavovaciemu programu, aké knižnice Váš program používa. Keď zostavovací program narazí na príkaz includelib, dá linkeru príkaz, aby danú knižnicu prilinkoval do objektového súboru. Samozrejme, že príkaz includelib nemusíte používať. Môžete špecifikovať mená importovaných knižníc v príkazovom riadku, ale verte mi, je to nuda. A hlavne príkazový riadok môže obsahovať len 128 znakov.
Teraz uložte príklad pod menom msgbox.asm.
Predpokladáme, že k súboru ml.exe existuje cesta, preložte
msgbox.asm nasledovne:
- ml /c
-
/c
hovorí MASM, aby program len preložil. Aby nepoužil link.exe. Väčšinou nebudete chcieť používať link.exe automaticky, budete chcieť vykonať ešte iné príkazy pred použitím link.exe - /coff hovorí
MASM, aby vytvoril .obj súbor v COFF
formáte. MASM používa variáciu formátu COFF
(Common Object File Format), ktorý sa používa v Unixe ako formát spustiteľných súborov - /Cp
hovorí MASM, aby zachovával tvar užívateľských identifikátorov. Ak používate MASM32, a použijete „option casemap:none“
v hlavičke Vášho zdrojového kódu, tesne pod direktívou .model, má to ten istý efekt.
/coff /Cp msgbox.asm
Po preložení súboru msgbox.asm sa vytvorí objektový súbor msgbox.obj.
Objektový súbor je len medzikrok k vytvoreniu spustiteľného súboru. Obsahuje príkazy a dáta v binárnom tvare. Už chýba len prilinkovanie linkerom.
Teda pokračujeme prilinkovaním :
- link /SUBSYSTEM:WINDOWS
/LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS informácia pre linker o aký typ spustiteľného súboru ide
/LIBPATH:<path
to import library> hovorí linkeru kde sú importované knižnice. Ak používate MASM32, tak sú všetky v adresári MASM32\lib.
Linker prečíta objektový súbor a nastaví adresy pre importované knižnice. Keď je proces linkovania dokončený, vytvorí sa spustiteľný súbor msgbox.exe.
Teraz máte súbor msgbox.exe.
Spustite ho. Nič sa nedeje. Samozrejme. To najdôležitejšie sme do nášho programu nevložili. Ale aj takto je to program pre Windows. A pozrite sa na jeho veľkosť! U mňa je to 1,536 bajtov.
Teraz do kódu vložíme naše okno so správou ( messagebox). Prototyp funkcie je nasledovný:
MessageBox
PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd je handle (kľučka) rodičovského okna.
Je to číslo identifikujúce daný objekt. Jeho hodnota nie je pre nás dôležitá. Len si zapamätajte, že reprezentuje okno. Ak chcete niečo robiť s oknom, musíte sa naň odkazovať pomcou jeho handle (kľučky).
lpText
je ukazovateľ na text, ktorý sa má zobraziť v klientskej oblasti okna messagebox-u. Ukazovateľ je adresa niečoho. Ukazovateľ na textový reťazec==adresa, kde je uložený text.
lpCaption
je ukazovateľ na titulok messagebox-u
uType
špecifikuje ikonu a druh tlačítok, ktoré sa majú v messagebox-e zobraziť
Teraz upravíme súbor msgbox.asm nasledovne:
.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 assembler je najlepší!“,0
.code
start:
invoke MessageBox,
NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,
NULL
end start
Preložte ho a spustite. Vidíte messagebox zobrazujúci text „Win32 assembler je najlepší!“.
Pozrime sa teda na zdrojový kód .
Definujeme dva nulou ukončené reťazce v sekcii .data. Pamätajte si, že každý ANSI reťazec musí byť ukončený NULL (0 hexadecimálne).
Používame dve konštanty,
NULL a MB_OK. Tieto konštanty sú zdokumentované v súbore windows.inc. Preto sa môžete na ne odkazovať menom a nie hodnotou. Toto zlepší čitateľnosť zdrojového kódu.
Operátor addr
sa používa na prejdenie na adresu návestia. Dá sa však používať len v kontexte s príkazom invoke.
Nemôžete ho použiť napríklad na priradenie adresy do regisra/premennej. Tam musíte použiť offset. Rozdiely sú dva:
- Napríklad, ak je návestie definované niekde ďalej v zdrojovom kóde od príkazu invoke, takéto použitie nebude fungovať.
-
addr
môže používať lokálne premenné, kým
offset nie.
Lokálna premenná je nejaký vymedzený pamäťový priestor. Vy budete poznať len jeho adresu počas behu programu. addr
je schopný zaoberať sa lokálnymi premennými preto, lebo prekladač zistí najskôr či sa jedná o globálnu alebo lokálnu premennú. Ak sa jedná o globálnu premennú, tak vloží adresu tejto premennej do objektového súboru. Doteraz teda pracuje ako offset.
Ak sa však jedná o lokálnu premennú, tak prekladač generuje sekvenciu príkazov pred volaním funkcie:
invoke
MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
……
MsgBoxCaption
db „Iczelion Tutorial No.2“,0
MsgBoxText
db „Win32 Assembly is Great!“,0
MASM vyhlási chybu. Ak sa použije offset miesto
addr
v tomto kóde, MASM tento program preloží bez problémov.
lea
eax, LocalVar
push eax
Potom je možné určovať adresy aj lokálnych premenných .
Win32 Assembly HomePage]