Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek |
|
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}.
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:
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.
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
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.
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. |
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe