Rozdział III - Proste Okno


W tym rozdziale zbudujemy program dla Windows wyświetlający w pełni funkcjonalne okno na pulpicie.

 

 

Pobierz plik z przykładem {z tego archiwum}.

 

Teoria

Programy dla Windows oparte są mocno na funkcjach API (Application Programming Interface - Interfejs Programowy Aplikacji) w swoich GUI (Graphic User Interface - Interfejs Graficzny dla Użytkownika). Podejście takie daje korzyści zarówno dla użytkowników jak i programistów. Użytkownicy nie muszą się uczyć sposobów obsługi GUI przy każdym nowym programie, ponieważ wszystkie programy Windows posiadają podobny interfejs. Programiści mają kody obsługi GUI, przetestowane i gotowe do użytku. Wadą dla programistów jest zwiększona złożoność programowania. W celu stworzenia i korzystania z dowolnego obiektu GUI takiego jak okna, menu lub ikony programiści muszą się stosować do ścisłych zasad. Lecz można to obejść stosując programowanie modułowe lub obiektowe (tak jest np. w Delphi i Borland C++ Builder, gdzie wiele aspektów środowiska Windows jest ukrytych przed programistą w komponentach).

Poniżej wypunktuję kroki niezbędne do utworzenia okna na pulpicie:

  1. Pobierz uchwyt (handle) swojego programu (wymagane)
  2. Pobierz wiersz polecenia (nie wymagane, chyba że twój program przetwarza wiersz polecenia)
  3. Zarejestruj klasę okna (window class) (wymagane, chyba że korzystasz z predefiniowanych typów okien, np. MessageBox lub okno dialogowe)
  4. Utwórz okno (wymagane)
  5. Ukaż okno na pulpicie (wymagane, chyba że nie chcesz ukazywać swojego okna natychmiast)
  6. Odśwież obszar roboczy okna (client area)
  7. Wejdź do pętli nieskończonej sprawdzając wiadomości pochodzące z Windows
  8. Jeśli nadejdą wiadomości, to zostaną obsłużone przez specjalizowaną funkcję, która jest odpowiedzialna za to okno
  9. Zakończ program, jeśli użytkownik zamknie okno

Struktura programu dla Windows jest raczej złożona w porównaniu z programem dla DOS. Jednakże środowisko Windows różni się zasadniczo od środowiska systemu DOS. Programy dla Windows muszą współistnieć ze sobą w tej samej pamięci operacyjnej komputera i stosować się do ścisłych zasad współpracy z systemem. Ty, jako programista, również powinieneś posiadać bardzo precyzyjny styl programowania i ustalone zwyczaje.

Treść

Poniżej znajduje się kod programu prostego okna. Zanim przejdziemy do krwawych szczegółów programowania w asemblerze systemu Windows, wymienię kilka punktów, które ułatwią ci twoje programowanie.

.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

INCLUDE \masm32\include\windows.inc

;wywołania funkcji z user32.lib oraz kernel32.lib

INCLUDE    \masm32\include\user32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDE    \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

;zainicjowane dane

.DATA

ClassName DB "SimpleWinClass",0       ;nazwa naszej klasy okna
AppName   DB "Nasze Pierwsze Okno",0  ;tytuł naszego okna

;niezainicjowane dane

.DATA?

hInstance   HINSTANCE ?               ;uchwyt egzemplarza naszego programu
CommandLine LPSTR ?                   ;wskazanie wiersza poleceń

;tutaj rozpoczyna się nasz kod

.CODE

start:
    INVOKE GetModuleHandle, NULL     ;pobieramy uchwyt programu
    mov    hInstance, eax            ;pod Win32 hmodule==hinstance
    INVOKE GetCommandLine            ;pobierz wiersz polecenia. Nie musisz wywoływać
    mov    CommandLine, eax          ;tej funkcji, jeśli twój program nie przetwarza
                                     ;wiersza polecenia
    INVOKE WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT ;główna funkcja
    INVOKE ExitProcess, eax          ;kończymy program. Kod wyjścia jest zwracany
                                     ;w eax z WinMain.

