Programowanie systemu Windows wymaga przyswojenia sobie trzech istotnych elementów. Wszystkie elementy systemu z punktu jego działania są oknami. Windows traktuje wszystkie te elementy w sposób podobny, przy czym niektóre okna mogą być tzw. oknami potomnymi innych okien. Aby okno, które tworzymy definiowało określony wygląd, należy skorzystać z odpowiednich stałych.
Tworzyć okna w systemie Windows można na 2 różne sposoby:
a) Nie korzystając z edytora zasobów i wykorzystując funkcję API
CreateWindowExA
- tworzącą okno z odpowiednimi parametrami podawanymi podczas wywołania. Sposób ten ukazano w Przykładzie 1.
b) Wykorzystując edytor zasobów, który tworzy plik o rozszerzeniu .rc, który następnie kompilujemy przy pomocy programu rc.exe do postaci pliku o rozszerzeniu .res. Sposób ten ukazano w Przykładzie 2. Etap kompilacji w przypadku wykorzystywania plików zasobów został opisany tutaj.
Przy pomocy funkcji CreateWindowExA tworzone były już programy w lekcji poprzedniej, jednak dla porównania do programów tworzonych przy użyciu edytora zasobów zostanie omówiony jeszcze jeden przykład takiego programu. Tworzymy program zawierający okno wraz z 3 kontrolkami: listą, polem edycyjnym i przyciskiem. Po wciśnięciu przycisku zawartość pola edycyjnego jest kopiowana do listy. Na początku definiujemy standardowo typ instrukcji procesora, określamy model pamięci jako płaski z wywołaniem procedur typu STDCALL. Dołączamy także odpowiednie biblioteki (user32.lib i kernel32.lib ). Następnie definiujemy komunikaty, które będą obsługiwane przez nasze okno wraz z kontrolkami:
WM_DESTROY EQU 2
WM_CREATE EQU 1
WM_COMMAND EQU 111h
WM_GETTEXT EQU 0Dh
LB_ADDSTRING EQU 180h
Wszystkie stałe wykorzystywane tak jak to miało miejsce w przypadku programów tworzonych na konsole do budowania okien można znaleźć w pliku windows.inc, w katalogu include. Aby zarejestrować jakiekolwiek zmiany zachodzące w naszym oknie, należy zdefiniować odpowiednie komunikaty obsługi okna. Komunikat WM_DESTROY
to stała definiująca zniszczenie okna, analogicznie WM_CREATE
oznacza tworzenie nowego okna. WM_COMMAND
informuje o zdarzeniu związanym z jakąś kontrolką w oknie, np. z wciśnięciem przycisku. Komunikat WM_GETTEXT
pobiera tekst z kontrolki do zdefiniowanego przez nas bufora a LB_ADDSTRING
pozwala na dodanie łańcucha znaków z bufora do listy.
Następnie definiujemy wszystkie stałe związane z wyglądem i właściwościami okna oraz kontrolek:
CS_VREDRAW EQU 1h
CS_HREDRAW EQU 2h
CS_GLOBALCLASS EQU 4000h
WS_TABSTOP EQU 10000h
WS_SYSMENU EQU 80000h
WS_THICKFRAME EQU 40000h
WS_MAXIMIZEBOX EQU 10000h
WS_OVERLAPPEDWINDOW EQU WS_TABSTOP + WS_SYSMENU
STYLE EQU CS_HREDRAW OR CS_VREDRAW OR CS_GLOBALCLASS OR WS_MAXIMIZEBOX
BS_DEFPUSHBUTTON EQU 1h
WS_VISIBLE EQU 10000000h
WS_CHILD EQU 40000000h
WS_BORDER EQU 800000h
WS_VSCROLL EQU 200000h
STYLBTN EQU WS_CHILD + BS_DEFPUSHBUTTON + WS_VISIBLE + WS_TABSTOP
STYLLST EQU WS_THICKFRAME + WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP + WS_VSCROLL
STYLEDT EQU WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP
IDI_APPLICATION EQU 32512
IDC_ARROW EQU 32512
SW_SHOWNORMAL EQU 1
Stała
CS_VREDRAW
przerysowuje okno, jeżeli zostanie zmieniona jego wysokość.
Podobnie stała CS_HREDRAW
przerysowuje okno jeżeli zostanie zmieniona jego szerokość. WS_SYSMENU
definiuje standardowy górny pasek okna który posiada ikonę i przycisk zamykający-krzyżyk. WS_THICKFRAME
określa grubą ramkę naokoło okna a WS_MAXIMIZEBOX
dodaje opcję maksymalizacji do okna. Następnie definiujemy styl okna WS_OVERLAPPEDWINDOW
jako sumę logiczną wcześniej zdefiniowanych stałych. Jej wynik to 10000h+80000h=90000h
. Podobnie definiujemy stałą STYLE
, którą później wykorzystamy przy rejestracji okna jako jego podstawowy styl.
Stała BS_DEFPUSHBUTTON
to styl przycisku. WS_VISIBLE
informuje o tym iż okno/kontrolka ma być widoczna po uruchomieniu aplikacji. WS_CHILD
definiuje ,kontrolkę jako kontrolkę podrzędną np. wobec okna na którym jest umieszczona. WS_BORDER
określa ramkę,
WS_VSCROLL
dodaje pionowy suwak. STYLBTN,STYLLST,STYLEDT
są sumą logiczną stałych określających styl odpowiednio dla przycisku, listy i pola edycyjnego. IDI_APPLICATION
to standardowy identyfikator ikony a IDC_ARROW
identyfikator kursora. SW_SHOWNORMAL
określa normalny tryb wyświetlania okna.
Następnie można zdeklarować funkcje API ,które użyjemy w naszym programie:
EXTERN SetFocus@4:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN CreateWindowExA@48:NEAR
EXTERN DefWindowProcA@16:NEAR
EXTERN DispatchMessageA@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetMessageA@16:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN LoadCursorA@8:NEAR
EXTERN LoadIconA@8:NEAR
EXTERN PostQuitMessage@4:NEAR
EXTERN RegisterClassA@4:NEAR
EXTERN ShowWindow@8:NEAR
EXTERN TranslateMessage@4:NEAR
EXTERN UpdateWindow@4:NEAR
Funkcja SetFocus@4
aktywuje pole edycyjne. SendMessageA@16
pozwala na przesłanie bądź pobranie wiadomości w zależności od pobranego parametru. CreateWindowExA@48
tworzy kontrolkę okna bądź same okno w programie. DefWindowProcA@16
odbiera nieobsłużone zdarzenia we wnętrzu uruchomionego okna. DispatchMessageA@4
powoduje wysłanie komunikatu do okna do którego jest przeznaczony. ExitProcess@4
kończy proces aplikacji i powoduje powrót do systemu operacyjnego. GetMessageA@16
pobiera komunikaty od systemu i umieszcza je w strukturze którą zdefiniujemy poniżej.
GetModuleHandleA@4
zwraca uchwyt dla danego modułu, w naszej aplikacji zwróci uchwyt modułu głównego. LoadCursorA@8
wczytuje ikonę kursora i zwraca uchwyt kursora. LoadIconA@8
wczytuje ikonę okna. PostQuitMessage@4
wysyła do aplikacji komunikat WM_QUIT, dzięki czemu możliwe jest jej zakończenie. RegisterClassA@4
rejestruje klasę okna.
ShowWindow@8
wyświetla utworzone i zarejestrowane wcześniej okno.
TranslateMessage@4
tłumaczy komunikaty w pętli komunikatów.
UpdateWindow@4
uaktualnia obszar klienta.
Poniżej definiujemy struktury używane w naszym programie-strukturę komunikatu i strukturę okna. Wszystkie pola struktur są cztero-bajtowe:
MSGSTRUCT STRUC
MSHWND DD ? ;uchwyt komunikatu
MSMESSAGE DD ? ;nazwa komunikatu
MSWPARAM DD ? ;dodatkowa informacja komunikatu
MSWLARAM DD ? ;dodatkowa informacja komunikatu
MSTIME DD ? ;określa czas w który wiadomość była wysłana
MSPT DD ? ;określa pozycję kursora
MSGSTRUCT ENDS
WNDCLASS STRUC
CLSSTYLE DD ? ;styl okna
CLWNDPROC DD ? ;adres procedury okna
CLSCBCLSEX DD ? ;dodatkowe bajty na miejsce dla struktury w pamięci
CLSCBWNDEX DD ? ;dodatkowe bajty na miejsce dla egzemplarza w pamięci
CLSHINST DD ? ;uchwyt danego egzemplarza modułu
CLSHICON DD ? ;uchwyt ikony z funkcji LoadIcon
CLSHCURSOR DD ? ;uchwyt kursora z funkcji LoadCursor
CLBKGROUND DD ? ;kolor tła okna
CLMENUNAME DD ? ;uchwyt menu
CLNAME DD ? ;nazwa klasy okna
WNDCLASS ENDS
W sekcji niezainicjalizowanych danych umieszczamy nasze struktury i uchwyt okna które utworzymy:
.DATA?
MSG MSGSTRUCT <?>
WC WNDCLASS <?>
NEWHWND DD ? ;uchwyt okna zdefiniowanego za pomocą funkcji CreateWindowExA
Dane jakie użyjemy w programie:
.DATA
HINST DD 0 ;uchwyt aplikacji
TITLENAME DB 'Okno1', 0 ;nazwa okna wyświetlona na pasku
CLASSNAME DB 'KlasaOkna', 0 ;nazwa klasy okna
BTNNAME DB 'Kopiuj', 0 ;nazwy kontrolek
LSTNAME DB ' ', 0
EDTNAME DB ' ', 0
CLSBUTN DB 'BUTTON', 0 ;nazwy klas kontrolek dla CreateWindowExA@48
CLSLIST DB 'LISTBOX', 0
CLSEDT DB 'EDIT', 0
HWNDBTN DWORD 0 ;uchwyty kontrolek
HWNDLST DWORD 0
HWNDEDT DWORD 0
BUFF DB 100 DUP (0) ;bufor na znaki
Po ustaleniu danych programu, przechodzimy do sekcji kodu:
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
Na początku pobieramy uchwyt aplikacji za pomocą funkcji GetModuleHandleA@4
. Wynik zwracany jest do rejestru EAX
, kopiujemy go do zmiennej [HINST]
. Następnie wypełniamy strukturę okna odpowiednimi stałymi, ładując także ikonę i kursor, ostatecznie rejestrujemy klasę okna funkcją
RegisterClassA@4
podając jej jako parametr OFFSET
struktury WC
:
MOV [WC.CLSSTYLE],STYLE
MOV [WC.CLWNDPROC],OFFSET WNDPROC
MOV [WC.CLSCBCLSEX],0
MOV [WC.CLSCBWNDEX],0
MOV EAX,[HINST]
MOV [WC.CLSHINST],EAX
PUSH IDI_APPLICATION
PUSH 0
CALL LoadIconA@8
MOV [WC.CLSHICON],EAX
PUSH IDC_ARROW
PUSH 0
CALL LoadCursorA@8
MOV [WC.CLSHCURSOR],EAX
MOV [WC.CLBKGROUND],16
MOV DWORD PTR [WC.CLMENUNAME],0
MOV DWORD PTR [WC.CLNAME],OFFSET CLASSNAME
PUSH OFFSET WC
CALL RegisterClassA@4
Po rejestracji okna, można je już utworzyć funkcją CreateWindowExA@48
, podając
odpowiednie parametry:
PUSH 0;wskaźnik na dowolne dane zawarte w oknie tuż po utworzeniu
PUSH [HINST] ;uchwyt aplikacji
PUSH 0;uchwyt do paska menu
PUSH 0 ;uchwyt do okna nadrzędnego względem okna które mamy zamiar utworzyć
PUSH 400 ;wysokość okna
PUSH 500 ;szerokość okna
PUSH 100 ;współrzędna pionowa okna
PUSH 100 ;współrzędna pozioma okna
PUSH WS_OVERLAPPEDWINDOW;styl okna
PUSH OFFSET TITLENAME;wskaźnik na tytuł okna
PUSH OFFSET CLASSNAME;wskaźnik do zarejestrowanej nazwy klasy okna
PUSH 0;rozszerzony styl okna
CALL CreateWindowExA@48
CMP EAX,0;czy błąd?
JZ BLAD
MOV [NEWHWND],EAX;uchwyt okna
Po utworzeniu okna sprawdzamy czy rejestr EAX
nie zawiera wartości 0, jeśli tak to podczas tworzenia okna wystąpił błąd i przechodzimy do sekcji obsługi błędu a z tamtąd na koniec programu. Po poprawnym utworzeniu wyświetlamy okno:
PUSH SW_SHOWNORMAL
PUSH [NEWHWND]
CALL ShowWindow@8
PUSH [NEWHWND]
CALL UpdateWindow@4
Jak już wcześniej było wspomniane, aby nasza aplikacja mogła poprawnie działać na wskutek zdarzeń wywołanych w oknie przez użytkownika (np. kliknięcie przycisku, ruch myszą w obrębie okna), musi zapewnić właściwą obsługę komunikatów. Stąd należy zdefiniować pętle, która te komunikaty będzie rejestrować i obsługiwać:
MSG_LOOP:
PUSH 0
PUSH 0
PUSH 0
PUSH OFFSET MSG;wskaźnik na strukturę komunikatu
CALL GetMessageA@16
CMP EAX,0;czy koniec?
JE KONIEC
PUSH OFFSET MSG
CALL TranslateMessage@4 ;tłumaczenie
PUSH OFFSET MSG
CALL DispatchMessageA@4 ;wysłanie
JMP MSG_LOOP
KONIEC:
PUSH [MSG.MSWPARAM]
CALL ExitProcess@4
BLAD:
JMP
KONIEC
Wszystkie operacje na oknie, wykonwane są przez procedurę okna o następującej postaci w WinAPI
:
LRESULT CALLBACK WindowProc(
HWND hwnd, // uchwyt okna
UINT uMsg, // komunikat
WPARAM wParam, //1 parametr komunikatu
LPARAM lParam //2 parametr komunikatu
);
W programie zdefiniowano ją w taki sposób aby na parametry wskazywał rejestr EBP, pozostałe rejestry umieszczamy na stosie aby nie modyfikować ich wartości podczas operacji procedury WNDPROC
:
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
W procedurze należy sprawdzić parametr dotyczący komunikatu pod adresem [EBP+0CH]
i w zależności od niego wykonać skok do odpowiedniej sekcji obsługi komunikatu:
CMP DWORD PTR [EBP + 0CH],WM_DESTROY;komunikat związany ze zniszczeniem okna
JE WMDESTROY
CMP DWORD PTR [EBP + 0CH],WM_CREATE;tworzenie kontrolek w oknie
JE WMCREATE
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;zdarzenie związane z kontrolką
JE WMCOMMAND
JMP DEFWNDPROC;nieobsłużone komunikaty kierujemy do DefWindowProcA@16
Etykieta WMCREATE
wskazuje na miejsce obsługi komunikatu, w którym
tworzone są kontrolki okna: lista, przycisk i pole edycyjne. Wykorzystujemy w tym celu również funkcję CreateWindowExA@48
. Podając jednak odpowiednie dla danej kontrolki parametry:
WMCREATE:
;Przycisk:
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 20
PUSH 60
PUSH 150
PUSH 200
PUSH STYLBTN
PUSH OFFSET BTNNAME
PUSH OFFSET CLSBUTN
PUSH 0
CALL CreateWindowExA@48
MOV HWNDBTN,EAX
;Pole edycyjne:
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 20
PUSH 100
PUSH 150
PUSH 10
PUSH STYLEDT
PUSH OFFSET EDTNAME
PUSH OFFSET CLSEDT
PUSH 0
CALL CreateWindowExA@48
MOV HWNDEDT,EAX
PUSH HWNDEDT;aktywacja pola
CALL SetFocus@4
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 90
PUSH 150
PUSH 150
PUSH 340
PUSH STYLLST
PUSH OFFSET LSTNAME
PUSH OFFSET CLSLIST
PUSH 0
CALL CreateWindowExA@48
MOV HWNDLST,EAX
XOR EAX,EAX
JMP FINISH
Po zakończeniu sekcji w której tworzono kontrolki wykonujemy skok na koniec procedury w celu ominięcia pozostałych sekcji - JMP FINISH
. Etykieta WMCOMMAND
wskazuje na miejsce obsługi komunikatu, w którym
obsługujemy zdarzenia związane z kontrolkami w oknie, w tym miejscu zdefiniowano instrukcje związane z główną funkcją programu:
WMCOMMAND:
MOV EAX,HWNDBTN
CMP DWORD PTR [EBP + 14H],EAX ;Czy LPARAM to uchwyt przycisku?
JNE FINISH
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH HWNDEDT
CALL SendMessageA@16
PUSH OFFSET BUFF
PUSH 0
PUSH LB_ADDSTRING
PUSH HWNDLST
CALL SendMessageA@16
XOR EAX,EAX
JMP FINISH
Na początku sprawdzono czy przycisk został kliknięty - wtedy parametr LPARAM
zawiera uchwyt przycisku. Następnie użyto funkcji SendMessageA@16
, gdzie jako parametry podano OFFSET
na bufor, ilość miejsca w bajtach na znaki- 100
, parametr WM_GETTEXT
oznaczający iż należy pobrać znaki do bufora oraz uchwyt pola edycyjnego - HWNDEDT
. Następnie ponownie użyto funkcji SendMessageA@16
, tym razem jednak jako parametr komunikatu podano LB_ADDSTRING
nakazujący dodać ciąg znaków w buforze do listy, a jako uchwyt podano uchwyt listy HWNDLST
. Odbieraniem nieobsłużonych komunikatów zajmuje się funkcja DefWindowProcA@16
zdefiniowana w sekcji DEFWNDPROC
:
DEFWNDPROC:
PUSH DWORD PTR [EBP + 14H]
PUSH DWORD PTR [EBP + 10H]
PUSH DWORD PTR [EBP + 0CH]
PUSH DWORD PTR [EBP + 08H]
CALL DefWindowProcA@16
JMP FINISH
Ostatnia z sekcji to WMDESTROY
, w której również zdefiniowano funkcję wysyłającą komunikat WM_QUIT
kończący aplikację i niszczący okno:
WMDESTROY:
PUSH 0
CALL PostQuitMessage@4
XOR EAX,EAX
FINISH:
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
Program po uruchomieniu przedstawiono na Rys.1.
Kod całego programu przedstawiono poniżej:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
WM_DESTROY EQU 2
WM_CREATE EQU 1
WM_COMMAND EQU 111h
WM_GETTEXT EQU 0Dh
LB_ADDSTRING EQU 180h
CS_VREDRAW EQU 1h
CS_HREDRAW EQU 2h
CS_GLOBALCLASS EQU 4000h
WS_TABSTOP EQU 10000h
WS_SYSMENU EQU 80000h
WS_THICKFRAME EQU 40000h
WS_MAXIMIZEBOX EQU 10000h
WS_OVERLAPPEDWINDOW EQU WS_TABSTOP + WS_SYSMENU
STYLE EQU CS_HREDRAW OR CS_VREDRAW OR CS_GLOBALCLASS OR WS_MAXIMIZEBOX
BS_DEFPUSHBUTTON EQU 1h
WS_VISIBLE EQU 10000000h
WS_CHILD EQU 40000000h
WS_BORDER EQU 800000h
WS_VSCROLL EQU 200000h
STYLBTN EQU WS_CHILD + BS_DEFPUSHBUTTON + WS_VISIBLE + WS_TABSTOP
STYLLST EQU WS_THICKFRAME + WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP + WS_VSCROLL
STYLEDT EQU WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP
IDI_APPLICATION EQU 32512
IDC_ARROW EQU 32512
SW_SHOWNORMAL EQU 1
EXTERN SetFocus@4:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN CreateWindowExA@48:NEAR
EXTERN DefWindowProcA@16:NEAR
EXTERN DispatchMessageA@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetMessageA@16:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN LoadCursorA@8:NEAR
EXTERN LoadIconA@8:NEAR
EXTERN PostQuitMessage@4:NEAR
EXTERN RegisterClassA@4:NEAR
EXTERN ShowWindow@8:NEAR
EXTERN TranslateMessage@4:NEAR
EXTERN UpdateWindow@4:NEAR
MSGSTRUCT STRUC
MSHWND DD ? ;uchwyt komunikatu
MSMESSAGE DD ? ;nazwa komunikatu
MSWPARAM DD ? ;dodatkowa informacja komunikatu
MSWLARAM DD ? ;dodatkowa informacja komunikatu
MSTIME DD ? ;określa czas w który wiadomość była wysłana
MSPT DD ? ;określa pozycję kursora
MSGSTRUCT ENDS
WNDCLASS STRUC
CLSSTYLE DD ? ;styl okna
CLWNDPROC DD ? ;adres procedury okna
CLSCBCLSEX DD ? ;dodatkowe bajty na miejsce dla struktury w pamięci
CLSCBWNDEX DD ? ;dodatkowe bajty na miejsce dla egzemplarza w pamięci
CLSHINST DD ? ;uchwyt danego egzemplarza modułu
CLSHICON DD ? ;uchwyt ikony z funkcji LoadIcon
CLSHCURSOR DD ? ;uchwyt kursora z funkcji LoadCursor
CLBKGROUND DD ? ;kolor tła okna
CLMENUNAME DD ? ;uchwyt menu
CLNAME DD ? ;nazwa klasy okna
WNDCLASS ENDS
.DATA?
MSG MSGSTRUCT <?>
WC WNDCLASS <?>
NEWHWND DD ? ;uchwyt okna zdefiniowanego za pomocą funkcji CreateWindowExA
.DATA
HINST DD 0 ;uchwyt aplikacji
TITLENAME DB 'Okno1', 0 ;nazwa okna wyświetlona na pasku
CLASSNAME DB 'KlasaOkna', 0 ;nazwa klasy okna
BTNNAME DB 'Kopiuj', 0 ;nazwy kontrolek
LSTNAME DB ' ', 0
EDTNAME DB ' ', 0
CLSBUTN DB 'BUTTON', 0 ;nazwy klas kontrolek dla CreateWindowExA@48
CLSLIST DB 'LISTBOX', 0
CLSEDT DB 'EDIT', 0
HWNDBTN DWORD 0 ;uchwyty kontrolek
HWNDLST DWORD 0
HWNDEDT DWORD 0
BUFF DB 100 DUP (0) ;bufor na znaki
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
MOV [WC.CLSSTYLE],STYLE
MOV [WC.CLWNDPROC],OFFSET WNDPROC
MOV [WC.CLSCBCLSEX],0
MOV [WC.CLSCBWNDEX],0
MOV EAX,[HINST]
MOV [WC.CLSHINST],EAX
PUSH IDI_APPLICATION
PUSH 0
CALL LoadIconA@8
MOV [WC.CLSHICON],EAX
PUSH IDC_ARROW
PUSH 0
CALL LoadCursorA@8
MOV [WC.CLSHCURSOR],EAX
MOV [WC.CLBKGROUND],16
MOV DWORD PTR [WC.CLMENUNAME],0
MOV DWORD PTR [WC.CLNAME],OFFSET CLASSNAME
PUSH OFFSET WC
CALL RegisterClassA@4
PUSH 0;wskaźnik na dowolne dane zawarte w oknie tuż po utworzeniu
PUSH [HINST] ;uchwyt aplikacji
PUSH 0;uchwyt do paska menu
PUSH 0 ;uchwyt do okna nadrzędnego względem okna które mamy zamiar stworzyć
PUSH 400 ;wysokość okna
PUSH 500 ;szerokość okna
PUSH 100 ;współrzędna pionowa okna
PUSH 100 ;współrzędna pozioma okna
PUSH WS_OVERLAPPEDWINDOW ;styl okna
PUSH OFFSET TITLENAME ;wskaźnik na tytuł okna
PUSH OFFSET CLASSNAME ;wskaźnik do zarejestrowanej nazwy klasy okna
PUSH 0 ;rozszerzony styl okna
CALL CreateWindowExA@48
CMP EAX,0;czy błąd?
JZ BLAD
MOV [NEWHWND],EAX;uchwyt okna
PUSH SW_SHOWNORMAL
PUSH [NEWHWND]
CALL ShowWindow@8
PUSH [NEWHWND]
CALL UpdateWindow@4
MSG_LOOP:
PUSH 0
PUSH 0
PUSH 0
PUSH OFFSET MSG;wskaźnik na strukturę komunikatu
CALL GetMessageA@16
CMP EAX,0;czy koniec?
JE KONIEC
PUSH OFFSET MSG
CALL TranslateMessage@4 ;tłumaczenie
PUSH OFFSET MSG
CALL DispatchMessageA@4 ;wysłanie
JMP MSG_LOOP
KONIEC:
PUSH [MSG.MSWPARAM]
CALL ExitProcess@4
BLAD:
JMP
KONIEC
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_DESTROY ;komunikat związany ze zniszczeniem okna
JE WMDESTROY
CMP DWORD PTR [EBP + 0CH],WM_CREATE ;tworzenie kontrolek w oknie
JE WMCREATE
CMP DWORD PTR [EBP + 0CH],WM_COMMAND ;zdarzenie związane z kontrolką
JE WMCOMMAND
JMP DEFWNDPROC ;nieobsłużone komunikaty kierujemy do DefWindowProcA@16
WMCREATE:
;Przycisk:
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 20
PUSH 60
PUSH 150
PUSH 200
PUSH STYLBTN
PUSH OFFSET BTNNAME
PUSH OFFSET CLSBUTN
PUSH 0
CALL CreateWindowExA@48
MOV HWNDBTN,EAX
;Pole edycyjne:
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 20
PUSH 100
PUSH 150
PUSH 10
PUSH STYLEDT
PUSH OFFSET EDTNAME
PUSH OFFSET CLSEDT
PUSH 0
CALL CreateWindowExA@48
MOV HWNDEDT,EAX
PUSH HWNDEDT;aktywacja pola
CALL SetFocus@4
;Lista:
PUSH 0
PUSH [HINST]
PUSH 0
PUSH DWORD PTR [EBP + 08H];uchwyt okna
PUSH 90
PUSH 150
PUSH 150
PUSH 340
PUSH STYLLST
PUSH OFFSET LSTNAME
PUSH OFFSET CLSLIST
PUSH 0
CALL CreateWindowExA@48
MOV HWNDLST,EAX
XOR EAX,EAX
JMP FINISH
WMCOMMAND:
MOV EAX,HWNDBTN
CMP DWORD PTR [EBP + 14H],EAX ;Czy LPARAM to uchwyt przycisku?
JNE FINISH
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH HWNDEDT
CALL SendMessageA@16
PUSH OFFSET BUFF
PUSH 0
PUSH LB_ADDSTRING
PUSH HWNDLST
CALL SendMessageA@16
XOR EAX,EAX
JMP FINISH
DEFWNDPROC:
PUSH DWORD PTR [EBP + 14H]
PUSH DWORD PTR [EBP + 10H]
PUSH DWORD PTR [EBP + 0CH]
PUSH DWORD PTR [EBP + 08H]
CALL DefWindowProcA@16
JMP FINISH
WMDESTROY:
PUSH 0
CALL PostQuitMessage@4
XOR EAX,EAX
FINISH:
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
W programie także znajdują się 3 kontrolki: lista, pole edycyjne i przycisk. Po wciśnięciu przycisku, tekst z pola edycyjnego jest kopiowany do listy. Jednak zamiast definiować okno i kontrolki w programie, posłużono się edytorem zasobów. Sposób tworzenia pliku zasobów został opisany tutaj. Gotowy plik zasobów naszego programu wygląda następująco:
#include "..\include\resource.h"
#define IDC_BTN1 101
#define IDC_EDT1 102
#define IDC_LST1 103
IDD_DLG1 DIALOGEX 10,10,313,223
CAPTION "Okno1"
FONT 8,"MS Sans Serif", 0, 0, 0
STYLE WS_VISIBLE|WS_CAPTION|WS_SYSMENU|WS_MAXIMIZEBOX
BEGIN
CONTROL "Kopiuj",IDC_BTN1,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP|
BS_DEFPUSHBUTTON
,120,90,42,15
CONTROL "",IDC_EDT1,"Edit",WS_CHILD|WS_VISIBLE|WS_BORDER|WS_TABSTOP,6,90,57,12
CONTROL "",IDC_LST1,"ListBox",WS_CHILD|WS_VISIBLE|WS_BORDER|WS_VSCROLL|
WS_SIZEBOX|WS_TABSTOP,213,90,94,57
END
Na samej górze pliku dołączony jest plik resource.h
zawierający wszystkie niezbędne stałe jakie należy dołączyć do każdego pliku zasobów. Następnie po dyrektywach #define
są zdefiniowane kolejno tzw. identyfikatory kontrolek w dialogu: przycisku, pola edycyjnego i listy w celu późniejszej ich identyfikacji w dialogu. Następnie zdefiniowany jest sam dialog o nazwie IDD_DLG1
. Kolejne parametry po słowie DIALOGEX
oznaczają: współrzędną x, współrzędną y, szerokość i wysokość okna. Następnie określona jest czcionka zastosowana w dialogu po słowie kluczowym FONT
. Styl określony jest po słowie STYLE
jako suma logiczna ustalonych stałych w edytorze zasobów. Następnie w sekcji BEGIN END
zawarta jest informacja na temat umieszczonych kontrolek w dialogu. Każda taka kontrolka zdefiniowana jest w sposób następujący: na początku mamy słowo kluczowe CONTROL
,
następnie zdefiniowana jest właściwość CAPTION, identyfikator kontrolki, klasa kontrolki definiująca jej rodzaj, styl jako suma logiczna stałych, współrzędna x, współrzędna y, szerokość i wysokość. Należy jednak pamiętać iż w edytorze ResEd wartości dotyczące współrzędnych oraz wysokości i szerokości jest podawana w jednostkach DLU's (Dialog Units) a nie pikselach jak miało to miejsce w programie w przykładzie 1.
Plik programu korzystającego z zasobów różni się od tego, który jest przedstawiony w przykładzie 1.
Główna różnica pomiędzy zwykłymi oknami a oknami tworzonymi na bazie dialogów jest taka, iż w przypadku tworzenia normalnego okna wszystkie jego właściwości definiowane są właściwościami definicji klasy używanej do tworzenia okna i reakcjami procedury okna na nadchodzące komunikaty. Natomiast w czasie tworzenia dialogu wszystkie jego właściwości są pobierane z zasobów.
Na samym początku obok stałych, należy także zadeklarować identyfikatory kontrolek, które będą używane w programie. Ich deklaracja wygląda następująco:
IDC_BTN1 EQU 101
IDC_EDT1 EQU 102
IDC_LST1 EQU 103
Następnie stałe jakie zostaną użyte-jest ich znacznie mniej niż w przypadku programu w przykładzie 1, gdyż większość jest już umieszczona w pliku .rc, a w programie należy umieścić jedynie stałe dotyczące obsługiwanych komunikatów:
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
LB_ADDSTRING EQU 180h
WM_INITDIALOG
i WM_CLOSE
są analogiczne do
WM_CREATE
i WM_DESTROY
użytych w przykładzie 1. Odpowiednio inicjują i niszczą dialog. Komunikat WM_SETICON
oznacza ustawienie ikony. Pozostałe komunikaty zostały już opisane w przykładzie 1. Po komunikatach można zadeklarować procedury użyte w programie:
EXTERN ExitProcess@4:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN LoadIconA@8:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
Nowościami są tu procedury:
DialogBoxParamA@20
, SendDlgItemMessageA@20
i EndDialog@8
. DialogBoxParamA@20
tworzy okienko dialogowe. Jej wywołanie z odpowiednimi parametrami zostało opisane poniżej. Funkcja SendDlgItemMessageA@20
jest funkcją służącą do przesyłania komunikatów w dialogach, jako jeden z parametrów przyjmuje identyfikator odpowiedniej kontrolki. Dane jakie użyto w programie:
.DATA
HINST DD 0 ;uchwyt aplikacji
BUFF DB 100 DUP (0) ;bufor na znaki
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
W danych należy zdefiniować nazwę dialogu z plików zasobów jako ciąg znaków- PA, którą następnie należy podać w funkcji DialogBoxParamA@20
. Sekcja kodu przedstawia się następująco:
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
Na początku standardowo pobierany uchwyt aplikacji, następnie wywołujemy funkcję DialogBoxParam
o następującej postaci w WinAPI:
int DialogBoxParamA(
HINSTANCE hInstance, // uchwyt aplikacji
LPCTSTR lpTemplateName, // nazwa dialogu
HWND hWndParent, // uchwyt okna nadrzędnego
DLGPROC lpDialogFunc, //wskaźnik na procedurę dialogu
LPARAM dwInitParam // wartość inicjalizacyjna
);
Następnie sprawdzamy rejestr EAX
i uzależniamy od niego koniec aplikacji, w przypadku zwrócenia innej wartości niż -1 przez funkcję DialogBoxParam
, należy zauważyć, iż nie definiujemy tutaj pętli pobierania komunikatów jak to miało miejsce w programie w przykładzie 1. Funkcja WNDPROC
użyta w celu przeprowadzenia operacji na dialogu ma podobną definicję do funkcji obsługi zwykłych okien. Wewnątrz jednak, należy posłużyć się innymi funkcjami WinAPI.
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
Tak jak to miało miejsce w przykładzie 1, należy sprawdzić rodzaj komunikatu jaki dotarł do okna. Za zakończenie okna odpowiada komunikat WM_CLOSE
. Wywoływana jest w tym celu funkcja EndDialog@8
:
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
Należy także sprawdzić inicjalizacje dialogu, określa to stała WM_INITDIALOG
. W przypadku inicjalizacji wczytujemy ikonę:
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_INITDIALOG;komunikat związany ze inicjalizacją okna
JNE OMININICJACJE
PUSH 0
PUSH [HINST]
CALL LoadIconA@8
PUSH EAX
PUSH 0
PUSH WM_SETICON
PUSH DWORD PTR [EBP+ 08H]
CALL SendMessageA@16
Główne instrukcje programu zostały zawarte w sekcji OMININICJACJE
:
OMININICJACJE:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
CMP DWORD PTR [EBP + 10H],IDC_BTN1;Czy WPARAM to identyfikator przycisku?
JNE FINISH
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
PUSH 0
PUSH LB_ADDSTRING
PUSH IDC_LST1
PUSH DWORD PTR [EBP+ 08H]
CALL SendDlgItemMessageA@20
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
Wywołano w niej 2 razy funkcję SendDlgItemMessageA@20
, której definicja WinAPI
wygląda następująco:
LONG SendDlgItemMessage(
HWND hDlg, //uchwyt dialogu
int nIDDlgItem, // identyfikator kontrolki
UINT Msg, //wiadomość do wysłania
WPARAM wParam, // pierwszy parametr wiadomości
LPARAM lParam // drugi paramter wiadomości
);
W przypadku 1 wywołania SendDlgItemMessageA@20
pobrano znaki do bufora, a w 2 wczytano je z bufora do listy, podając jako parametr Msg
- odpowiedni rodzaj komunikatu na daną operację. Uruchomiony program przedstawiony jest na Rys.2.
Całość programu przedstawiona jest na poniższym listingu:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
IDC_BTN1 EQU 101
IDC_EDT1 EQU 102
IDC_LST1 EQU 103
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
LB_ADDSTRING EQU 180h
EXTERN ExitProcess@4:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN LoadIconA@8:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
.DATA
HINST DD 0 ;uchwyt aplikacji
BUFF DB 100 DUP (0) ;bufor na znaki
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_INITDIALOG;komunikat związany ze inicjalizacją okna
JNE OMININICJACJE
PUSH 0
PUSH [HINST]
CALL LoadIconA@8
PUSH EAX
PUSH 0
PUSH WM_SETICON
PUSH DWORD PTR [EBP+ 08H]
CALL SendMessageA@16
OMININICJACJE:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
CMP DWORD PTR [EBP + 10H],IDC_BTN1;Czy WPARAM to identyfikator przycisku?
JNE FINISH
PUSH OFFSET BUFF
PUSH 100
PUSH WM_GETTEXT
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SendDlgItemMessageA@20
PUSH OFFSET BUFF
PUSH 0
PUSH LB_ADDSTRING
PUSH IDC_LST1
PUSH DWORD PTR [EBP+ 08H]
CALL SendDlgItemMessageA@20
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
Program z ćwiczenia 3 należy zrealizować przy pomocy edytora zasobów. Przykładowe wywołanie przedstawiono na rysunku poniżej-Rys.3.