Powrót do strony wyboru lekcji

Programowanie okien 2

Tworzenie okien za pomocą funkcji CreateWindowExA i edytora zasobów



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.

Przykład 1

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.

Nie można wyświetlić obrazu
Rys.1. Interfejs programu.

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

Pobierz kod

Przykład 2

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

Pobierz kod

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.

Nie można wyświetlić obrazu
Rys.2. Interfejs programu .

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

Pobierz kod

Ćwiczenie 4

Program z ćwiczenia 3 należy zrealizować przy pomocy edytora zasobów. Przykładowe wywołanie przedstawiono na rysunku poniżej-Rys.3.

Pobierz szablon

Nie można wyświetlić obrazu
Rys.3. Przykładowe wywołanie programu.

<Poprzednia lekcjaKolejna lekcja>