WinMain PROC hInst:     HINSTANCE,\
             hPrevInst: HINSTANCE,\
             CmdLine:   LPSTR,\
             CmdShow:   DWORD

LOCAL wc:   WNDCLASSEX               ;na stosie tworzymy zmienne lokalne
LOCAL msg:  MSG
LOCAL hwnd: HWND
                                     ;wypełniamy pola struktury wc
    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   ;rejestrujemy naszą klasę okna
    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  ;wyświetlamy nasze okno na pulpicie
    INVOKE UpdateWindow, hwnd         ;odświeżamy obszar roboczy

    .WHILE TRUE                       ;wchodzimy w pętle wiadomości
        INVOKE GetMessage, ADDR msg, NULL, 0, 0
        .BREAK .IF (!eax)
        INVOKE TranslateMessage, ADDR msg
        INVOKE DispatchMessage, ADDR msg
    .ENDW

    mov eax, msg.wParam              ;kod powrotu zwracamy w eax
    ret

WinMain ENDP

WndProc PROC hWnd:   HWND, uMsg:   UINT, wParam: WPARAM, lParam: LPARAM 

    .IF uMsg==WM_DESTROY             ;jeśli użytkownik zamyka okno 
        INVOKE PostQuitMessage,NULL  ;kończymy pracę aplikacji 
    .ELSE 
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam ;standardowa obsługa okna 
        ret 
    .ENDIF 

    xor eax, eax 
    ret 

WndProc ENDP 

END start 

Analiza

Możesz być przerażony, iż tak prosty program dla Windows wymaga tyle kodowania. Jednakże większość z tych wierszy kodu to po prostu "szablony", które można przenosić z jednego pliku źródłowego do innego. Lub, jeśli tak wolisz, mógłbyś niektóre umieścić w bibliotece i wykorzystywać jak kody startowe i końcowe. Możesz zapisywać jedynie kody w funkcji WinMain. W rzeczywistości to właśnie robią kompilatory języka C++. Pozwalają ci one zapisywać kody w WinMain bez martwienia się o inne obowiązki. Musisz jedynie dostarczyć funkcję o nazwie WinMain, w przeciwnym wypadku kompilatory języka C++ nie będą w stanie połączyć twoich kodów z kodem startu i zakończenia programu. W języku asemblera nie masz takich ograniczeń. Możesz użyć dowolnej nazwy w miejsce WinMain lub wcale nie stosować tej funkcji.

Przygotuj się. To będzie bardzo długa lekcja. Przeanalizujmy ten program od A do Z.

 

.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

 

Pierwsze trzy wiersze są "koniecznością".

Następnie umieszczony został prototyp funkcji WinMain. Ponieważ w dalszej części kodu wywołujemy tę funkcję, musimy zdefiniować jej prototyp, aby można było zastosować INVOKE.

Na początku pliku źródłowego musimy dołączyć windows.inc, gdzie zawarto ważne struktury i stałe używane przez nasz program. Plik dołączany, windows.inc, jest po prostu plikiem tekstowym. Możesz otworzyć go za pomocą dowolnego edytora tekstu. Zwróć uwagę, iż windows.inc nie zawiera jeszcze wszystkich struktur i stałych. Hutch wraz ze mną pracuje nad tym. Możesz dołączyć nowe elementy, jeśli nie ma ich w tym pliku.

Nasz program wywołuje funkcje znajdujące się w user32.dll (na przykład CreateWindowEx, RegisterWindowClassEx) i w kernel32.dll (ExitProcess), więc musimy skonsolidować nasz program z tymi dwoma bibliotekami importu. Następne pytanie brzmi - skąd mam wiedzieć, którą bibliotekę importu należy połączyć z moim programem? Odpowiedź - musisz wiedzieć, gdzie znajduje się wywoływana przez twój program funkcja API. Na przykład, jeśli wywołujesz funkcję API z gdi32.dll, to musisz połączyć program z biblioteką gdi32.lib.

