Академический Документы
Профессиональный Документы
Культура Документы
Lekcija 1: Osnove
Za pracenje ovih lekcija je potrebno osnovno poznavanje MASM assemblera. U slucaju da niste upoznati sa njim
downloadujte win32asm.exe i proucite tekst iz paketa pre nego sto nastavite sa ovim lekcijama.
Teorija:
Win32 programi se izvrsavaju u posebnom zasticenom modu koji postoji jos od 80286 vremena. 80286 je zastareo
procesor koji se vise ne koristi tako da nas interesuju samo 80386 i noviji. Windows kao operativni sistem izvrsava
svaki program u odvojenom virtuelnom prostoru sto znaci da svaki program ima svoj sopstveni prostor od 4GB.
Ovo ipak ne znaci da svaki Win32 program ima na raspolaganju 4GB fizicke memorije vec da ima mogucnost da radi
u jednom okruzenju gde se adrese lokacija u memoriji krecu u tom rasponu. Windows ce uraditi sve sto je potrebno da
bi adresa kojoj pristupamo bila vazeca ali naravno program mora da se povinuje pravilima operativnog sistema jer ce u
suprotnom izazvati poznati General Protection Fault (GPF).
Svaki program je sam u svom virtuelnom prostoru sto je suprotno onome sto smo imali na Win16. U 16-bitnom
operativnom sistemu je svaki program mogao da "vidi" bilo koji drugi, sto nije moguce u Win32. Ovo je dobro jer
umanjuje sanse da jedan program pise preko informacija ili instrukcija drugog programa.
Organizacija memorije se takodje promenila od 16-bitnih dana. Pod Win32 vise ne morate da se brinete o segmentima!
Postoji samo jedan "memorijski model" zvani "flat memory model". Vise ne postoje segmenti od 64K vec jedan
prostor od 4GB. Ovo zaci da vise nema potreba da se igrate sa segment registrima tako da se mozete direktno odnositi
na bilo koji deo memorije sto je OGROMNA olaksica programerima i to je bas ono sto cini Win32 assembly
prgramiranje lako kao C.
Kada programirate za Win32 morate da znate neka osnovna pravila. Jedno od takvih pravila je to da Windows koristi
registre esi, edi, ebp i ebx i da ocekuje da se njihove vrednosti ne menjaju. Zato zapamtite ovo prvo pravilo: ako zelite
da koristite neki od ovi registara u nekoj svojoj porvratnoj ("callback") funkciji onda nemojte da zaboravite da pre
izlaska iz funkcije povratite vrednosti tih registara. Povratna fukcija je neka vasa funkcija ciju ste adresu negde dali
Windowsu pa je on pokrece. Jedan klasican primer takve funkcije je funkcija koja prima i odgovara na sve Windowsove poruke upucene odredjenom prozoru, popularno zvana "window procedure". Ovo nikako ne znaci da ne mozete da
koristite gore navedene registre vec samo da morate da osigurate da ce njihova vrednost pri izlasku iz funkcije biti ista
kao i na ulasku u nju.
Sadrzaj:
Evo skeleta jednog programa. Ne brinite ako ne razumete nesto od koda. Malo kasnije cu objasniti svaki deo
pojedinacno.
.386
.MODEL Flat, STDCALL
.DATA
<Ne inicijalizovan data segment>
......
.DATA?
<Data segment sa inicijalizovanim vrednostima>
......
.CONST
<Konstante>
......
.CODE
<etiketa>
<Programski kod>
.....
end <etiketa>
To je sve! Ajmo da sada analiziramo ovaj skelet.
.386
Ovo je komanda naseg assemblera koja mu govori da koristi istrukcije procesora 80386. Pored nje mozete da koristite
i .486, .586 ali je najsigurnije da se ostane pri .386. Pored ovoga postoje i dva skoro identicna moda u obliku
Lekcija 2: MessageBox
U ovoj lekciji cemo napraviti potpuno funkcionalni Windows program koji ce prikazati poruku "Win32
assembly is great!". Skinite primer ovde . Preptostavlja se da ste iskusni sa MASM assemblerom.
Teorija:
Windows se sastoji od puno funkcija ciju glavnicu cini Windows API (Application Programming Interface).
Windows API je ogromna kolekcija veoma korisnih funkcija koje se nalaze u samom Windowsu i dostupne
su svim programima pisanim za Windows. Sve te funkcije se u stvari nalaze u nekoliko DLL modula kao na
primer u kernel32.dll, user32.dll ili gdi32.dll-u. Kernel 32 dll sadrzi API funkcije koje se koriste u radu sa
memorijom i procesima. User32.dll se bavi korisnickim interfejsom vaseg programa dok se Gdi32.dll koristi
za rad sa grafikom. Postoji jos DLL-ova sto sem ova glavna tri i sve ih mozete koristiti ako imate dovoljno
informacija o zeljenim API funkcijama.
Windows programi se dinamicki povezuju sa ovim DLL-ovima sto znaci da se sam kod ovih funkcija ne
ukljucuje u kod vaseg EXEa po njegovom stvaranju. Da bi program znao gde moze da nadje zeljene API
funkcije za vreme njegovog izvrsavanja, morate da u njega ukljucite i izvesne informacije. Te iformacije su
biblioteke, zvane Import Libraries. Ako ne vezete program sa odgovarajucim bibliotekama on nece moci da
locira API funkcije i samim tim nece biti operativan.
Kada je Windows program ucitan u memoriju Windows cita informacije smestene u programu. Te
informacije izmedju ostalog obuhvataju imena API funkcija koje program poziva kao i DLL kome te funkcije
pripadaju. Kada Windows pronadje te informacije on onda ucita te DLL-ove i "popravi" sve reference ka tim
funkcijama tako da kada program pozove neku od njih tok ode pravo do mesta u memoriji gde se nalazi
odredjena funkcija.
Postoje dve kategorije API funkcija: Jedna je ANSI dok je druga UNICODE. Imena API funkcija pisanih za
ANSI se zavrsavaju sa "A", na primer, MessageBoxA, dok se umena UNICODE funkcija zavrsavaju sa "W"
(skraceno od "Wide Char"). Windows 95 je baziran na ANSI standardu dok je Windows NT na UNICODEu.
Mi smo uglavnom upoznati sa ANSI stringovima koji su uvek jedan skup karaktera okoncan sa jednom NULL
karakterom. ANSI karakter zauzima samo jeda bajt. Dok je ANSI dovoljan za Evropske jezike on ipak ne
moze da obuhvati i nekoliko istocnjackih jezika koji sadrze po nekoliko stotina razlicitih karaktera. Iz tog
razloga je na scenu stupio UNICODE ciji je karakter velicine dva bajta i omogucava nam da imamo do
65536 jedinstvenih karaktera.
U vecini slucajeva cete koristiti odgovarajuce "include" fajlove koji ce da odradjuju koje verzije API funkcija
cete koristiti u zavisnosti za koju platformu pisete program tako da se sve te API funkcije sa A i W
zavrsetcima mozete pozivati bez tih dodatnih karaktera, na primer MessageBoxA mozete pozivati kao
MessageBox.
Osnovni Skelet:
Za sada pogledajmo samo kako izgleda skelet a kasnije cemo ga popuniti.
.386
.model flat, stdcall
.data
.code
start:
end start
Rad programa krece od prve instrukcije koja se nalazi ispod oznake koja prati end direktivu. U ovom skeletu
rad programa pocinje od prve instrukcije odmah ispod startoznake. Izvrsenje ce se linearno nastaviti sve
dok ne dodje na red neka instrukcija koja utice na dalji tok programa, kao na primer jmp, jne, je, ret ili
neka druga. Te funkcije preusmeravaju tok programa na neku drugu instrukciju. Kada program treba da se
okonca onda jednostavno treba da pozove API funkciju ExitProcess.
ExitProcess proto uExitCode:DWORD
Ova linija iznad je bila prototip ili deklaracija funkcije. Ona opisuje funkciju assembleru/linkeru tako da on
moze da izvrsi provere parametara umesto vas. Format jednog prototipa je ovakav:
ImeFunkcije PROTO [NazivParametra]:TipParametra, [NazivParametra]:TipParametra,...
U kratko, ime funkcije prati kljucna rec PROTO a onda lista sa tipom svakog pojedinacnog parametra gde je
svaki tip jednog parametra odvojen zarezom od drugog. U ExitProcess primeru, ExisProcess definise
funkciju koja prima samo jedan parametar tipa DWORD. Prototipovi funkcija su od posebne koristi kada se
koristi makro "invoke" koji olaksava pozivanje funkcija i proverava ono sto smo mi napisali sa onim sto je
definisano u prototipu. Na primer, ako uradite sledece:
call ExitProcess
bez smestanja neke DWORD vrednosti na stack, assembler/linker nece biti u stanju da primeti tu vasu
gresku pa cete vi kasnije pri radu programa primetiti da blokira. Ali, ako koristite:
invoke ExitProcess
Linker ce da vas informise da ste zaboravili da smestite neku DWORD vrednost na stack i time ce spreciti
sve greske takvog tipa. Ja preporucujem koriscenje invoke umesto standardne call instrukcije. Sintaksa
invoke makroa je sledeca:
INVOKE izraz [,argumenti]
izraz moze biti ime funkcije ili pointer ka funkciji. Parametri su razdvojeni zarezima od imena funkcije i
jedan od drugog.
Vecina prototipova API funkcija se nalazu u "include" fajlovima. Ako koristite Hutchov MASM32 paket onda
ih mozete sve naci u \MASM32\Include\ direktorijumu. Include fajlovi imaju ekstenziju .INC i ostatak imena
odgovara imenu DLLa. Na primer, ExitProcess se nalazi u kernel32.dll-u, "izvod" te funkcije se nalazi u
biblioteci kernel32.lib a prototipovi se nalaze u kernel32.inc fajlu.
Mozete takodje da pisete prototipove za vase sopstvene funkcije.
Kroz moje primere cu koristiti Hutchov windows.inc koji mozete skinuti sa http://win32asm.cjb.net
Sto se tice ExitProcess-a, uExitCode parametar je vrednost koju zelite da vas program prosledi Windowsu
po okoncanju programa. Mozete da pozovete ExitProcess ovako:
invoke ExitProcess, 0
Stavite tu liniju direktno ispod start oznake i dobicete Win32 program koji odmah okoncava svoj rad. Koliko
god mali i beskoristan bio, to je ipak jedan funkcionalan Windows program.
Ovo je minimum:
.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 govori MASM-u da se sve oznake razlikuju od drugih slicnih oznaka koje su pisane sa
drugim setom malih ili velikih slova. Na primer, ExitProcess nije isto sto i exitprocess ili EXITprocess.
Zapazite novu direktivu, include . Ova direktiva je pracena imanom fajla koji zelite da bude ubacen na to
specificno mesto. U ranijem primeru kada MASM dodje to linije include \masm32\include\windows.inc on ce
otvoriti windows.inc koji se nalazi u \MASM32\include direktorijumu i obraditi sve konstante nadjene u
njemu kao da ste copy/paste -ovali sadrzaj tog fajla na bas to mesto. Hutchov windows.inc sadrzi puno
konstanti i struktura koje su potrebne kada se pisu Win32 programi ali ne sadrzi prototip ni jedne funkcije.
Windows.inc ni u kom slucaju nije potpun. Hutch se trudi da u njega ubaci sto je vise moguce konstanti i
struktura ali je jos uvek jako veliki broj njoh ostao van. Za najnovije verzije windows.inc fajla sa vremena
na vreme proveravajte moju i Hutchovu stranu.
Kako se u windows.inc ne nalaze definicije funkcija moracete da u program ubacujete i ostale potrebne
include fajlove iz \MASM32\include direktorijuma.
U nasem renijem primeru pozivamo funkciju iz kernel32.dll-a, tako da moramo da ubacimo i definicije
funkcija za kernel32.dll. Fajl koji nam treba je kernel32.inc. Ako otvorite taj fajl sa nekim tekst editorom
videcete da je pun prototipovima funkcija iz kernel32.dll-a. Ako ne ukljucite kernel32.inc mocicete da
pozivate ExitProcess i ostale funkcije ali samo koristeci instrukciju call. Necete moci da invoke funkiju. Ono
sto hocu da kazem je: da bi koristili invoke za neku funkciju morate da negde u kodu imate i prototip
funkcije. U primeru ako ne ubacite kernel32.inc mozete sami definisati ExitProcess negde u kodu i slobodno
koristiti invoke. Include fajlove koristimo da se ne bi gubili vreme sami kucajuci definicije svih tih funkcija
koje cemo koristiti.
Sada se susrecemo sa novom direktivom, includelib . includelib ne radi kao include . To je samo nacin da
kazete assembleru koju biblioteku vas program koristi. Kada assembler vidi includelib direktivu on zapisuje
komandu linkeru negde u object fajl tako da linker zna sa kojim bibliotekama linker mora da spoji vas
program. Ovo mozete uraditi i rucno ali je verujte veoma naporno, a i pored toga moze se pokazati kao
nemoguc zadatak jer komandna linija moze imati samo 128 karaktera.
Sada sacuvajte primer pod imenom msgbox.asm. Pretpostavljajuci da je ml.exe u istom katalogu ili da je u
PATH-u, assemblujte msgbox ovako:
ml /c /coff /Cp msgbox.asm
/c govori MASMu da samo assembluje i ne poziva link.exe. U vecini slucajeva necete zeleti MASM da
poziva automatski link.exe jer cete zeleti da odradite jos neke stvari pre linkera.
/coff govori MASM-u da stvori .OBJ fajl u COFF formatu. MASM koristi varijacuju COFF-a (Common
Object File Format) koji se koristi pod Unixom kao format za object i izvrsne fajlove.
/Cp govori MASM-u da ne dira velika i mala slova. Ako koristite Hutchov MASM32 paket mozete staviti
"option casemap:none" na pocetak koda, ispod .model direktive.
Posto uspesno assemblujete msgbox.asm dobicete msgbox.obj. msgbox.obj je objekat i samo je jedan
korak od konacnog EXE fajla. Sve instrukcije i informacije se nalaze na svom mestu i samo je ostalo da
linker sredi adrese funkcija koje se pozivaju.
Zatim nastavite sa linkom:
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS obavestava Link o tipu EXEa ovog programa.
/LIBPATH:<path to import library> govori Link-u gde se nalaze biblioteke. Ako koristite MASM32, one ce se
nalaziti u MASM32\lib direktorijumu.
Link cita objekat i popravlja adrese tako da funkcije iz eksternih DLLova ili drugih modula budu dostupne.
Kada se ovaj proces zavrsi rezultat je msgbox.exe.
Sada ste dobili msgbox.exe. Videcete da se nista ne dogadja kada ga pokrenete jer nismo nista stavili u
njega. To je ipak Windows programiranje. Ali pogledaj velicinu tog programa! Na mom kompjuteru on je
svega 1,536 bajta.
Sada cemo da stavimo jedan MessageBox. Prototip te funkcije je sledeci:
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd je identifikacija (handle) prozora kome ovaj MessageBox pripada. O ovoj identifikaciji (window
handle) mislite kao o jednom broju koji predstavlja odredjeni prozor. Njena vrednost vam nije bitna.
Samo treba da se secate da ona oznacava jedan odredjeni prozor. Da bi bilo sta radili sa nekim
prozorom morate znati njegov handle.
lpText je pointer ka tekstu koji zelite da se pojavi u glavnom delu prozora MessageBox-a. Pointer je u
principu samo adresa necega. Pointer ka stringu je samo adresa prvog bajta tog stringa.
lpCaption je pointer ka naslovu MessageBox-a
uType govori funkciji kakvu ikonu i koje dugmice treba da stavi na prozor
Ajmo da sada ubacimo poziv ka MessageBox-u u program.
A ovo je sta se dobije kada se sve spoji u jednu celinu:
.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
Assemblirajte i pokrenite program. Videcete prozor sa tekstom "Win32 Assembly is Great!".
Ajmo da jos jednom pogledamo kod. Napravili smo dva stringa koja se zavrsavaju sa NULL karakterom u
.DATA sekciji. Secate se da se svaki ANSI string u Windows-u mora zavrsiti sa NULL (0) karakterom.
Koristimo dve konstante, NULL i MB_OK. Ove konstante su definisane u windows.inc tako da umesto da
koristimo neke nerazumljive brojeve samo navodimo imena tih vrednosti sto uvecava citkost koda.
ADDR operator se koristi da bi se dobila adresa neke oznake (labele) ili funkcije i samo radi u konteksu
invoke makroa. Pored ADDR, mozete koristiti i offset koji ima skoro isti efekat. Postoje neke sitne razlike i
jedna od njih je da dok offset moze da radi sa labelama koje su definisane kasnije u kodu, ADDR to ne
moze.
Na primer, ako je neka promenjiva definisana negde nize u izvornom kodu, ADDR jednostavno nece raditi.
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0
MASM ce da prijavi gresku. Ako koristite offset umesto ADDR onda ce MASM da odradi posao bez problema.
Druga razlika je da ADDR moze da radi i sa lokalnim promenljivim dok offset to ne moze. Lokalna
promenljiva je samo neki rezervisan prostor na staku i adresa te promenjive je poznata samo za vreme
izvrsavanja programa a ne za vreme assemblovanja. Offset se prevodi za vreme assemblija tako da je
prirodno da ne moze da radi sa lokalnim promenljivim. ADDR je sposoban da prvo proveri da li je
promenljiva lokalna ili ne, tako da ako jeste onda se prevodi u skup instrukcija koje dolaze do adrese
promenljive, a ako nije onda radi identicno offset-u. Evo kako dolazi do adrese lokalne promenljive:
lea eax, LocalVar
push eax
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ;pozivi ka funkcijama iz user32.lib i kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA ;inicijalizovan data segment;
ClassName db "SimpleWinClass",0 ;ime klase prozora
AppName db "Our First Window",0 ;ime naseg prozora
.DATA? ;ne inicijalizovan data segment
hInstance HINSTANCE ? ;instance handle programa
CommandLine LPSTR ?
.CODE ;ovde pocinje programski kod
start:
invoke GetModuleHandle, NULL ;dodji do instance handle-a naseg programa.
;pod Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ;dodji do komandne linije. Ovo vam nije neophodno AKO
;vasem programu nije potrebna komandna linija.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine,SW_SHOWDEFAULT ;poziv glavnoj funkciji
invoke ExitProcess, eax ;izadji iz programa. izlazni kod je vracen kroz eax, u WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ;napravi lokalne promenljive na stacku
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX ;popuni elemente wc strukture sa vrednostima
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ;registruj klasu prozora
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ;prikazi prozor na radnoj povrsini
invoke UpdateWindow, hwnd ;osvezi povrsinu prozora
.WHILE TRUE ;proveravaj poruke u petlji
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ;vrati izlazni kod u eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT,wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ;ako korisnik zatvori nas prozor
invoke PostQuitMessage,NULL ;ugasi program
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ;predefinisana procedura za obradu poruka
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
Moze vas odbiti cinjenica da je cak i za jedan jednostavan Windows program potrebno dosta koda. Zato je
vazno vec sada napomenuti da je vecina tog neophodnog koda prilicno standardna pa cete je moci kopirati
iz jednog svog starog programa ili ako vam se tako vise svidja mozete da napisete svoju biblioteku cijih ce
par funkcija obavljati sav taj posao u vasim narednim programima, a da na vama ostane da samo napisete
glavni deo. To je u principu ono sto C kompajleri cine. Oni vas puste da pisete kod WinMain procedure i da
ne brinete o drugim stvarima. Jedina mana toga je sto morate imati proceduru zvanu WinMain i nikako
drugacije inace C kompajleri nece moci da kombinuju vas kod sa uvodom i zakljuckom jednog windows
programa. Takvih zabrana nema sa ASM jezikom. Mozete koristiti koje god ime hocete umesto WinMain, a
mozete da i ne koristite nikakvu funkciju.
Pripremite se, ovo ce biti duga, duga lekcija. Ajmo da analiziramo ovaj program do koske!
.386
.model flat,stdcall
option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
Prve tri linije su obavezne. .386 obavestava MASM da nameravamo da koristimo 80386 set instrukcija u
ovom programu. .mode flat,stdcall mu govori da ce nas program da koristi FLAT model memorije i da
koristimo stdcall nacin prosledjivanja parametara kao default.
Sledece je prototip WinMain funkcije. Kako cemo kasnije pozvati WinMain moramo da prvo definisemo
prototip te funkcije da bi posle mogli da je pozovemo koristeci invoke.
Moramo da ubacimo i windows.inc na pocetak izvornog koda. On sadrzi vazne strukture i konstante koje se
cesto koriste u programima. Windows.inc fajl je samo jedan tekstulni fajl i mozete ga otvoriti sa bilo kojim
tekst editorom. Vazno je napomenuti da windows.inc (jos uvek) ne sadrzi sve strukture i konstante i da
Hutch i ja jos uvek radimo na njemu. Vi mozete dodati nove ako se tamo ne nalaze.
Nas program poziva API funkcije koje se nalaze u user32.dll-u (CreateWindowEx i RegisterWindowClassEx
na primer) i u kernel32.dll-u (na primer ExitProcess), tako da moramo da se vezemo i sa tim biliotekama.
Sledece pitanje moze biti: Kako ja mogu da znam koje bi biblioteke trebalo da budu linkovane sam mojim
programom? U tom slucaju bi odgovor bio: Morate nauciti gde se koja API funkcija nalazi. Tako, ako
koristite negu funkciju iz gdi32.dll, morate se linkovati sa gdi32.lib.
Ovo je pristup MASMa. TASMov nacin vezivanja sa ovim tipom biblioteka je znatno jednostavniji: uvek samo
treba linkovati import32.lib i nista vise.
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
Sledece na redu su "DATA" sekcije.
U .DATA smo stavili dva stringa koja se zavrsavaju sa NULL karakterom (ASCIIZ stringovi): ClassName koji
je naziv klase naseg prozora i AppName sto je ime naseg prozora. Napomena: ove dve promenjive imaju
dodeljene vrednosti vec u startu.
U .DATA? su takodje samo dve promenljive: hInstance (instance handle naseg programa) i CommandLine
(komandna linija istog). Ovi nepoznati tipovi HINSTANCE i LPSTR su u stvari samo sinonimi za DWORD i
deklarisani su u windiws.inc. Vi mozete koristiti DWORD umesto njih ako vam je lakse. Napomena: sve
promenljive iz .DATA? sekcije nemaju pocetne vrednosti na pocetku vec samo govorimo Windowsu da treba
da rezervise memoriju za njih kada pokrene program.
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
.....
end start
.CODE sadrzi sve nase instrukcije. Vas kod se mora nalaziti izmedju <pocetak>: i end <pocetak>. Ime ove
oznake koja je u ovom slucaju "pocetak" nije vazno. Mozete koristiti bilo koje ime sve dok je jedinsveno u
programu i postuje MASMova pravila o nazivanju promenjivih i labela.
Nasa prva instrukcija je poziv GetModuleHandle koji nam daje instance handle naseg programa. Pod Win32,
instance handle i module handle su ista stvar. Mozete misliti o instance handle-u kao o identifikaciji vaseg
programa. Ona se koristi u nekoliko API funkcija koje nas program mora da zove tako da je dobro na
pocetku programa doci do nje i sacuvati je negde.
U stvari, u Win32, instance handle je linearna adresa programa.
Nakon povratka iz Win32 funkcije ona moze vratiti neku vrednost. Ako takve vrednosti ima onda ce ona biti
u eax registru. Sve druge vrednosti su vracene kroz promenljive koje su prosledjene funkciji.
Win32 funkcije koju vi zovete skoro skoro nikada nece menjati vrednosti ebx, edi, esi i ebp registara.
Nasuprot njima, ecx i edx je malo verovatno da ostanu neizmenjeni.
Nemojte ocekivati da vrednosti eax, ecx i edx registara ostanu sacuvane posle API poziva.
Nakon izvrsetka API funkcije ocekujte povratnu vrednostu u eax-u. Ako se bilo koja od vasih funkcija zove
od strane Windowsa onda i vi morate da pratite ovo pravilo: cuvajte i ponovo podesavajte vrednosti ebx,
edi, esi i ebp registare tako da i posle poziva funkcije oni nastave da imaju iste vrednosti kao i pre njenog
poziva. Ako se ne pridrzavate ovoga moze se desiti da vas program blokira. Ovo se odnosti na Window
Procedure funkciju i sve druge callback funkcije.
Poziv ka GetCommandLine nije obavezan ako vas program nece proveravati komandnu liniju. U ovom
primeru sam samo pokazao kako se ta funkcija poziva samo da znate kako se to radi u slucaju da vam je to
potrebno u nekom vasem programu.
Sledeci je poziv ka WinMain. On ovde prima cetiri parametra. Instance handle programa, instance handle
prethodne kopije istog programa, komandnu liniju i stanje prozora pri startovanju. Pod Win32 NE POSTOJI
prethodan instance programa tako da je vrednost hPrevInst uvek 0. Ovo je ostalo uz Win16 dana kada je
koliko god puta ista kopija programa bila pokrenut on bio u istom memorijskom prostoru pa je program
zeleo da sazna da li je prvi ili ne. Pod Win16, ako je hPrevInstance NULL onda nema druge operativne
kopije programa u memoriji.
Funckcija ne mora da se zove WinMain. Imate potpunu slobodu sto se njenog imena tice. Cak ne morate ni
da koristite takvu funkciju vec mozete sav kod iz nje da ubacite do GetCommandLine poziva i sve ce opet
savrseno raditi.
Po povratku iz WinMain, eax sadrzi izlaznu vrednost i mi tu vrednost prosledjujemo ka ExitProcess funkciji
koja gasi nas program.
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
Prethodna linija je protip funkcije WinMain. Zapazite parametre sto prate PROC direktivu. To su parametri
koje prima WinMain i kojima vi mozete da pristupite po imenu tako da nije potrebno da baratate sa
stackom. Pored toga, MASM ce stvoriti neophodan pocetan i zavrsan kod funkcije tako da vi ne morate da
se bavite sa stackom na ulazu i izlazu iz funkcije.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
Direktiva LOCAL uzima memoriju sa stacka koju ce da koristi kao lokalnu promenljivu. Sve LOCAL direktive
se moraju nalaziti odmah ispod PROC direktive. LOCAL direktiva je pracena sa <ime promenljive>:<tip
promenljive>. Tako LOCAL wc:WNDCLASSEX govori MASMu da veze memoriju sa stacka u velicini
WNDCLASSEX strukture za promenljivu koju nazivamo wc. Na ovaj nacin mozemo raditi sa wc bes teskoca
koje prate manipulaciju stacka. Losa strana ovoga je sto lokalne promenljive nemozemo koristiti van
procedure jer su one stvorene na ulazu u istu i unistene pred izlaz. Druga losa strana je da ne mozete
automatski dati pocetnu vrednost ovim promenljivim zato sto one pretstavljaju samo memoriju sa stacka
koja je dinamicno vezana. Morate rucno dodeliti vrednosti ovim promenljivim.
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx,addr wc
Linije iznad su stvarno jednostavne. Potrebno je samo nekoliko linija instrukcija i koncept iza svih tih linija je
klasa prozora . Window class ili klasa prozora nije nista drugo do skupa osobina odredjenog prozora. Ona
sadrzi par vaznih obelezja prozora kao na primer njegovu ikonu, kursor, funkciju odgovornu za njega, boju
itd. Prozor stvarate iz njegove klase. Ovo je na neki nacin objektno orijentisan koncept. Ako zelite da
stvorite vise od jednog prozora sa istim karakteristikama videcete da je veoma korisno sto sve te
informacije postoje samo na jednom mestu i ne dupliraju se bez razloga. Setitte se da je Windows
dizajniran kada su memorijski moduli imali mali kapacitet i vecina kompjutera je imala samo 1 MB RAMa pa
se memorija morala stedeti. Kada pravite novu klasu za svoj prozor onda morate ispuniti WNDCLASS ili
WNDCLASSEX strukturu i pozvati RegistarClass ili RegisterClassEx pre nego sto nastavite sa kreiranjem
prozora. Svaku novu klasu morate registrovati samo jednom.
U Windowsu postoji par predefinisanih klasa kao sto su na primer BUTTON (dugme) ili EDIT (polje za unos
teksta). Za ove prozore, ili bolje receno kontrole, ne morate registrovati klase vec samo pozvati
CreateWindowEx sa predefinisanim imenom klase.
Najvazniji clan WNDCLASSEX strukture je lpfnWndProc. lpfn oznacava da se radi o pointeru ka funkciji, ili
njenoj adresi. Ovo je "long" pointer jer u Win32 ne postoje "near" ili "far" pointeri kao nekada u DOSu. lpfn
je u principu samo nesto sto nam je ostalo iz Win16 dana. Svaka klasa mora biti vezana sa jednom
funkcijom koja se zove "window procedure". Windows procedure je odgovorna sa reagovanja na poruke
koje su upucene njenom prozoru. Windows ce slati razne poruke ovoj proceduri da bi je obavestilo o
izvesnim dogadjajima koji mogu imati veze sa unosom korisnika preko misa ili tastature. Na window
proceduri se nalazi odgovornost da pravilno odgovori na ove poruke. Vecinu vremena cete provoditi pisuci
akcije koje treba da se izvrse kada se primi neka poruka.
Evo i detaljnijeg opisa svih clanova WNDCLASSEX strukture:
WNDCLASSEX STRUCT DWORD
cbSizeDWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS
cbSize: Velicina WNDCLASSEX strukture u bajtovima. Mozemo koristiti SIZEOF operator koji ce nam vratiti
ovu vrednost.
style: Stil prozora koji se stvara koristeci ovu klasu. Mozete kombinovati vise stilova zajedno koristeci "or"
operator.
lpfnWndProc: Adresa window procedure odgovorne za sve poruke koje se salju prozorima koji koriste ovu
klasu.
cbClsExtra: Broj ekstra bajtova koje ce Windows da alocira odmah po zavrsetku Window-Class trukture. Taj
deo memorije ce biti ispuljen nulama i tu vi mozete drzati neke informacije u vezi sa klasom.
cbWndExtra: Broj ekstra bajtova koje ce Windows da alocira nakon instance prozora. Operativni sistem ce
da podesi ove bajtove na nule. Ako program koristi WNDCLASS strukturu da bi registrovao dialog stvoren
koristeci CLASS direktivu u resursu onda on mora podesiti ovaj clan klase na DLGWINDOWEXTRA.
hInstance: Instance handle modula.
hIcon: Identifikacija ikone koju dobijate LoadIcon funkcijom.
hCursor: Identifikacija kursora koju dobijate LoadCursor funkcijom.
hbrBackground: Boja prozora stvorenog koristeci ovu klasu.
lpszMenuName: Identifikacija menija koji ce se koristiti za prozore.
lpszClassName: Naziv klase.
hIconSm: Identifikacija male ikone koja je vezana sa klasom. Ako je ovaj clan NULL sistem ce potraziti malu
ikonu i ikoni datoj u hIcon clanu.
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
Nakon registrovanja klase pozivamo CreateWindowEx da bi stvorili nas prozor baziran na datoj klasi. Postoji
12 parametara u ovoj funkciji.
CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD
Ajmo da vidimo opis svih parametara:
dwExStyle: Ekstra stilovi prozora. Ovo je parametar dodat staroj CreateWindow funkciji. Tu mozete staviti
nove stilove prozora za Windows95 & NT.Sada mozete odrediti standardne stilove prozora u dwStyle
parametru ali ako zelite neke od novih, kao na primer prozor koji je iznad svih ostalih onda ih morate ubaciti
u ovaj parametar. Ako je njegova vrednost NULL nece se koristiti ni jedan novi stil.
lpClassName: (Obavezno) Adresa ASCIIZ stringa koji sadrzi ime klase prozora koju zelite da koristite za
prozor. Klasa moze biti neka vasa regisrovana klasa ili jedna od predefinisanih. Svaki prozor koji stvorite
mora biti zasnovan na nekoj klasi.
lpWindowName: Adresa ASCIIZ stringa koji sadrzi ime prozora. Ime prozora ce biti pikazano u njegovoj
naslovnoj liniji. Ako je ovaj parametar NULL onda ce ta linija biti prazna.
dwStyle: Stilovi prozora. Ovde mozete odrediti kako zelite da izgleda prozor. Koriscenje NULL vrednosti je
dozvoljeno ali takav prozor ona nece imati sistemski meni i dugmice na sebi pa cete morati da koristite
ALT+F4 da bi ga zatvorili. Najprimenjeniji stil prozora je WS_OVERLAPPEDWINDOW. Stil prozora je u stvari
samo skup prekidaca koji zauzimaju jedan bit u celoj 32bitnoj vrednosti tako da ih mozete kobinovati
koristeci "or" operator. WS_OVERLAPPEDWINDOW je kombinacija vise stilova koji se cesto koriste.
X,Y: Koordinate gornjeg levog ugla prozora. Ova ce vrednost biti CW_USEDEFAULT ako zelite da Windows
odlucuje o tome gde ce se pojaviti prozor.
nWidth, nHeight: Sirina i visina prozora u pixelima. Mozete takodje koristiti CW_USEDAFAULT koji ce
prepustiti Windowsu da izabere odgovarajucu sirinu i visinu za vas.
hWndParent: Identifikacija (handle) prozora-roditelja ukoliko jedan postoji. Ovaj parametar govori Windows
da je novi prozor dete nekog postojeceg prozora koji time postaje njegov roditelj. Napomena: ovo nije
odnost roditelj-dete kao u slucaju MDI interfejsa. Dete jednog prozora nije po defaultu vezano za radni
prostor roditelja vec se ta veza koristi od strane samog Windows-a za neke njegove stvari. Kada je jedan
prozor zatvori automatski ce se zatvoriti i sva njegova deca-prozori. Kako je nas prozor samo jedan i ne
zelimo da bude dete bilo kog drugog prozora mi cemo za ovaj handle dati NULL vrednost.
hMenu: Identifikacija menija ovog prozora. Koristite NULL ako ce se koristiti meni odrednjen u klasi prozora
(Pogledajte WNDCLASSEX strukturu po lpszMenuName). lpszMenuName je default meni za tu klasu. Svaki
prozor stvoren sa tom klasom ce imati isti meni po defaultu osim ako se ovo ne preinaci preko hMenu
parametra. hMenu se koristi za jos jednu stvar: u slucaju da je prozor koji stvaramo kontrola onda prozor
ne moze imati svoj meni i parametar se koristi kao identifikacioni broj kontrole. Windows moze da odredi da
li je hMenu identifikacija kontrole ili menija tako sto ce da pogleda lpClassName parametar. Ako je ime klase
jedno od predefinisanih onda ce hMenu uzeti za ID kontrole, a u suprotnom ce ga smatrati za ID menija tog
prozora.
hInstance: Identifikacija programa koji stvara prozor.
lpParam: Opcioni pointer ka informacijama koje prosledjujemo prozoru. Ovo koriste MDI prozori da bi dosli
do CLIENTCREATESTRUCT informacija. Uglavnom cete ovaj parametar ostavljati NULL sto znaci da nikakve
informacije nece biti proslednjene CreateWindow funkciji. Prozor moze da dodje do ovih informacija
koristeci GetWindowLong funkciju.
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
Po uspesnom povratku iz CreateWindowEx funkcije dobicete identifikaciju novog prozora u eax registru.
Morate da cuvate ovaj broj za kasniju upotrebu. Prozor ovako stvoren nije odmah prikazan i morate pozvati
ShowWindow sa identifikacijom prozora i zeljenim stanjem prozora kako bi on bio prikazan. Zatim pozivate
UpdateWindow da bi Windows iscrtao i time obnovio radnu povrsinu prozora. Ovaj poziv je koristan kada
zelite da se u datom trenutku prozor ponovo iscrta ali ga sada mozete i izostaviti.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
Sada se prozor vec nalazi na ekranu ali on ne moze da prima nikakav unos sto znaci da ga mi moramo
informisati o svim relevantnim dogadjajima. Ovo se radi koristeci jednu petlju koja radi sa porukama. Postoji
samo jedna ovakva pelja u svakom modulu. Ova petlja koristi GetMessage da non-stop proverava poruke
sto Windows salje prozoru. GetMessage daje windowsu pointer ka MSG strukturi koja ce biti ispunjena sa
informacijama o poruci koju je Windows poslao. GetMessage se nece okoncati dok ne dodje do neke
poruke. Za to vreme Windows moze da prepusti kontrolu drugim programima i to cini multitasking semu
Win16 platforme. GetMessage vraca FALSE ako je primila WM_QUIT poruku. Tada treba okoncati petlju i
izaci iz programa.
TranslateMessage je pomocna funkcija koja uzima unos iz tastature i stvara novu poruku - WM_CHAR koja
je onda smestena u listu poruka upucenih prozoru. Poruka sa WM_CHAR sadrzi i ASCII vrednost stisnutog
tastera sto je mnogo lakse za koriscenje nego scan kodovi. Ova poruka ne mora da se koristi ako program
ne koristi unos sa tastature.
DispatchMessage salje poruku window proceduri odgovornoj za odredjeni prozor.
mov eax,msg.wParam
ret
WinMain endp
Kada se petlja okonca izlazna vrednost ne nalazi u wParam clanu MSG strukture. Ovu vrednost mozete
smestiti u eax tako da se ona vrati Windowsu. Windows trenutno ne koristi ovu vrednost ali je bolje igrati
na sigurno i povinovati se pravilima.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
Ovo je nasa window procedura. Ne morate koristiti WndProc naziv. Prvi parametar, hWnd je identifikacija
prozora kome je upucena ova poruka. uMsg je sama poruka. uMsg nije MSG struktura vec samo jedan broj.
Windows ima na stotine poruka koje nas uglavnom i ne zanimaju. Operativni sistem salje odgovarajuce
poruke kada se nesto dogodi datom prozoru, tu poruku prima window precedura i odgovara na nju ako je
zanima. wParam i lParam su samo dodatni parametri koji se koriste sa nekim porukama. Vecina poruka
salje dodatne informacije o dogadjaju kroz ova dva parametra.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Ovo je sada kritican deo. Ovo je gde se nalazi veci deo inteligencije programa. Kod koji odgovara na poruke
se nalazi u window proceduri. Vas kod mora da proverava da li ga interesuje trenutna poruka. Ako su
poruke vazne onda cete napisati kod koji ce da odradjuje neke operacije, a ako ne onda MORATE pozvati
DefWindowProc i proslediti joj sve parametre koje ste primili kroz window proceduru. Ova DefWindowProc
funkcija reaguje na sve poruke koje mi ne zelimo da koristimo a od vitalnog su znacaja za rad programa ili
sistema.
Jedina poruka na koju vi MORATE reagovati je WM_DESTROY. Ova poruka se salje window proceduri kada
je prozor zatvoren. Dok poruka dodje do vase procedure prozor je vec uklonjen sa ekrana i ona vas samo
obavestava da je sve gotovo o da treba da se pripremite za gasenje programa. Nemate drugog izbora sem
da ugasite program kada jednom dodje do ove tacke. Ako zelite da vam se pruzi sansa da sprecite korisnika
pri zatvaranju vaseg prozora onda morate da odgovorite na WM_CLOSE poruku. Za sada, nazad na
WM_DESTROY: posle rasciscavanja svih resurasa sto je program koristio morate pozvati PostQuitMessage
koja ce poslati WM_QUIT, koji ce redom informisati GetMessage da je program gotov i da treba da vrati
FALSE, sto ce opet okoncati glavnu petlju i ugasiti program. Mozete sami poslati WM_DESTROY poruku
svom prozoru sa DestroyWindow funkcijom.
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
kao i PAINTSTRUCT strukturu. Posle uspesnog poziva zovete GetClientRect da bi dosli do dimenzija radne
povrsine prozora. Ove dimenzije se vracaju kroz rect promenljivu koju cemo kasnije proslediti DrawText
funkciji kao jedan od parametara. Sintaksa DrawText funkcije je sledeca:
DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD
DrawText API funkcija je jednostavna za koriscenje. Ona se brine o nekim detaljima kao sto je automatsko
formatiranje teksta kako bi on stao u dati prostor, razne konverzije itd, tako da se vi mozete koncentrisati
na string. O TextOut funkciji cemo pricati u sledecoj lekciji. Vec smo rekli da DrawText formatira tekst kako
bi on stao u zadati pravougaonik. Ova funkcija koristi vec izabrani font, boju i pozadinu, a po izlazu u eax
smesta visinu teksta u jedinicama DC-a, u nasem slucaju, pikselima. Parametri su sledeci:
hdc je identifikacija DC-a.
lpString je pointer ka stringu koji zelite da nacrtate u pravougaoniku. Ovaj string mora biti zavrsen sa
NULL karakterom inace vi morate da date koliko je dugacak u nCount parametru.
nCount je broj karaktera u stringu. Ako je string zavrsen sa NULL karakterom nCount mora biti -1,
drugacije mora biti broj karaktera u stringu koji hocete da se nacrta.
lpRect je pointer ka pravougaoniku (struktura tipa RECT) u kome zelite da se crta. Nije moguce crtati
van ovog prevougaonika.
uFormat je vrednost koja govori kako ce se string prikazati u prevougaoniku. Koristimo tri vrednosti
kombinovane sa "or" operatorom:
DT_SINGLELINE - Tekst u jednoj liniji.
DT_CENTER - Horizontalno centriran tekst.
DT_VCENTER - Vertikalno centriran tekst. Mora se koristiti u kombinaciji sa DT_SIGNLELINE.
Posto zavrsite crtanje morate pozvati EndPaint funkciju da bi oslobodili DC.
To je sve. Osnovne tacke u ovoj lekciju su sledece:
Pozivate BeginPaint/EndPaint kao odgovor na WM_PAINT poruku.
Radite sta vec zelite sa radnom povrsinom prozora izmedju BeginPaing i EndPaint poziva.
Ako zelite da crtate po prozoru kao odgovor na neku drugu poruku imate dva izbora:
Koristite GetDc/ReleaseDC funkcije i crtajte izmedju ovih poziva.
Zovite InvalidateRect ili UpdateWindow i obelezite celu povrsinu prozora za crtanje i time
naterajte Windows da posalje WM_PAINT.
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
CreateFont stvara logicki font koji je najpriblizniji onom sto smo mi trazili kroz date parametre. Ova funkcija
ima vise parametara od bilo koje druge funkcije u Windows-u i vraca identifikaciju fonta koju cemo koristiti
sa SelectObject funkcijom. Ove parametre cemo ispitati do detalja.
CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD
1. nHeight je zeljena visina karaktera. NULL za predefinisanu visinu.
2. nWidth je zeljena sirina karaktera. Ovo bi trebalo da bude NULL kako bi Windows odredio sirinu u
zavisnosti od visine. Kako u primeru predefinisana vrednost cini slova ne citkim ja sam stavio 16
umesto NULL.
3. nEscapement je orijentacija jednog karaktera u odnosu na drugi u desetinama stepena. Ovo bi trebalo
da bude 0. Ako stavite 900 dobicete sve karaktere jedan iznad drugog. Ako stavite 1800 oni ce ici u
nazad, a ako stavite 2700 karakteri ce biti jedan ispod drugog.
file:///D|/Download%20Free%20zona/Knjige%20Racunari%20staro/Win32asm/tut005.html[18.4.2010 3:08:07]
4. nOrientation je nagib karaktera u desetinama stepena. Sa 900 ce svi karakteri biti polozeni. Koristite
1800 kada su karakteri jedan ispod drugog...
5. nWeight je gustina karaktera. Windows je predefinisao sledece velicine:
FW_DONTCARE equ 0
FW_THIN equ 100
FW_EXTRALIGHT equ 200
FW_ULTRALIGHT equ 200
FW_LIGHT equ 300
FW_NORMAL equ 400
FW_REGULAR equ 400
FW_MEDIUM equ 500
FW_SEMIBOLD equ 600
FW_DEMIBOLD equ 600
FW_BOLD equ 700
FW_EXTRABOLD equ 800
FW_ULTRABOLD equ 800
FW_HEAVY equ 900
FW_BLACK equ 900
6. cItalic - 0 za normalne, bilo sta drugo za kose karaktere.
7. cUnderline - 0 za normalne, bilo sta druo za podvucene karaktere.
8. cStrikeOut - 0 za normalne, bilo sta drugo za precrtane karaktere.
9. cCharSet je set karaktera za font. Ovo bi trebalo da bude OEM_CHARSET koji ce prepustiti Windows-u
da izabere font koji zavisi od verzije Windows-a.
10. cOutputPrecision je vrednost koja govori koliko blizu mora da bude izabrani font sa onim sto smo mi
trazili. Uglavnom cete koristiti OUT_DEFAULT_PRECIS.
11. cClipPrecision je preciznost klipovanja. Ovo odredjuje nacin klipovanja karaktera koji su delimicno van
pravougaonika odredjenog za crtanje. Predefinisana vrednost je CLIP_DEFAULT_PRECIS.
12. cQuality je kvalitet rezultujuceg crtanja. Ovaj parametar odredjuje koliko ce pazljivo GDI pokusati da
prilagodi zeljeni logicki font fizickom fontu. Postoje tri vrednosti: DEFAULT_QUALITY, PROOF_QUALITY
i DRAFT_QUALITY.
13. cPitchAndFamily je tip fonta. Moguce vrednosti ovog parametra morate kombinovati sa "or"
operatorom.
14. lpFacename je pointer ka ASCIIZ stringu sto odredjuje naziv fizickog fonta koji ce se koristiti.
Ovaj opis nije nikako sveobuhvatan. Obratite se svojoj Win32 API dokumentaciji za vise detalja.
invoke SelectObject, hdc, eax
mov hfont,eax
Posto dobijemo identifikaciju logickog fonta, moramo je koristiti da bi izabrali font u DC koristeci
SelectObject. SelectObject stavlja nove GDI objekte kao sto su pera, cetke i fontovi u DC i njima ce crtati
sve GDI funkcije. Povratna vrednost je identifikacija starog objekta iz tog DC-a koju bi trebalo da sacuvamo
da bi je kasnije vratili u DC.
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
Koristite RGB makro da dodjete do 32 bitne RGB vrednosti koju koristimo sa SetColorText i SetBkColor.
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
Zovite TextOut funkciju za nacrtate tekst na radnoj povrsini prozora. Teksi i njegova boja ce odgovarati
onome sto smo prethodno podesili.
invoke SelectObject,hdc, hfont
Kada zavrsimo sa fontom trebalo bi da vratimo stari font u DC. Tako treba postupiti sa svim objektima koje
stavljamo u DC.
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
char WPARAM 20h ; the character the program receives from keyboard
Ovo je promenljiva u koju cemo smestiti karakter primljen od tastature. Kako je karakter poslat u WPARAM
parametru Window procedure definisacemo promenljivu tipa WPARAM zbog jednostavnosti. Pocetna
vrednost je 20h tj. razmak jer kada se prozor pojavi po prvi put nema nikakvog unosa pa zelimo da
prikazemo samo "razmak"
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
Ovo je dodato window proceduri da bi obradjivala WM_CHAR poruku. Kod samo stavlja karakter u
promenljivu zvanu "char" i onda poziva InvalidateRect. InvalidateRect obelezava dati pravougaonik za
ponovo crtanje i tako primorava Windows da posalje WM_PAINT poruku window proceduri. Sintaksa izleda
ovako:
InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD
lpRect je pointer ka pravougaoniku na radnoj povrsini prozora koji zelimo da obelezimo za ponovno crtanje.
Ako je ovo NULL ceo prozor ce biti obelezen.
bErase govori Windowsu da treba da obrise pozadinu. Ako je ovo TRUE onda ce windows obrisati pozadinu
u pravougaoniku cim se pozove BeginPaint.
Znaci logika je ova: cuvamo sve informacije koje cemo koristiti za crtanje prozora i pozivamo WM_PAINT
poruku da bi nacrtali ono sto zelimo. Podrazumeva se da kod koji se nalazi pod WM_PAINT mora biti
spreman da obradi informacije koje su za njega pripremljene.
Mozemo da crtamo po prozoru i odmah po primljenoj WM_CHAR poruci koristeci GetDC/ReleaseDC par, sve
je u redu i tako ali ce biti zabavno kada ce prozor morati da se ponovo iscrta. Kako je kod za crtanje u
WM_CHAR poruci, window procedura nece biti u stanju da nacrta i nas karakter. Zakljucak je da je bolje
sav kod u vezi sa crtanjem ostaviti u WM_PAINT sekciji.
invoke TextOut,hdc,0,0,ADDR char,1
Kada je InvalidateRect pozvana, ona salje WM_PAINT poruku nasoj window proceduri tako da je kod iz
WM_PAINT sekcije izvrsen. On ce pozvati BeginPaint, doci do DC-a, zvati TextOut koji crta karakter na
radnoj povrsini, na koordinatama x=0, y=0 i eventualno pozvati EndPaint. Cak i kada prozor spustimo pa
ponovo vratimo na dekstop sve ce ostati ispravno jer ce se generisati WM_PAINT poruka koja ce ponovo
nacrtati poslednji uneti karakter.
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
Window procedura ceka levi klik. Kada primi WM_LBUTTONDOWN lParam sadrzi koordinate kursora na
radnoj povrsini i te koordinate zatim cuva u promenljivoj tipa POINT koja je definisana ovako:
POINT STRUCT
x dd ?
y dd ?
POINT ENDS
Onda podesava MouseClick flag na TRUE da bi obelezila da postoji barem jedan levi klik na radnu povrsinu.
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
Kako je x koordinata low word lParam parametra, a clanovi POINT strukture su 32 bita veliki mi moramo da
nulisemo high word eax-a pre no sto ga sacuvamo u hitpoint.x .
shr eax,16
mov hitpoint.y,eax
Posto je y koordinata hight word lParam parametra, moramo da je prvo stavimo u low word eax-a (ax) i da
nulisemo hight word istog pre nego sto ga sacuvamo u hitpoint.y . Ovo radimo pomeranjem bitova iz eax-a
za 16 mesta u desno.
Posto sacuvamo koordinate misa podesimo MouseClick na TRUE kako bi kod iz WM_PAINT odeljka znao da
treba da nacrta string. Onda zovemo InvalidateRect kako bi primorali Windows da nam posalje WM_PAINT
poruku.
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
Kod iz WM_PAINT sekcije mora da proveri da li je MouseClick TRUE. Na pocetku programa MouseClick mora
biti FALSE jer se do tada nije desio ni jedan klik i ne treba crtati nista na prozoru pre nego dodje do jednog.
Ako je doslo do klika onda se crta string na radnoj povrsini prozora pocevsi od koordinata misa. Pozivamo
lstrlen da bi dosli do duzine stringa koju cemo dati kao poslednji parametar TextOut funkcije.
Lekcija 8: Meni
U ovoj lekciji cemo nauciti kako da stavimo meni na nas prozor. Skinite primer 1 i primer 2 .
Teorija:
Meni je jedan od najvaznijih delova naseg prozora. On pretstavlja listu akcija koje nas program nudi
korisniku. Korisnik ne treba da cita prirucnik programa kako bi mogao da koristi program vec bi trebalo da
mu bude dovoljno da proceslja meni pa da zna sta program moze da uradi i da odmah krene da se igra sa
programom. Kako je meni alatka koja omogucuje korisniku brzi start sa radom on mora da postuje
odredjene standarde. Na primer prva dva pod menija bi trebalo da budu 'File' i 'Edit' a zadnji 'Help'. Sve
vase menije mozete dodati izmedju 'Edit' i 'Help' pod menija. Ako element menija pokrece dijalog onda
njegovo ime treba da se zavrsava sa tri tacke (...).
Meni je tip resursa. Postoji nekoliko tipova resurasa kao na primer dijalog, ikone, bitmap slike, itd. Resursi
su opisani u drugom fajlu koji uglavnom nosi ekstenziju .rc . Zatim se resursi kombinuju sa ostatkom
izvornog koda pri linkovanju pa je rezultat EXE sa kodom i resursima.
Resurs skriptove mozete pisati koristeci bilo koji tekst editor. Oni su sacinjeni od fraza koje opisuju izgled i
ostale osobine svakog pojedinacnog resursa. Ipak pisanje ovih skriptova je krajnje nepotrebno jer postoje
vizuelni editori kojima puno lakse pravimo resurs skripte. Ovakvi se editori uglavnom nalaze u paketima tipa
Visual C++, Borland C++ itd ali mozete izabrati i neki od besplatnih resors editora sto se mogu naci na
internetu.
Ovako se opisuje jedan meni u resursu:
MyMenu MENU
{
[menu list here]
}
C programeri ce videti da je ovo slicno definisanju strukture. MyMenu je ime menija praceno sa MENU
komandom i listom u { } zagradama. Pored ovih zagrada mozete koristiti i BEGIN / END par kao u Pascalu
ako vam se tako vise svidja.
Lista u meniju moze biti ili MENUITEM ili POPUP komanda.
MENUITEM komanda definise jednostavan element menija koji ne poziva nikakav pod meni. Sintaksa je
sledeca:
MENUITEM "&text", ID [,options]
Pocinje sa MENUITEM komandom pracenom tekstom koji zelimo da koristimo za taj meni. Obratite paznju
na & simbol. On cini karakter koji ga prati da bude podvucen. Posle teksta dolazi ID menija. ID je broj koji
ce biti koriscen za identifikaciju menija kada se primi window poruka u window proceduri i zato svaki ID
mora biti jedinstven.
Opcije su izborne. Set opcija je sledeci:
GRAYED - meni nije aktivan i ne pokrece WM_COMMAND poruku. Tekst je sive boje.
INACTIVE - meni nije aktivan i ne pokrece WM_COMMAND poruku. Tekst je normalno prikazan.
MENUBREAK - ovaj meni i svaki sledeci ce se pojaviti u novoj liniji.
HELP Ovaj i svaki sledeci meni je poravnan na desno.
Mozete koristiti jednu od gore navedenih opcija ili ih mozete kombinovati koristeci "or" operator. Paznja:
INACTIVE i GRAYED se ne mogu kombinovati.
POPUP komanda ima sledecu sintaksu:
POPUP "&text" [,options]
{
[menu list]
}
POPUP komanda definise element koji kada klinkemo otvara pod meni. Lista moze biti sacinjena od
MENUITEM ili POPUP elemenata. Pored ovi postoji i jos jedan tip: MENUITEM SEPARATOR koji prikazuje
samo jednu horizontalnu liniju koju mozemo koristiti za grupisanje pod menija.
Posto zavrsite sa skriptom treba da pripisete meni nekom prozoru.
Ovo mozete uraditi na dva nacina.
U lpszMenuName clanu WNDCLASSEX strkuturi. Ako imate meni nazvan "FirstMenu" mozete ga
pripisati prozoru ovako:
file:///D|/Download%20Free%20zona/Knjige%20Racunari%20staro/Win32asm/tut008.html[18.4.2010 3:08:06]
.DATA
MenuName db "FirstMenu",0
...........................
...........................
.CODE
...........................
mov wc.lpszMenuName, OFFSET MenuName
...........................
Kroz Menu Handle parametar CreateWindowEx funkcije:
.DATA
MenuName db "FirstMenu",0
hMenu HMENU ?
...........................
...........................
.CODE
...........................
invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu, eax
invoke CreateWindowEx,NULL,OFFSET ClsName,\
OFFSET Caption, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,\
NULL,\
hMenu,\
hInst,\
NULL\
...........................
Koja je razlika izmedju ova dva nacina?
Kada pripisete meni kroz WNDCLASSEX strukturu meni postaje default za sve prozore te klase.
Ako zelite da svaki stvoreni prozor iste klase ima razliciti meni onda morate izabrati drugi nacin. U tom
slucaju ce svaki prozor koji stvorimo sa CreateWindowEx i navedemo menu handle imati taj nas meni
umesto menija definisanog u WNDCLASSEX strukturi.
Sada cemo videti kako meni obavestava prozor u akcijama koje korisnik pokrece.
Kada korisnik izabere neki element menija, window procedura ce primiti WM_COMMAND poruku. Low word
wParam parametra sadrzi ID elementa.
Sada imamo sve potrebne informacije da stvorimo i koristimo menije. Ajmo da to i uradimo.
Primer:
Ovaj prvi primer pokazuje kako napraviti i koristiti meni ubacen kroz klasu prozora.
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.const
IDM_TEST equ 1 ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name here
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
Lekcija 9: Kontrole
U ovoj lekciji cemo se upoznati sa kontrolama koje su bitan element ulaza i izlaza informacija iz naseg
programa. Skinite primer ovde .
Teorija:
Windows nam pruza nekoliko predefinisanih klasa prozora koje mi mozemo koristiti u nasim programima.
Ugavnom ih koristimo kao komponente dijaloga tako da su te kontrole deca nasih dijaloga. Ove kontrole
same odgovaraju na svoje poruke, a zatim obavestavaju roditelja o promenama koje su se dogodile. One
su ogromna olaksica programerima i treba ih koristiti sto vise. U ovoj lekciji cu ih sve smestiti na obican
prozor da bi vam pokazao kako se mogu stvoriti i koristiti. Ove kontrole je bolje koristiti na dijalozima.
Primeri predefinisanih klasa prozora su dugme (BUTTON), liste (LISTBOX), polja za unos teksta (EDIT) i
druge.
Da bi stvorili kontrolu pozivamo CreateWindow ili CreateWindowEx. Nije potrebno registrovanje klase
prozora jer je ona vec registrovana. Parametar koji sadrzi ime klase MORA biti predefinisana klasa kontrole
koju zelimo da stvorimo. Ako zelite da stvorite dugme morate staviti "BUTTON" za ime klase. Drugi
parametri koje morate staviti su handle roditelja i ID kontrole. ID kontrole mora biti jedinstven jer se koristi
za razlikovanje jedne kontrole od drugih.
Posto je kontrola stvorena ona ce poslati poruku roditelju cim joj se stanje promeni. Uglavnom decu prozora
stvarate za vreme WM_CREATE poruke roditelja. Deca salju WM_COMMAND poruku prozoru sa svojim IDom u low word delu wParam parametra dok je kod poruke u hight word delu istog. Handle kontrole se
nalazi u lParam parametru. Svaka klasa ovih kontrola ima svoj skup kodova poruka. Detalje mozete naci u
Win32 API dokumentaciji.
Roditelj moze takodje slati komande svojoj deci koristeci SendMessage funkciju. SendMessage salje datu
poruku sa wParam i lParam parametrima poruke prozoru koji je odredjen sa handle parametrom
SendMessage funkcije. Ovo je veoma korisna funkcija jer moze slati poruke bilo kom prozoru ciji handle
imate.
Posto napravimo kontrole, roditelj mora paziti na WM_COMMAND poruke da bi bio u stanju da prati sta se
desava sa kontrolama.
Primer:
Napravicemo prozor koji sadrzi polje za unos teksta i dugme. Kada stisnete dugme pojavljuje se
MessageBox sa tekstom koji ste uneli u polje za tekst. Takodje postoji i meni sa 4 elementa:
1.
2.
3.
4.
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box
.const
ButtonID equ 1 ; The control ID of the button control
EditID equ 2 ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
Analizirajmo ovaj program.
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
or ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
Stvaramo kontrole za vreme WM_CREATE poruke naseg glavnog prozora. Pozivamo CreateWindowEx sa
ekstra stilom podesenim na WS_EX_CLIENTEDGE koji cini da ivice prozora izgledaju uronjene u roditelja.
Ime svake klase je predefinian i to je "EDIT" za polje sa tekstom i "BUTTON" za dugme. Zatim ubacimo stil
kontrola. Svaki tip kontrole ima svoj skup ovih stilova koji je sacinjen od nekoliko standardnih i nekoliko
ekstra stilova. Na primer dodatak standardnom setu stilova za "BUTTON" klasu su svi stilovi koji pocinju sa
"BS_" sto je skracenica za "button style". Stilovi za polje sa tekstom pocinju sa "ES_"" sto je skacenica za
"edit style". Ove stilove morate da potrazite u Win32 API dokumentaciji. ID kontrole stavljamo na mesto
identifikacije menija. Ovo je u redu jer kontrole nemaju nikakve menije.
Nakon stvaranja svake kontrole cuvamo handle svake od njih u promenljive radi kasnije upotrebe.
SetFocus zovemo da bi dali fokus polju za unos teksta tako da korisnik moze odmah krenuti sa kucanjem
teksta.
Sada dolazi uzbudljivi deo. Svaka kontrola salje obavestenja roditelju kroz WM_COMMAND.
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
Prisetite se da elementi menija takodje salju WM_COMMAND poruku da bi obavestili prozor o svom stanju.
Ako se pitate kako mozete razlikovati WM_COMMAND poruku prozora od WM_COMMAND poruke kontrole,
malo nize cete naici na odgovor.
type
Low word of wParam High word of wParam lParam
Menu Menu ID
0
0
Control Control ID
Notification code
Child Window Handle
Vidite da treba proveriti lParam parametar. Ako je nula onda je WM_COMMAND stigao od menija. Ne
mozete koristiti wParam za razlikovanje menija i kontrola zato sto identifikacija menija i identifikacija
kontrola mogu biti identicne, a kod poruke moze biti nula.
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
Mozete staviti tekst u polje za unos teksta koristeci SetWindowText, a mozete izbrisati sav tekst pozivom na
SetWindowText sa NULL parametrom. SetWindowText je funkcija opste primene. Mozete je koristiti da
menjate naziv prozora ili tekst ispisan na dugmetu.
Da bi dobili tekst iz polja koristite GetWindowText.
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
Gore prikazan kod se bavi akcijama koje treba izvrsiti kada korisnik stisne dugme. Prvo proverava low word
wParam parametra da vidi da li je ID, ID dugmeta. Ako jeste onda proverava hight word wParam parametra
da vidi da li je kod BN_CLICKED taj koji je poslat kada je dugme pritisnuto.
Interesantan deo dolazi kada smo sigurni da je kod poruke BN_CLICKED. Zelimo da dodjemo do teksta
polja za unos i prikazemo ga u MessageBox-u. Mozemo napraviti duplikat koda u IDM_GETTEXT sekciji
iznad ali to nema smisla. Ako nekako mozemo poslati WM_COMMAND poruku sa low word-om wParam
parametra koji sadrzi IDM_GETTEXT onda mozemo izbeci dupliranje koda i tako pojednostaviti nas program.
SendMessage je odgovor na ovu nasu potrebu. Ova funkcija salje bilo koju poruku sa bilo kojim
vrednostima parametara. Tako da umesto dupliranja koda saljemo WM_COMMAND poruku nasem prozoru
sa IDM_GETTEXT i 0. Ovo ce proizvesti isti rezultat kao i klik na "Get Text" element menija. Nasa window
procedura nece primetiti nikakvu razliku.
Koristite ovu tehniku sto cesce jer ce kod tako biti organizovaniji.
I na kraju, ne zaboravite TranslateMessage funkciju u glavnoj petlji. Kako korisnik mora ubaciti tekst u polje
sa tekstom, program mora prevesti unos sa tastature u citak tekst. Ako zaboravite ovu funkciju onda
korisnik nece nista moci da ukuca u polje sa tekstom.
Windows takodje daje i nekoliko API funkcija specificnih za odredjene kontrole. Primer ovakvih funkcija je
GetDlgItemText, CheckDlgButton itd. Ove funkcije su tu da programer ne bi non-stop morao da proveravao
u dokumentaciji sta sve wParam i lParam parametri poruke nose. Koristite ovakve funkcije sto vise i
pribegavajte SendDlgItemMessage funkciji samo kada ne postoji drugo resenje.
Windows salje poruke koje se ticu dijaloga dijalog proceduri koja ima sledeci format:
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
Dijalog procedura je veoma slicna window proceduri. Jedina razlika je u tome sto dijalog procedura vraca
TRUE/FALSE vrednosti umesto LRESULT. Winodws se bavi svim dijalog porukama u internoj window
proceduri dijaloga i takodje zove nasu dijalog proceduru da bi mi mogli da dodamo akcije. Ako nasa dijalog
procedura odgovara ne neku poruku onda treba da vrati TRUE, a u suprotnom FALSE. Dijalog procedura ne
treba da zove DefWindowProc zato sto nije prava window procedura.
Postoje dva nacina na koja mozemo koristiti dijaloge. Jedan je da oni budu glavni prozori nase aplikacije, a
drugi je da ih koristiko za unos podataka. U ovoj lekciji cemo se pozabaviti sa onim prvim.
"Koriscenje dijaloga kao glavnog prozora" se moze odraditi na dva nacina.
1. Jedan nacin je da koristite dijalog kao normalan prozor tako sto cete registrovati tip klase po uzoru na
dijalog sa RegisterClassEx pozivom. Ovako ce dijalog primati poruke kroz window proceduru datu u
lpfnWndProc clanu klase prozora, a ne kroz dijalog proceduru. Dobra strana ovoga je da ne morate
sami stvarati kontrole vec ce to Windows odratiti za vas i takodje to sto ce se Windows baviti
tastaturom (npr. Tab navigacija). Pored ovoga, mozete takodje sami odrediti kursor i ikonu prozora u
strukuturi klase.
2. Vas program samo stvara dijalog bez pravljenja bilo kakvog roditelja. Ovaj nas pristup lisava potrebe
za glavnom petljom jer ona postoji vec u Windowsu koji poruke salje dijalog proceduri. Cak ne morete
da registrujete ni klasu prozora.
Ovo ce biti dugacka lekcija u kojoj cemo se baviti sa oba pristupa.
Primeri:
dialog.asm
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Dialog.rc
#include "resource.h"
#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002
#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003
MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT,15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13,WS_GROUP
END
MyMenu
MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
Analiza:
Analizirajmo prvi primer.
Ovaj primer pokazuje kako se koristi dijalog za stvaranje klase prozora i kako se stvara prozor od te klase.
Ovo pojednostavljuje vas program zato sto ne morate da pravite kontrole sami.
Prvo cemo analizirati dijalog.
MyDialog DIALOG 10, 10, 205, 60
Definisite ime dijaloga, u ovom slucaju, "MyDialog" preceno sa komandom "DIALOG". Sledeca cetiri broja su
x, y, sirina i visina dijaloga u jedinicama dijaloga (ne u pikselima!).
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
Definisete stil dijaloga.
CAPTION "Our First Dialog Box"
Ovo je tekst koji ce biti u naslovu dijaloga.
CLASS "DLGCLASS"
Ovo je bitna linija. Ova CLASS komanda omogucava da koristimo dijalog za klasu. Odmah za tom
komandom dolazi naziv klase.
BEGIN
EDITTEXT IDC_EDIT,15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
Ovaj je blok definisao kontrole dijaloga. One su definisane izmedju BEGIN i END komandi. Evo kako izgleda
sintaksa:
control-type "text" ,controlID, x, y, width, height [,styles]
control-types su konstante resurs kompajlera i njih cete pronaci u prirucniku istog.
A sada cemo pogledati assembly kod. Zanimljiv deo je u strukturi klase prozora:
mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName
Ovaj bi clan bio ostavljen NULL ali ako zelimo da napravimo i registrujemo klasu koristeci dijalog za primer
onda moramo staviti DLGWINDOWEXTRA u taj clan. Ime klase mora biti identicno imenu koje smo dali u
skripti. Ostali clanovi su podeseni kao u slucaju obicnog prozora. Posto popunite strukturu mozete
registrovati klasu sa RegisterClassEx. Ovu funkciju smo zvali i kada smo registrovali klasu obicnog prozora.
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
Nakon registrovanja klase stvaramo dijalog. U ovom primeru stvaram modeless tip dijaloga koristeci
CreateDialogParam funkciju. Ova funkcija prima 5 parametara ali cemo mi popuniti samo prva dva:
identifikaciju programa (instance handle) i pointer ka imenu dijaloga. Napomena: drugi parametar nije
pointer ka imenu klase vec ka imenu dijaloga.
Sada imamo dijalog i njegove kontrole stvorene od strane Windows-a. Vasa procedura ce primiti
WM_CREATE poruku kao i obicno.
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
Posto je dijalog stvoren zelimo da damo fokus polju za unos teksta. Ako kod stavimo u WM_CREATE sekciju
procedure, GetDlgItem poziv nece uspeti zato sto tada kontrole jos uvek nisu stvorene. Jedini nacin da ovo
postignemo je da to uradimo nakon sto su i dijalog i sve kontrole stvorene. Zato stavljam ove dve linije
nakon UpdateWindow poziva. GetDlgItem funkcija uzima ID kontrole i vraca handle svog prozora. Evo kako
doci do handle-a ako znamo ID kontrole.
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
Program ulazi u glavnu petlju u kojoj prima poruke i pre nego sto ih prevede i prosledi proceduri zovemo
IsDialogMessage funkciju da bi dali priliku proceduri Windows-a da se pozabavi sa tastaturom umesto nas.
Ako ova funkcija vrati TRUE to znaci da je poruka namenjena dijalogu i da je obradjuje Windows-ova
procedura. Ovde vidimo jos jednu razliku ove i prethodne lekcije. Kada window procedura zeli da dodje do
teksta kontrole ona zove GetDlgItemText funkciju umesto GetWindowText. GetDlgItemText prima ID
kontrole umesto handle-a prozora kontrole. Ovo pojednostavljuje poziv u slucajevima kada koristite dijaloge.
Sada cemo da vidimo drugi pristup koriscenja dijaloga kao glavnog prozora. U ovom primeru cu napraviti
modal dijalog. Nece videti nikakvu glavnu petlju niti window proceduru jer te stvari nisu potrebne!
dialog.asm(part 2)
.386
.model flat,stdcall
option casemap:none
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start
dialog.rc(part 2)
#include "resource.h"
#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002
#define IDR_MENU1 3003
#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003
MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT,15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
Analiza sledi:
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
Definisemo DlgProc funkciju da bi mogli da koristimo ADDR operator u sledecoj liniji:
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
Ova linija zove DialogBoxParam funkciju koja prima 5 parametara: identifikaciju programa (instance
handle), ime dijaloga, handle roditelja, adresu dijalog procedure i ekstra parametar koji mozemo koristiti po
volji. Kako je DialogBoxParam stvorio modal dijalog ta se funkcija nece okoncati sve dok se ne unisti
dijalog.
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
Dijalog procedura, nasuprot window proceduri ne prima WM_CREATE poruku. Prva poruka koju ce dijalog
procedura primiti ce biti WM_INITDIALOG i u tu sekciju smestamo sav kod zaduzen za inicijalizaciju. Ako
koristite ovu poruku morate vratiti TRUE.
Interna procedura dijaloga ne salje WM_DESTROY poruku nasoj proceduri tako da ako zelimo da
reagujemo kada korisnik pritisne X da bi zatvorio prozor, moramo se pozabaviti sa WM_CLOSE porukom. U
nasem primeru saljemo WM_COMMAND poruku sa IDM_EXIT vrednoscu u wParam parametru. Ovo ima isti
efekat kao da je koristnik izabrao Exit iz menija. EndDialog je pozvan kao odgovor na IDM_EXIT.
Obrada podataka pod WM_COMMAND sekcijom ostaje ista.
Jedini nacin da unistite dijalog je da pozovete EndDialog funkciju. Ne pokusavajte da zovete
DestroyWindow! EndDialog ne unistava dijalog odmah. Ona samo javlja internoj proceduri dijaloga da treba
unistiti prozor ali zatim nasavlja sa sledecom instrukcijom.
Ajmo sada da pogledamo resurs fajl. Primetna promena je da smo umesto koriscenja teksta za ime menija
mi koristili numericku vrednost, IDR_MENU1. Ovo je neophodno ako zelite da zakacite meni dijalogu koji je
stvoren sa DialogBoxParam funkcijom. U resurs skripti morate dodati i komandu MENU pracenu sa ID meni
resursa.
Razlika dva primera iz ove lekcije koju mozete odmah primetiti je nedostatak ikonice u drugom primeru.
Ikonu mozete podesiti slanjem WM_SETICON poruke u WM_INITDIALOG sekcije.
CrLf db 0Dh,0Ah,0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
Popunjavamo elemente ofn strukture.
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535
EditID equ 1 ; ID of the edit control
.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; Handle to the edit control
hFile HANDLE ? ; File handle
hMemory HANDLE ? ;handle to the allocated memory block
pMemory DWORD ? ;pointer to the allocated memory block
SizeReadWrite DWORD ? ; number of bytes actually read or write
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW mov eax,msg.wParam
ret
WinMain endp
WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;==================================================
; Initialize the members of OPENFILENAME structure
;==================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
U WM_CREATE sekciji stvaramo polje za unos teksta. Parametri za poziciju i velicinu kontrole su sve nule jer
cemo kasnije podesiti velicinu tako da pokriva ceo prozor.
Takodje ne zovemo ShowWindow da bi prikazali polje za unos teksta zato sto smo u stil stavili WS_VISIBLE
sto automatski prikazuje prozore. Ovo smo isto uradili i za glavni prozor.
;=================================================
; Initialize the members of OPENFILENAME structure
;=================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
Posle stvaranja kontrole spremamo ofn strukturu. Posto cemo istu strukturu koristiti i za cuvanje i otvaranje
fajlova popunicemo samo zajednicke elemente koje ce koristiti GetOpenFileName i GetSaveFileName.
WM_CREATE sekcija je odlicno mesto za vrsenje raznih inicijalizacija.
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
Kada se menja velicina naseg glavnog prozora primamo WM_SIZE poruku. Istu poruku cemo primiti i kada
se prozor pojavi po prvi put. Da bi mogao da prima takve poruke prozor mora imati CS_VREDRAW i
CS_HREDRAW stil. Ovu priliku koristimo da promenimo velicinu naseg polja za unos teksta koje ce zauzeti
celu radnu povrsinu glavnog prozora. Potrebne informacije primamo kroz lparam. Hight word lParam
parametra sadrzi visinu, a low word sirinu radne povrsine prozora. Zatim ove informacije koristimo
pozivajuci MoveWindow funkciju koja pored toga sto menja poziciju prozora takodje menja i njegovu
velicinu.
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
Kada korisnik izabere File/Open meni ispunicemo Flag element ofn strukture i pozvacemo GetOpenFileName
kako bi prikazali dijalog za otvaranje fajlova.
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
Posto korisnik izabere fajl za otvaranje pozivamo CreateFile da bi otvorili fajl. Dajemo funkciji do znanja da
treba da pokusa ta otvori fajl za citanje i pisanje. Kada se fajl otvori funkcija vraca handle otvorenog fajla
koji cuvamo u globalnoj promenljivoj za kasniju upotrebu. Evo i sintakse ove funkcije:
CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD
dwDesiredAccess Odredjuje koje ce operacije biti vrsene nad fajlom.
0 - otvara fajl za ucitavanje atributa. Potrebno je da imate pravo da pisete ili citate informacije iz fajla.
GENERIC_READ - otavara fajl za citanje.
GENERIC_WRITE - otvara fajl za pisanje.
dwShareMode Odredjuje koje ce operacije neki drugi proces moci da obavlja nad fajlom koji smo mi
otvorili.
0 - fajl se nece deliti sa drugim procesima.
FILE_SHARE_READ - dopusta drugim procesima da citaju informacije iz otvorenog fajla.
FILE_SHARE_WRITE - dopusta drugim procesima da pisu u otvoreni fajl.
lpSecurityAttributes Nije bitno na Windows 95.
dwCreationDistribution Odredjuje akciju koju ce CreateFile obaviti nad fajlom ako on postoji ili ne postoji.
CREATE_NEW - stvara novi fajl. Funkcija se nece okoncati uspesno ako fajl vec postoji.
CREATE_ALWAYS - stvara novi fajl. Ako postoji fajl funkcija ce pisati preko njega.
OPEN_EXISTING - otvara fajl. Funkcija se nece okoncati uspesno ako fajl ne postoji.
OPEN_ALWAYS - otvara fajl ako postoji. Ako ne postoji funkcija ce ga stvoriti kao da smo odredili
CREATE_NEW akciju.
TRUNCATE_EXISTING Otvara fajl. Kada ga otvori brise sve iz njega. Proces sto ovo zeli da uradi mora
imati barem GENERIC_WRITE pristup. Funkcija se nece okoncati uspesno ako fajl ne postoji.
dwFlagsAndAttributes Odredjuje atribute fajla.
FILE_ATTRIBUTE_ARCHIVE - fajl je arhiva. Aplikacije koriste ovaj atribut za obelezavanje fajlova za
brisanje ili za sigurnosne kopije.
FILE_ATTRIBUTE_COMPRESSED - fajl ili direktorijum je komresovan. Za fajl ovo znaci da su sve
informacije u fajlu kompresovane. Za direktorijum ovo znaci da je kompresija standard za novo
stvorene fajlove.
FILE_ATTRIBUTE_NORMAL - fajl nema nikakve dodatne atribute. Ovaj atribut je vazeci jedino ako se
koristi samostalno.
FILE_ATTRIBUTE_HIDDEN - fajl je skriven i nece biti ukljucen u obicnu listu direktorijuma.
FILE_ATTRIBUTE_READONLY - fajl je samo za citanje. Aplikacije mogu da citaju informacije iz ovog
fajla ali ga ne mogu menjati ili brisati.
FILE_ATTRIBUTE_SYSTEM - fajl je deo operativnog sistema ili ga ekskluzivno koristi operativni sistem.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
Kada se fajl otvori alociramo blok memorije koji cemo koristiti sa ReadFile i WriteFile funkcijama. Kao tip
bloka stavljamo GMEM_MOVEABLE sto ce dopustiti Windowsu da pomera blok po memoriji kako bi je
stedeo sto vise. GMEM_ZEROINIT govori GlobalAlloc funkciji da memoriju ispuni nulama.
Kada se GlobalAlloc uspesno okonca eax ce biti handle memorijskog bloka. Njega cemo proslediti
GlobalLock funkciji koja ce nam dati pointer ka memoriji.
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
Kada je memorija spremna za upotrebu pozivamo ReadFile koji ce da popuni nasu memoriju sa
informacijama iz fajla. Kada je fajl otvoren ili stvoren, fajl pointer je 0. U ovom slucaju ce znaci citanje fajla
poceti od prvog bajta. Prvi parametar ReadFile funkcije je handle fajla, drugi je broj bajta koji treba da
procita, zatim, adresa promenjive koja ce primiti broj bajta procitanih iz fajla.
Posto popunimo memoriju sa informacijama, stavicemo ih u polje za unos teksta tako sto cemo poslati toj
kontroli WM_SETTEXT poruku sa lParam parametrom koji ce biti pointer ka memoriji sa informacijama.
Posle ovog poziva ce polje za unos teksta biti ispunjeno tekstom iz fajla.
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
Sada vise ne moramo da drzimo fajl otvoren jer zelimo da modifikovan tekst sacuvamo u drugi fajl. Fajl
zatvaramo sa CloseHandle pozivom ciji je parametar handle fajla. Zatim odkljucavamo i oslobadjamo
memoriju. U stvari, ne morate da oslobadjate memoriju sada jer je mozete ponovo koristiti kasnije za
cuvanje fajla. U primeru je oslobadjam cisto radi demonstracije.
invoke SetFocus,hwndEdit
Kada se otvari dijalog za otvaranje fajla, fokus ce otici sa polja na unos teksta na ovaj dijalog tako da po
njegovom zatvaranju treba da vratimo fokus polju za unos teksta.
Ovo je kraj operacije citanja fajla. Sada korisnik moze da menja tekst. Kada bude zeleo da sacuva promene
ici ce na File/Save koji ce prikazati dijalog za cuvanje fajlova. Pravljenje ovog dijaloga nije puno drugacije
od pravljenja dijaloga za otvaranje fajlova. U stvari je jedina razlika u funkciji koju pozivamo. Vecinu
elemenata ofn strukture mozete koristiti ponovo bez ikakvih promena. Jedine promene su izvrsene u Flag
elementu.
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
U nasem slucaju zelimo da stvorimo novi fajl tako da OFN_FILEMUSTEXIST i OFN_PATHMUSTEXIST
izostavljamo jer u suprotnom fajl nece moci da se stvori ako fajl sa takvim imenom vec ne postoji u
direktorijumu u kome se nalazimo.
dwCreationDistribution parametar CreateFile funkcije se mora promeniti u CREATE_NEW jer zelimo da
stvorimo novi fajl.
Ostatak koda je slican onome sto smo koristili za otvaranje fajla izuzev sledeceg:
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
Saljemo WM_GETTEXT poruku polju za unos teksta da bi kopirali informacije u nas blok memorije. Vrednost
u eax-u ce biti velicina informacija u bajtima. Posto se informacije kopiraju u memorijski blok, kopiramo ga u
novi fajl.
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME & lt;>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle to the memory mapped file, must be
; initialized with 0 because we also use it as
; a flag in WM_DESTROY section too
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle to the source file
hFileWrite HANDLE ? ; Handle to the output file
hMenu HANDLE ?
Memory DWORD ? ; pointer to the data in the source file
SizeWritten DWORD ? ; number of bytes actually written by WriteFile
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ; Obtain the menu handle
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
end start
Analiza:
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
Kada korisnik izabere fajl za otvaranje putem standardnog dijaloga mi pozivamo CreateFile da bi ga otvorili.
Zapazite da smo fajl otvorili koristeci GENERIC_READ sto ga otvara samo za citanje, i sa nulom za
dwShareMode zato sto necemo da neki drugi proces modifikuje fajl dok se mi bavimo sa njim.
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
Zatim zovemo CreateFileMapping za stvaranje objekta. Evo i sintakse ove funkcije:
CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD
Treba da znate da CreateFileMapping ne mora da mapira ceo fajl. Mozete koristiti ovu funkciju da mapirate
samo deo fajla. Broj bajtova koji ce se mapirati u memoriji su odredjeni sa dwMaximumSizeHight i
dwMaximumSizeLow parametrima. Ako date broj veci od stvarne velicine fajla, fajl ce biti uvecan do te nove
granice. Ako zelite da mapirate ceo fajl i da ne bude nikakvog viska pozovite funkciju i stavite nule na
mestu ova dva parametra.
Mozete koristiti NULL kod lpFileMappingAtributes parametra i dobicete standardan set atributa.
flProtect definise zastitu za mapirani fajl. U nasem primeru koristimo PAGE_READONLY sto nam dozvoljava
samo da citamo iz mapiranog fajla. Vazno je napomenuti da ovaj atribut ne sme da dolazi u sukob sa
slicnim atributom iz CreateFile funkcije inace CreateFileMapping nece uspeti.
lpName pointer ka imenu mapiranog fajla. Ako zelite da delite ovaj fajl sa drugim procesima onda mu
morate dodeliti neko ime. U nasem primeru samo nas jedan proces treba da koristi fajl tako da smo
ignorisali ovaj parametar.
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
Ako je CreateFileMapping okoncan uspesno, menjamo naslov prozora u ime otvorenog fajla. Ime fajla sa
njegovom punom adresom se nalazi u bufferu. Posto zelimo da prikazemo samo ime moramo dodati
nFileOffset element OPENFILENAME strukture adresi buffera.
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
Zbog predostroznosti necemo otvarati vise od jednog fajla u isto vreme tako da iskljucujemo Open meni, a
ukljucujemo Save. EnableMenuItem je funkcija koju koristimo u ovu svrhu.
Nakon ovoga cekamo da korisnik izabere File/Save As meni ili da ugasi nas program. Ako korisnik zeli da
ugasi program prvo moramo da zatvorimo mapiranje fajla i sam fajl kao u prilozenom:
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
Videli smo da kada windowprocedura prima WM_DESTROY poruku proveravamo vrednost hMapFile
promenljive da vidimo da li je nula ili ne. Ako nije nula zovemo CloseMapFile:
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
CloseMapFile zatvara mapirani fajl i sam fajl tako da nece biti gubitaka resurasa kada se program napokon
okonca.
Ako korisnik izabere da sacuva tekst u drugi fajl program prikazuje standardni dijalog. Posto korisnik ukuca
ime novog fajla, fajl se stvara CreateFile pozivom.
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
Cim je fajl stvoren zovemo MapViewOfFile da bi mapirali fajl u memoriju. Evo sintakse:
Proces handle i proces ID su dve razlicite stvari. Proces ID je jedinstveni identifikacioni broj procesa. Proces
handle je vrednost koju je Windows nama dao za koriscenje drugih API funkcija koje traze ovu vrednost.
Proces handle se ne moze koristiti za identifikaciju procesa zato sto nije jedinstven.
Nakon CreateProcess poziva novi proces je stvoren i CreateProcess funkcija se okoncava. Mozete proveriti
da li je proces jos uvek aktivan pozivajuci GetExitCodeProcess funkciju. Sintaksa funkcije sledi.
GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD
Ako je ovaj poziv uspesan, lpExitCode ce sadrzati status procesa. Ako je vrednost STILL_ACTIVE to znaci da
se proces jos uvek nije okoncao.
Mozete naterati proces da se ugasi pozivajuci TerminateProcess funkciju. Ona ima sledecu sintaksu:
TerminateProcess proto hProcess:DWORD, uExitCode:DWORD
Mozete odrediti i zeljeni izlazni kod procesa i to moze biti bilo koja vrednost. TerminateProcess nije cist
nacin prekidanja procesa zato sto DLL-ovi zakaceni za proces nece dobiti obavestenje da je proces
okoncan.
Primer:
Primer koji sledi ce stvoriti novi proces kada korisnik bude izabrao "create process" iz menija. Probace da
pokrene "msgbox.exe". Ako korisnik bude zeleo da prekine proces moze da izabere "terminate process" iz
menija. Program ce prvo proveriti da li je proces trenutno aktivan, pa ako jeste onda ce pozvati
TerminateProcess kako bi ga prekinuo.
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3
.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; contains the process exitcode status from GetExitCodeProcess call.
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analiza:
Program stvara glavni prozor i dolazi do meni handle za kasniju upotrebu. Zatim ceka da korisnik izabere
komandu iz menija. Kada izabere "Process" iz glavnog menija obradjujemo WM_INITPOPUP poruku i
modifikujemo meni pre nego sto je on prikazan.
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
Zasto se osvrcemo na ovo poruku? Zato sto zelimo da pripremimo meni pre nego sto se on prikaze
korisniku. U nasem primeru, ako novi proces jos nije pokrenut zelimo da "start process" bude slobodan, a
"terminate process" zakljucan; obratno ako je proces vec aktivan.
Prvo proveravamo da li je proces jos uvek aktivan pozivajuci GetExitCodeProcess funkciju, sa handle-om
procesa koji smo dobili pozivajuci CreateProcess. Ako GetExitCodeProcess vrati FALSE proces nije pokrenut
pa zakljucavamo "terminate process" element menija. Ako GetExitCodeProcess vrati TRUE znamo da je
proces bio porkenut ali moramo da proverimo i ExitCode. Ako je STILL_ACTIVE onda je proces aktivan pa
treba da zakljucamo "start process" jer ne zelimo da pokrenemo vise od jednog procesa.
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
Kada korisnik izabere "start process" prvo proveravamo da li je hProcess element PROCESS_INFORMATION
strukture zatvoren. Ako je ovo prvi put da se pokrece proces, hProcess ce biti 0 jer smo celu strukturu
stavili u .data sekciju. Ako ova vrednost nije 0 to znaci da je proces zavrsen, a mi jos uvek nismo zatvorili
handle tako da cemo to sada da ucinimo.
Mozemo pozvati GetStartupInfo da popunimo startupinfo strukturu koju cemo proslediti CreateProcess
funkciji, koja ce pokrenuti novi proces. Nisam proverio povratnu vrednost CreateProcess funkcije zato sto bi
to ucinilo primer kompleksnijim. U pravom programu ovo treba proveriti. Odmah nakon CreateProcess
zatvaramo handle glavnog threada. Zatvaranje handle ne znaci da okoncavamo thread vec samo da ne
zelimo da koristimo tu vrednost u nasem programu. Ako je ne zatvorimo izazvace gubljenje memorije.
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
Kada korisnik izabere "terminate process" proveravamo da li je proces jos uvek aktivan pozivajuci
GetExitCodeProcess. Ako je jos uvek aktivan, prekinucemo proces sa TerminateProcess i zatvoricemo i
handle jer nam vise nije potreban.
Kada se funkcija threada okonca sa ret instrukcijom, Windows poziva ExitThread funkciju za taj thread. I
sami mozete pozvati ExitThread iz vase funkcije threada ali nema posebne potrebe za tim.
Kada se thread okonca on vraca neku vrednost do koje mozete doci GetExitCodeThread funkcijom.
Ako zelite da ugasite jedan thread iz nekog drugog threada, treba da koristite TerminateThread. Ovu
funkciju treba da koristite samo u ekstremnim slucajevima zato sto ona momentalno gasi trhead ne dajuci
mu priliku da oslobodi resurse.
Sada prelazimo na komunikaciju medju threadovima.
Postoje tri nacina:
1. Koriscenje globalnih promenljivih
2. Windows poruke
3. Event (dogadjaj)
Threadovi dele resurse procesa ukljucujuci i globalne promenljive tako da mogu koristiti njih za
komunikaciju. Problem je sto je sinhronizacija veoma bitna. Na primer ako dva threada koriste istu
strukturu od 10 elemenata sta ce se desiti kada Windows iznenada prekine rad jednog threada u sred
popunjavanja strukture, i pocne sa izvrsavanjem drugog? Drugi thread ce biti ostavljen sa ne popunjenom
strukturom! Programi sa vise threadova su tezi za odrzavanje i debugovanje, a problemi kao ovaj se tesko
lociranju.
Takodje mozete koristiti Windows poruke za komunikaciju threadova. Ako su svi threadovi sa korisnickim
interfejsom onda nema problema i ovaj vid kominikacije se moze slobodno koristiti u oba smera. Sve sto
treba da ucinite je da definisete jednu ili vise vasih Windows poruka koje ce imati neko znacenje za vase
threadove. Poruke se definisu tako sto se koristi WM_USER poruka kao osnova. Na primer:
WM_MYCUSTOMMSG equ WM_USER+100h
Windows nece koristiti vrednosti od WM_USER pa na gore za svoje poruke tako da ih vi mozete koristiti za
sta god su vam potrebne.
Ako neki of vasih threadova ima korisnicki interfejs a drugi ne onda ovaj metod ne mozete koristiti za
dvosmernu komunikaciju jer radni thread (onaj bez interfejsa) ne moze da prima poruke ali zato mozete
koristiti ovu kombinaciju:ovo:
Thread sa korisnickim interfejsom ------> globalne promenljive ----> Thread radnik
Thread radnik ------> Windows poruke ----> Thread sa korisnickim interfejsom
Ovaj metod cemo koristiti u nasem primeru.
Poslednji nacin komuniciranja su event objekti (dogadjaji). Na event mozete gledati kao na signalnu
zastavicu. Ako je event ugasen onda thread spava i ne dobija deo procesorskog vremena. Kada se event
aktivira onda i Windows bude thread i on pocinje da obavlja zadatu mu radnju.
Primer:
Ako vec niste, skinite primer i pokrenite thread1.exe. Stisnite "Savage Calculation" u meniju. Ovo ce narediti
programu da izvrsi "add eax, eax" 600,000,000 puta. Za ovo vreme ne mozete raditi nista sa glavnim
prozorom: ne mozete ga pomeriti, koristiti menije itd. Kada se racunanje zavrsi pojavice se MessageBox
nakon cega ce prozor poceti normalno da funkcionise.
Da bi izbegli ovakvo ponasanje prozora pomericemo racunsku operaciju u proceduru koja ce da se izvrsi
kao drugi thread. Ovim cemo dopustiti glavnom threadu da se bavi korisnickim interfejsom. Mozete videti
da cak i ako prozor nesto sporije odgovara neko inace, on ipak odgovara.
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
ADDR ThreadID
invoke CloseHandle,eax
Ova funkcija stvara thread sto ce paralelno sa primarnim threadom izvrsavati proceduru sa imenom
ThreadProc. Nakon uspesnog poziva, CreateThread se zavrsava i ThreadProc odmah pocinje da radi. Kako
ne koristimo handle threada treba da ga zatvorimo inace ce biti curenja memorije. Zatvaranje ovog handle
ne gasi i thread vec samo znaci da vise ne mozemo koristiti taj handle.
ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP
Kao sto vidite ThreadProc obavlja racunanje koje zahteva neko vreme, a kada zavrsi salje WM_FINISH
poruku glavnom prozoru. WM_FINISH je poruka koju smo definisali ovako:
WM_FINISH equ WM_USER+100h
Nema potrebe da dodate 100h na WM_USER ali je tako sigurnije.
WM_FINISH poruka nesto znaci samo u nasem programu. Kada glavni prozor primi WM_FINISH poruku on
odgovara prikazivanjem MessageBox-a koji kaze da je racunanje obavljeno.
Mozete stvoriti nekoliko threadova tako sto cete par puta stisnuti "Create Thread".
U ovom primeru komunikacija je jednosmerna. Samo radni thread moze javiti kraj glavnom prozoru tj.
primarnom threadu. Ako zelite da glavni thread nesto javi radnom to mozete uciniti ovako: Dodajte
meni sa imenom "Kill Thread" ili slicno.
Potrebna je globalna promenljiva koja ce se kroistiti kao signalna zastavica. Ako je TRUE znaci da
thread mora da stane, FALSE da moze da nastavi sa radom.
Izmenite ThreadProc tako da u loop-u proverava vrednost ove promenjive.
Kada korisnik izabere "Kill Thread" iz menija, glavni thread ce staviti TRUE u globalnu promenljivu. Kada
radni thread vidi da je ova promenljiva TRUE, izacice iz loop-a i time zavrsiti.
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
.endif
Ovo zaustavlja thread i skace na poziv ka WaitForSingleObject. Zapazimo da ne moramo rucno da stavimo
event objekat u ne signalno stanje zato sto smo za bManualReset parametar CreteEvent funkcije koristili
FALSE.
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;--------------------------------------------------------------------------------------------------; This is a dummy function
; It does nothing. I put it here to show where you can insert functions into
; a DLL.
;---------------------------------------------------------------------------------------------------TestFunction proc
ret
TestFunction endp
End DllEntry
;------------------------------------------------------------------------------------; DLLSkeleton.def
;------------------------------------------------------------------------------------LIBRARY DLLSkeleton
EXPORTS TestFunction
Svaki DLL mora imati ulaznu tacku. Ovo je funkcija koju Windows poziva svaki put kada:
Se DLL prvi put ucitava
Odstranjuje iz memorije
Thread (nit egzekucije) je stvorena u istom procesu
Thread (nit egzekucije) je unistena u istom procesu
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
Ova funkcija moze da nosi bilo koje ime sve dok na kraju imate odgovarajuci END <ime funkcije >. Ova
funkcija prima tri parametra od kojih su samo prva dva bitna.
hInstDLL je handle modula. Ovo nije isto sto i "instance handle" procesa. Posto kasnije necete tako lako
doci do ovoga, sacuvajte ga za slucaj da vam zatreba.
reason moze imati jednu od sledece cetiri vrednosti:
DLL_PROCESS_ATTACH DLL prima ovu vrednost kada je po prvi put ubacen u prostor procesa. Ovu
priliku mozete iskoristiti za inicijalizaciju.
DLL_PROCESS_DETACH DLL prima ovu vrednost kada se izbacuje iz prostora procesa. Mozete
iskoristiti ovu priliku za, na primer oslobadjanje alocirane memorije itd.
DLL_THREAD_ATTACH DLL prima ovu vrednost kada proces stvara novi thread (nit egzekucije).
DLL_THREAD_DETACH DLL prima ovu vrednost kada je thread iz procesa unisten.
Vracate TRUE u eax ako zelite da DLL nastavi da radi. Ako vratite FALSE, DLL nece biti ucitan. Na primer,
ako vasa inicijalizacija ukljucuje alociranje neke memorije i to joj ne podje za rukom, ova funkcija moze da
izadje sa FALSE kako bi oznacila da DLL nece raditi.
Svoje funkcije mozete stavljati u DLL posle ili pre ove glavne funkcije (takodje: ulazne funkcije ili tacke). Ali
ako zelite da budu vidljive drugim programima morate staviti njihova imena u eksportnu listu DLL-a (fajl sa
ekstenzijom .def).
Da bi se kompilirao, DLLu je potreban ovaj (.def) fajl. Sada cemo baciti pogled na njega.
LIBRARY DLLSkeleton
EXPORTS TestFunction
Prva linija treba da pocinje sa LIBRARY praceno imenom DLL-a (na primer moje_rutine.dll).
Nakon toga ide EXPORT komanda. Ona govori linkeru koje funkcije treba eksportovati, tj koje ce biti
moguce pozvati iz drugih programa. U primeru, zelimo da drugi moduli imaju mogucnost pozivanja
TestFunction tako da smo njeno ime stavili nakon komande EXPORT.
Druga promena su komande koje trebe dodati komandnoj liniji linkera a to su /DLL i
/DEF:<naziv_vaseg_def_fajla>. Evo primera:
link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj
Komandna linija assemblera ostaje ista, /c /coff /Cp. Nakon sto linkujete program dobicete .dll i .lib fajlove.
.lib je biblioteka koju koroistite pri vezivanju ovog DLL-a sa ostalim programima koji ce ga koristiti. Vidite da
je proces isti kao pri vezivanju sa kernel32.dll ili user32.dll.
Sada cu vam pokazati kako da koristite LoadLibrary funkciju kako bi ucitali DLL.
;--------------------------------------------------------------------------------------------; UseDLL.asm
;---------------------------------------------------------------------------------------------.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0
.data?
hLib dd ? ; the handle of the library (DLL)
TestHelloAddr dd ? ; the address of the TestHello function
.code
start:
invoke LoadLibrary,addr LibName
;--------------------------------------------------------------------------------------------------------; Call LoadLibrary with the name of the desired DLL. If the call is successful
; it will return the handle to the library (DLL). If not, it will return NULL
; You can pass the library handle to GetProcAddress or any function that requires
; a library handle as a parameter.
;-----------------------------------------------------------------------------------------------------------.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
Toolbar
Tooltip
Statusbar
Treeview
Listview
Animation
Header
Hot-key
Progressbar
RICHEDIT
Richedit
msctls_updown32 Up-down
SysTabControl32 Tab
Property sheets, property pages i image list kontrole imaju svoje, specificne funkcije za kreiranje. Drag list kontrole je u
stvari listbox tako da nema sopstvenu klasu. Gore navedena imena klasa su proverena u resurs skriptama koje je stvorio
Visual C++ editor i razlikuju se od naziva koje mozete naci u Borlandovoj Win32 API referenci i u Charles Petzoldovom
"Programming Windows 95". Gore navedena lista je tacna.
Ove uobicajene kontrole mogu da koriste i uobicajene stilove kao sto su na primer WS_CHILD i drugi. Pored ovih imaju i
nesto sopstvenih stilova kao sto su TVS_XXXXX za tree view kontrolu, LVS_XXXXX za list view kontrlu itd. Win32 API
referenca je sto se ovoga tice vas najbolji prijatelj.
Sada kada znamo kako da kreiramo kontrolu mozemo da se pozabavimo sa komunikacijom izmedju kontrola i njihovog
roditelja. Za razliku od ostalih kontrola koje smo videli ranije, ove uobicajene kontrole ne koriste WM_COMMAND za
komunikaciju za roditeljem vec koriste WM_NOTIFY kada se god dogodi nesto interesantno. Roditelj moze da kontrolise
svoju decu slanjem poruka. Postoji puno novih poruka koje odgovaraju ovim kontrolama. Konsultujte Win32 API referencu
za vise informacija.
U narednom primeru cemo ispitati progress bar i status kontrolu.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDC_PROGRESS equ 1 ; control IDs
IDC_STATUS equ 2
IDC_TIMER equ 3
.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; the classname of the progress bar
Message db "Finished!",0
TimerID dd 0
.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
file:///D|/Download%20Free%20zona/Knjige%20Racunari%20staro/Win32asm/tut018.html[18.4.2010 3:07:59]