To podejście odnosi się do asemblera MASM. Asembler Borlanda - TASM stosuje prostszy sposób dołączania bibliotek importu: po prostu konsolidujesz swój program tylko z jedną, jedyną biblioteką - import32.lib.


.DATA ClassName DB "SimpleWinClass",0 AppName DB "Nasze Pierwsze Okno",0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ?


W następnej kolejności mamy sekcje "data".

W .DATA deklarujemy dwa łańcuchy znakowe zakończone znakiem o kodzie zero (łańcuchy ASCIIZ): ClassName będący nazwą klasy okna oraz AppName, który określa tytuł naszego okna. Zwróć uwagę, iż te dwie zmienne są zainicjowane.

W .DATA? są zadeklarowane dwie zmienne: hInstance (uchwyt naszego programu) i CommandLine (wiersz poleceń naszego programu). Dziwne typy danych HINSTANCE i LPSTR są w rzeczywistości nowymi nazwami dla DWORD (podwójne słowo - dana 32 bitowa). Możesz odszukać je w pliku windows.inc. Zwróć uwagę, iż wszystkie zmienne w sekcji .DATA? nie są zainicjowane, tzn. nie muszą one przechowywać żadnej określonej wartości przy starcie programu, lecz chcemy zarezerwować miejsce na przyszłe potrzeby.

 

.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

 

Sekcja .CODE zawiera wszystkie twoje instrukcje. Twój program musi być umieszczony pomiędzy <etykieta startowa> a END <etykieta startowa>. Nazwa tej etykiety nie jest ważna. Możesz nazwać ją dowolnie wg swojego gustu o ile będzie to nazwa unikalna i nie będzie naruszać konwencji nazw asemblera MASM.

Pierwszą instrukcją jest wywołanie GetModuleHandle w celu pobrania uchwytu egzemplarza (instance handle) naszego programu. W Win32 uchwyt egzemplarza (instance handle) i uchwyt modułu (module handle) są jednym i tym samym uchwytem. Możesz potraktować uchwyt egzemplarza jako numer identyfikacyjny swojego programu. Jest on stosowany jako parametr kilku funkcji API, które musi wywołać nasz program, więc zwykle najlepszym pomysłem będzie pobrać go na samym początku działania programu.

Uwaga: w rzeczywistości w win32 uchwyt egzemplarza jest liniowym adresem twojego programu w pamięci.

Po powrocie z funkcji Win32 jej wynik, jeśli takowy jest, można znaleźć w rejestrze eax.

Wszystkie inne wartości są zwracane poprzez zmienne przekazane na liście parametrów funkcji, którą zdefiniowałeś dla wywołania.

Wywoływane przez ciebie funkcje Win32 prawie zawsze zachowują rejestry segmentowe oraz rejestry ebx, edi, esi i ebp. Z drugiej strony rejestry ecx i edx są traktowane jako rejestry robocze i po powrocie z funkcji przyjmują nieokreśloną zawartość. Uwaga: nie oczekuj, iż zawartość rejestrów eax, ecx i edx będzie zachowana po wywołaniu funkcji API.

Przy wywołaniu danej funkcji API oczekujemy wartość zwrotną w eax. Jeśli którakolwiek z twoich funkcji będzie wywoływana przez Windows, to musi trzymać się tego samego scenariusza: zachować i odtworzyć wartości rejestrów segmentowych, ebx, edi, esi oraz ebp przy powrocie, w przeciwnym razie twój program bardzo szybko przestanie działać, dotyczy to również twojej procedury okna oraz funkcji zwrotnych (callback functions).

Wywołanie GetCommandLine nie jest konieczne, jeśli twój program nie przetwarza wiersza poleceń. W tym przykładzie pokazałem ci, jak to zrobić w przypadku, gdybyś tego potrzebował w swoim programie.

Następnie umieszczone jest wywołanie funkcji WinMain. Tutaj otrzymuje ona cztery parametry: uchwyt egzemplarza naszego programu, uchwyt poprzedniego egzemplarza naszego programu. wiersz poleceń oraz stan okna przy pierwszym pojawieniu się na pulpicie. W Win32 NIE istnieje poprzedni egzemplarz. Każdy program przebywa sam w swojej przestrzeni adresowej, więc wartość hPrevInst wynosi zawsze 0. Jest to pozostałość z czasów Win16, gdy wszystkie egzemplarze programu działały w tej samej przestrzeni adresowej, a jakiś egzemplarz chciał się dowiedzieć, czy jest pierwszym z nich. W Win16 jeśli hPrevInst ma wartość NULL, to oznacza to pierwszy egzemplarz programu.

 

Uwaga:

Nie musisz deklarować nazwy funkcji jako WinMain. W rzeczy samej masz tutaj całkowitą swobodę. Nie musisz nawet tworzyć żadnego odpowiednika funkcji WinMain. Możesz wkleić kod funkcji WinMain za wywołaniem GetCommandLine a twój program wciąż będzie w stanie działać poprawnie.


Po powrocie z WinMain w rejestrze eax umieszczony zostaje kod wyjścia. Przekazujemy ten kod jako parametr do funkcji ExitProcess, która zakończy wykonywanie naszej aplikacji.


WinMain PROC Inst:      HINSTANCE,\
             hPrevInst: HINSTANCE,\
             CmdLine:   LPSTR,\
             CmdShow:   DWORD 


Powyższy wiersz jest deklaracją funkcji WinMain. Zwróć uwagę na pary parametr:typ, które występują za dyrektywą PROC. Są to parametry, które funkcja WinMain otrzymuje od kodu wywołującego. Do parametrów tych możesz odwoływać się poprzez nazwę zamiast za pomocą operacji na stosie. Dodatkowo MASM wygeneruje kody wejścia i zakończenia tej funkcji. Więc nie musimy się przejmować ramką stosu przy wejściu i wyjściu z funkcji.


LOCAL wc:   WNDCLASSEX 
LOCAL msg:  MSG 
LOCAL hwnd: HWND


Dyrektywa LOCAL rezerwuje pamięć na stosie dla zmiennych lokalnych używanych przez daną funkcję. Wiązka dyrektyw LOCAL musi się znajdować bezpośrednio poniżej dyrektywy proc.

Za dyrektywą LOCAL bezpośrednio występuje <nazwa zmiennej lokalnej>:<typ zmiennej>. Więc wiersz LOCAL wc:WNDCLASSEX nakazuje asemblerowi MASM zarezerwowanie pamięci na stosie o rozmiarze struktury WNDCLASSEX dla zmiennej o nazwie wc (window class - klasa windows). Do zmiennej wc możemy się odwoływać w naszym kodzie bez trudności związanych z manipulacją stosem - robi to za nas asembler generując odpowiednie adresy. Sądzę, że to prawdziwe dobrodziejstwo. Wadą jest to, iż zmienne lokalne nie mogą być używane poza funkcją, w której je utworzono oraz będą automatycznie usunięte ze stosu po powrocie z funkcji do miejsca wywołania. Drugą wadą jest brak możliwości automatycznej inicjalizacji zmiennych lokalnych, ponieważ są one jedynie pamięcią stosu przydzieloną w momencie wejścia do funkcji. Musisz przypisać im ręcznie wartości po dyrektywach LOCAL.


    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


Linie powyżej, chociaż wyglądają niepokojąco skomplikowanie, są naprawdę proste w założeniu. Potrzebne jest tylko kilka wierszy instrukcji. Służą one do określenia klasy okna (window class), która jest niczym innym tylko planem specyfikacji okna. Definiuje ona kilka istotnych charakterystyk okna takich jak jego ikona, kursor, funkcja odpowiedzialna za jego obsługę, kolor itp. Okno powstaje ze swojej klasy. Jest to w pewnym sensie koncepcja ukierunkowana na obiekty. Jeśli będziesz chciał utworzyć więcej niż jedno okno o tej samej charakterystyce, to sensowne jest przechowanie tych wszystkich parametrów tylko w jednym miejscu i odwołanie się do nich w razie potrzeby. Schemat taki zaoszczędzi mnóstwo pamięci unikając powtarzania informacji. Zapamiętaj, iż Windows opracowano w przeszłości, gdy układy pamięci nie były łatwo dostępne i większość komputerów posiadała jedynie 1MB pamięci. Windows musi być bardzo efektywne w wykorzystywaniu rzadkich zasobów pamięciowych. Chodzi o to, iż jeśli definiujesz swoje własne okno, to musisz wprowadzić pożądane charakterystyki swojego okna do struktury WNDCLASS lub WNDCLASSEX i wywołać RegisterClass lub RegisterClassEx zanim będziesz mógł to okno utworzyć. Dla każdego typu okna wystarczy jednokrotna rejestracja.

Windows posiada kilka predefiniowanych klas okien, takich jak przycisk (button) i okno edycyjne (edit box). Dla tych okien (lub kontrolek) nie musisz dokonywać rejestracji klasy okna, po prostu wywołujesz CreateWindowEx z predefiniowaną nazwą klasy.

Najważniejszym polem struktury WNDCLASSEX jest lpfnWndProc. lpfn oznacza długie wskazanie do funkcji (long pointer to function). W Win32 nie ma bliskich i dalekich wskazań, tylko wskazania z uwagi na nowy, płaski model pamięci. Jednakże jest to znów pozostałość z czasów Win16. Każda klasa okna musi być powiązana z funkcją zwaną procedurą okna (window procedure). Procedura okna jest odpowiedzialna za obsługę wiadomości dla wszystkich okien utworzonych na podstawie powiązanej z nią klasy okna. System Windows będzie przesyłał wiadomości do procedury okna, aby ją powiadomić o ważnych zdarzeniach dotyczących tych okien, za które jest ona odpowiedzialna, takich jak dane z klawiatury lub myszki. Od procedury okna zależy, czy zareaguje ona inteligentnie na każde otrzymane zdarzenie okna. Większość czasu poświęcisz na tworzenie sterowników zdarzeń w procedurze okna.

Poniżej umieściłem opis każdego pola struktury WNDCLASSEX

WNDCLASSEX STRUCT DWORD 
    cbSize        DWORD ? 
    style         DWORD ? 
    lpfnWndProc   DWORD ? 
    cbClsExtra    DWORD ? 
    cbWndExtra    DWORD ? 
    hInstance     DWORD ? 
    hIcon         DWORD ? 
    hCursor       DWORD ? 
    hbrBackground DWORD ? 
    lpszMenuName  DWORD ? 
    lpszClassName DWORD ? 
    hIconSm       DWORD ? 
WNDCLASSEX ENDS
    INVOKE CreateWindowEx, NULL,\ 
                           ADDR ClassName,\ 
                           ADDR AppName,\ 
                           WS_OVERLAPPEDWINDOW,\ 
                           CW_USEDEFAULT,\ 
                           CW_USEDEFAULT,\ 
                           CW_USEDEFAULT,\ 
                           CW_USEDEFAULT,\ 
                           NULL,\ 
                           NULL,\ 
                           hInst,\ 
                           NULL


Po zarejestrowaniu klasy okna możemy wywołać CreateWindowEx w celu utworzenia naszego okna na podstawie przesłanej klasy okna. Zwróć uwagę, iż funkcja ta przyjmuje 12 parametrów.


CreateWindowExA PROTO dwExStyle: DWORDORD,\ 
                      lpClassName:  DWORD,\ 
                      lpWindowName: DWORD,\ 
                      dwStyle:      DWORD,\ 
                      X:            DWORD,\ 
                      Y:            DWORD,\ 
                      nWidth:       DWORD,\ 
                      nHeight:      DWORD,\ 
                      hWndParent:   DWORD,\ 
                      hMenu:        DWORD,\ 
                      hInstance:    DWORD,\ 
                      lpParam:      DWORD


Obejrzyjmy szczegółowy opis każdego parametru:

    mov    hwnd, eax 
    INVOKE ShowWindow, hwnd, CmdShow 
    INVOKE UpdateWindow, hwnd 


Przy pozytywnym powrocie z CreateWindowEx w rejestrze eax zostaje zwrócony uchwyt okna. Musimy zachować tę wartość do przyszłego użytku. Nowo utworzone okno nie zostaje automatycznie wyświetlone. Należy wywołać funkcję ShowWindow z uchwytem okna oraz pożądanym "stanem wyświetlania" okna, aby pojawiło się ono na ekranie. Następnie możesz wywołać UpdateWindow w celu zamalowania obszaru roboczego (client area). Funkcja ta jest użyteczna przy odświeżaniu zawartości tego obszaru. Jednakże możesz też pominąć to wywołanie.


.WHILE TRUE
    INVOKE GetMessage, ADDR msg, NULL, 0, 0
    .BREAK .IF (!eax)
    INVOKE TranslateMessage, ADDR msg
    INVOKE DispatchMessage, ADDR msg
.ENDW

 

W tym momencie nasze okno pojawia się na ekranie. Lecz nie może ono otrzymywać danych ze świata. Zatem musimy "informować" je o istotnych zdarzeniach. Dokonujemy tego za pomocą pętli wiadomości. Dla każd ego modułu jest tylko jedna pętla wiadomości, która ciągle sprawdza, czy nadeszły jakieś wiadomości z systemu Windows za pomocą wywołania funkcji GetMessage. Funkcja ta przekazuje do systemu Windows wskazanie struktury MSG. Struktura ta zostanie wypełniona informacją na temat wiadomości. którą system Windows chce przekazać oknu w tym module. Z funkcji GetMessage nie nastąpi powrót, aż będzie jakaś wiadomość dla okna w module. W tym czasie system Windows może oddać sterowanie innemu programowi. W ten sposób zbudowano schemat wielozadaniowości na platformie Win16. Funkcja GetMessage zwraca FALSE, jeśli otrzymano wiadomość WM_QUIT, co z kolei spowoduje przerwanie pętli wiadomości i zakończenie programu.

TranslateMessage jest funkcją użytkową, która odczytuje kody matrycowe klawiatury i tworzy nową wiadomość (WM_CHAR) umieszczaną w kolejce wiadomości. Wiadomość WM_CHAR zawiera wartość ASCII naciśniętego klawisza, która jest łatwiejsza do obsługi niż kody matrycowe klawiatury. Możesz pominąć to wywołanie, jeśli twój program nie przetwarza danych z klawiatury.

DispatchMessage wysyła dane wiadomości do procedury okna odpowiedzialnej za określone okno, dla którego ta wiadomość jest przeznaczona.


    mov eax, msg.wParam
    ret
WinMain ENDP


Jeśli pętla wiadomości zostanie zakończona, to kod wyjścia będzie umieszczony w polu wParam struktury MSG. Możesz umieścić tek kod w rejestrze eax, aby zwrócić go systemowi Windows. Obecnie Windows nie korzysta ze zwracanej wartości, lecz lepiej stosować się do bezpiecznych zasad z uwagi na przyszłe rozszerzenia.


WndProc PROC hWnd: HWND,\
             uMsg: UINT,\
             wParam: WPARAM,\
             lParam: LPARAM 


To jest nasza procedura okna. Nie musisz nazywać jej WndProc. Pierwszy parametr hWnd jest uchwytem okna należącym do okna, do którego adresowana jest wiadomość. uMsg jest wiadomością. Zwróć uwagę, iż uMsg nie jest strukturą MSG. To tylko liczba. Windows definiuje setki wiadomości, których większością twój program nie będzie zainteresowany. Windows wysyła odpowiednią wiadomość do okna w przypadku, gdy dzieje się coś istotnego dla tego okna. Procedura okna otrzymuje tę wiadomość i reaguje inteligentnie na nią. wParam i lParam są po prostu dodatkowymi parametrami używanymi przez niektóre wiadomości. Wysyłają one towarzyszące dane jako dodatek do samej wiadomości. Dane te są przekazywane procedurze okna za pomocą lParam i wParam.


    .IF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam ret
    .ENDIF
    xor eax, eax
    ret
WndProc ENDP


I oto nadchodzi najważniejsza część. To tutaj znajduje się większość inteligencji twojego programu. Kody odpowiadające na każdą wiadomość Windows są w procedurze okna. Twój kod musi sprawdzić, czy jest zainteresowany odebraną wiadomością Windows. Jeśli tak, to musi wykonać wszystko, co należy zrobić w odpowiedzi na tę wiadomość a następnie wrócić z zerem w rejestrze eax. jeśli nie, to MUSI wywołać DefWindowProc przekazując jej wszystkie otrzymane parametry, aby nastąpiła standardowa obsługa wiadomości. Funkcja DefWindowProc jest funkcją API obsługującą wszystkie wiadomości, którymi nie jest zainteresowany twój program.

Jedyną wiadomością, na którą MUSISZ odpowiedzieć jest WM_DESTROY. Wiadomość ta jest przesyłana do twojej procedury okna zawsze, gdy twoje okno zostaje zamknięte. W czasie gdy procedura okna odbierze tę wiadomość, okno jest już usunięte z ekranu. Jest to tylko powiadomienie o fakcie usunięcia okna i powinieneś przygotować się na powrót do systemu Windows. W odpowiedzi możesz posprzątać swoje dane zanim oddasz kontrolę systemowi. Jeśli dojdzie do tego etapu, nie masz innego wyjścia, jak zakończyć swój program. Jeśli chcesz mieć szansę powstrzymania użytkownika przed zamknięciem twojego okna, powinieneś przetwarzać wiadomość WM_CLOSE. Wracając do WM_DESTROY po wykonaniu porządków w danych musisz wywołać PostQuitMessage, która wyśle do twojego modułu wiadomość WM_QUIT. Wiadomość ta spowoduje, iż GetMessage zwróci wartość zero w rejestrze eax, co z kolei przerwie pętlę wiadomości i zwróci sterowanie do systemu Windows. Możesz wysłać wiadomość WM_DESTROY do swojej własnej procedury okna wywołując funkcję DestroyWindow.

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program SimpleWindow;

uses Windows;

function WndProc(h:HWND;uMsg:UINT;wP:WPARAM;lP:LPARAM) : longint;
begin
  if uMsg=WM_DESTROY then
 begin  
    PostQuitMessage(0);
    WndProc := 0;
 end
  else
    WndProc := DefWindowProc(h,uMsg,wP,lP);
end;

function WinMain(hI,hPI:HINST;CmdLine:LPSTR;cmdShow:longint) : longint;
const
  ClassName = 'SimpleWinClass';
var
  wc : WndClassEx;
  ms : msg;
  h  : HWnd;
begin
  wc.cbSize        := SizeOf(WndClassEx);
  wc.style         := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc   := @WndProc;
  wc.cbClsExtra    := 0;
  wc.cbWndExtra    := 0;
  wc.hInstance     := hI;
  wc.hbrBackground := COLOR_WINDOW + 1;
  wc.lpszMenuName  := 0;
  wc.lpszClassName := ClassName;
  wc.hIcon         := LoadIcon(0,IDI_APPLICATION);
  wc.hIconSm       := wc.hIcon;
  wc.hCursor       := LoadCursor(0,IDC_ARROW);
  RegisterClassEx(wc);
  h := CreateWindowEx(0,ClassName,'Nasze pierwsze okno',
                      WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
                      CW_USEDEFAULT,CW_USEDEFAULT,0,0,hI,0);
  ShowWindow(h,CmdShow);
  UpdateWindow(h);
  while GetMessage(ms,0,0,0) do
  begin
    TranslateMessage(ms);
    DispatchMessage(ms);
  end;
  WinMain := ms.wParam;
end;

begin
  ExitProcess(WinMain(GetModuleHandle(0),0,0,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.