[ Kurs API ] |
Poniżej znajdziesz rewelacyjny kurs API! Dzięki niemu będziesz mógł wzbogacić swoje programy o nadzwyczajne możliwości!
Jeśli chcesz zawsze być na bieżąco z nowościami w portalu Coders' World - wpisz swój adres e-mail w pole powyżej i kliknij [ZAPISZ]! Dzięki temu masz gwarancję, że jako pierwszy dowiesz się o zmianach w Coders' World!
|
[ Kurs API: Wstęp ] |
||
Czym jest API? API Windows to zestaw funkcji, które pozwalają zbudować praktycznie dowolną aplikację działającą w systemach Windows 95, 98, NT, 2000 oraz CE. Za ich pomocą można manipulować graficznym interfejsem użytkownika, wyświetlać grafikę i sformatowany tekst, można także zarządzać zasobami systemu, takimi jak pamięc, pliki i procesy. Funkcje API zostały zaprojektowane dla programistów używajacych C\C++, choć możliwe jest oczywiście tworzenie aplikacji także w innych językach wykorzystując funkcje API. Aplikacje, które używaja funkcji API wymagaja oczywiście, aby był zainstalowany jeden z wyżej wymienionych systemów. Nie wszystkie funkcje API działaja na wszystkich systemach serii Windows, niektóre, zwłaszcza te nowszej daty mogą wymagać Windows'a NT lub 2000. O tym jakie funkcje maja jakie wymagania można sie dowiedzieć z różnorakiego bogactwa podręcznikow i plików pomocy (polecam książke Petzold'a "Programowanie Windows 95/98/NT", oraz podręcznik pomocy, który jest rozprowadzany razem z pakietem VC++, firmy Microsoft - MSDN). API nie jest językiem. Jest to swego rodzaju "sposób na życie" dla programistow w Windows. Oczywiście można sie obejść bez API wykorzystując biblioteki obiektowe, takie jak MFC (Microsoft) czy OWL (Borland) lub jeszcze inne. Jednak ich zastosowanie nie eliminuje złożonosci systemu jakim jest niewatpliwie Windows, wcześniej czy później przyjdzie czas, gdy zmuszeni zostaniemy do sięgniecia do źródeł czyli czystych funkcji API. Dlaczego warto znac API Windows? Pierwsza, najwazniejsza korzysc to taka, ze poznajemy sposob dzialania systemu niejako od wewnatrz. Caly Windows opiera swe dzialanie na kilku bibliotekach dll, w których zawarte sa najbardziej potrzebne funkcje API, ktore my programisci powinnismy wykorzystywac. Druga korzysc to maly rozmiar programow. Skompilowany exec napisany z wykorzystaniem MFC lub innej biblioteki obiektowej zajmuje nierzadko kilka MB, podczas gdy aplikacja korzystajaca tylko z API kilkadziesiat, kilkaset kilobajtow ( oczywiscie pod warunkiem, ze nie zawiera dziesiatek MB zasobow w postaci bitmap w TRUE COLOR ). Aplikacje API nie potrzebuja do dzialania zadnych zewnetrznych bibliotek ( oprocz tych, które my jawnie do niej dolaczymy i tych znajdujacych sie w katalogu Windows'a ). Dla przykladu aplikacja wykorzystujaca DirectX nie potrzebuje nic, prócz plików dll, które zostaja umieszczone w systemie podczas instalacji sterowników DirectX, reszta potrzebnego kodu to biblioteki kernel(32).dll, user(32).dll i GDI(32).dll. Sa to trzy podstawowe biblioteki, bez ktorych niemozliwe byloby dzialanie systemu Windows. Kernel32.dll odpowiada za funkcjonowanie jadra systemu ( zarzadzanie pamiecia, operacje I/O i uruchamianie zadan ), user32.dll to obsluga interfejsu uzytkownika ( klawiatura, mysz itp. oraz logika okien ). GDI32.dll to interfejs graficzny, ktory umozliwia nam rysowanie na ekranie lub drukowanie na drukarce. W czym pisać aplikacje korzystające z API? Wskazane jest posiadanie kompilatora, który umożliwia generownie 32-bitowego kodu. Dobrym początkiem moze byc pakiet Microsoftu, Visual C++ Developer Studio, ktory zawiera nie tylko kompilator, ale wiele narzędzi do tworzenia i zarządzania zasobami, można też w nim tworzyc aplikacje oparte na bibliotece obiektowej MFC. Polecana wersja to 5.0 lub 6.0, dostępne są też Service Packi, osobiscie używam nr 3 dla ver. 5.0. To tylko nakreślenie czym jest tak naprawde to straszne API, w rzeczywistosci nie jest to nic trudnego, po kilku próbach można to nawet polubic ;))
Autorem artykułu jest: |
||
[ Kurs API: Podstawy ] |
||
Przygotowanie projektu Tak, jak obiecałem poprzednim razem, zajmiemy się dzisiaj pierwszymi programami w API Windows. Ufam, że kompilator został zainstalowany, ręce rozgrzane, klawiatura w pełni sprawna, obsługa Windows w miarę dobrze znana (ponieważ poprzednim razem zapomniałem dodać, że aby dobrze programować w Windows trzeba choć ociupinkę znać ten system i umieć się nim posługiwać :). Uruchamiamy więc nasz kompilator (w naszym przypadku VC ver. 5.0), przechodzimy do menu "File", wybieramy opcję "New" i w tym momencie naszym oczom ukazuje się okienko z piekła rodem zawierające masę kolorowych ikonek:
Pliki źródłowe Aby móc cokolwiek skompilować, trzeba by napisać choć kilka linii kodu, no nie? Tak więc kontynuujmy nasze dzieło co by niepotrzebnie nie tracić czasu. Pierwsze co robimy to menu "Project", w którym wybieramy opcję "Add To Project" a w nim z kolei "New". Co dostajemy?
Bardzo podobne okienko do tego na początku, z tym, że tym razem aktywna zakładka to "Files" i bardzo dobrze, bo o to nam dokładnie chodzi. Z listy dostępnych typow plików, które możemy dodać do projektu wybieramy "C++ Source File". Po prawej stronie widzimy jedno puste pole domagąjace się wpisania czegoś. Wpisujemy nazwę pliku, jaki zostanie dodany do projektu, nic nie kombinując w pozostałych okienkach, no bo po co, tak jak jest, jest dobrze. Po kliknięciu OK ukazuje nam się piękny czysty arkusz na który możemy przelać wymysły naszej chorej wyobraźni. Kod A co przelać na ten arkusz, żebysmy mogli ujrzeć tak długo oczekiwane okienko. Otóż juz mówię. Napiszmy np. coś takiego:
#include <windows.h>
case WM_DESTROY:
case WM_KEYDOWN:
default:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
wndclass.style = CS_HREDRAW | CS_VREDRAW;
if(RegisterClass(&wndclass) == 0)
hWnd = CreateWindow(
if(hWnd == NULL)
ShowWindow(hWnd, SW_SHOW);
while(GetMessage(&msg, NULL, 0, 0)) Po wklepaniu wciskamy kombinację Ctrl+F5, albo naciskamy czerwonawy przycisk w kształcie wykrzyknika na pasku narzędzi (powinien domyślnie byc doczepiony gdzieś tam, pod paskiem menu), zresztą każdy zagorzały programista powinien go mieć zawsze pod ręka. Jeśli nie popełnilismy żadnych błędów powinniśmy otrzymać piękne białe okno, z paskiem tytułu, klawiszami do zamykania, minimalizowania i maksymalizowania okna. Co robi nasz program? Jeżeli sadzic po efektach wizualno - dzwiekowych NIC! Z drugiej jednak strony robi to co każda porządna aplikacja w Windows - reaguje na komunikaty. Jeśli jeszcze nie wiecie co to komunikaty wyjasni się to w dalszej częsci kursu. Na dziś to może tyle no bo nie można się przemęczac przecież, a za to następnym razem omówimy dokładniej budowę naszego programu, dowiemy się co robią funkcje WinMain() i MianWndProc() oraz co to są komunikaty.
Autorem artykułu jest: |
[ Kurs API: Funkcje, komunikaty ] |
||
Zgodnie z obietnicą daną poprzednim razem, dzisiaj zajmiemy się omówieniem dokładniej naszego programu, co pozwoli także na nieco głębsze zapoznanie się z istotą działania samego systemu Windows. Omówimy sobie poszczególne elementy programu podanego ostatnio jako przykład, powiemy o funkcjach WinMain() i MainWndProc(), strukturze WNDCLASS i pętli komunikatów. Więc bez zbędnego mącenia wody zaczynajmy. Funkcje, komunikaty i ich petla
Jeśli przyglądniemy się naszemu programowi z "lotu ptaka", nie wnikając w szczegóły zauważymy, że mamy w nim tylko dwie funkcje, co może się wydawać początkującym programistom w Windows trochę niesamowite. To jednak prawda, do napisania zupełnie poprawnie pracującej aplikacji w Windz'ie wystarczą tylko te dwie funkcje, choć w aplikacji robiącej różne pożyteczne rzeczy (np. w takim Q4) jest to zdecydowanie za mało, to jednak bez tych dwóch ani rusz dalej. Wytężając bardziej wzrok dostrzegamy, że jedna z nich to WinMain() a druga to MainWndProc(). Jak nadmieniłem już we wstępie funkcje API zostały napisane z myślą o wykorzystaniu ich w C\C++. W każdym działającym programie w ww. językach podstawą wszystkiego jest funkcja main(), a co ona robi, nie trzeba chyba nikomu tłumaczyć (jeśli ktoś nie wie niech natychmiast śle mail'a! :)). Jako, że Wndows jest znany powszechnie z tego, że jest systemem bardzo pokręconym, w którym wszystko działa nie tak jak tego się można spodziewać, oczywiście musiało się to odbić na sposobie pisania aplikacji. Zamiast starej, dobrej funkcji main() widzimy tu jakiegoś koszmarnego stwora o nazwie może i nieco podobnej, ale już parametrami i wartością zwracaną nijak nie przypominającego swojego protoplastę z dos'a. Brzmi to mniej więcej tak: brrrrrr!, aż ciarki przechodzą. Nie będę się tu może wgłębiał w drobne szczegóły w rożnicach pobieranych typów przez tą funkcję w różnych przykładowych programach, z którymi przyjdzie się nam zmierzyć, zapewniam jednak, że zawsze chodzi o to samo. A o co chodzi, już wyjaśniam: HINSTANCE hInstance jest to tzw. uchwyt realizacji (????). Czym z kolei jest uchwyt? Uchwyt to po prostu liczba, ktorej uzywa aplikacja do identyfikowania czegos czego moze "dotknac". W naszym wypadku uchwyt przekazywany do tej funkcji identyfikuje nasz program. Nie musimy jednak zawracac sobie tym glowy bo bedziemy z tego bardzo rzadko korzystac. HINSTANCE hPrevInstance jest to uchwyt (juz wiemy co to) do poprzedniej realizacji programu. W Windows mozemy uruchomic kilka kopii tego samego programu (o czym moze nie wszyscy wiedza :)), sa to wlasnie tzw. realizacje. W starym Windows kazdy nowy program uruchamiany z pliku exe korzystal z zasobow programu, ktory byl zaladowany jako pierwszy, z tego samego exec'a, natomiast w nowym, tym bardziej pokreconym zrezygnowano z tego i ten parametr jest zawsze ustawiany na NULL, wiec nie powinien nas obchodzic w najmniejszym stopniu. LPSTR lpCmdLine w koncu cos znajomego. Nic innego jak linia polecen programu. Tu umieszczany jest lancuch znakow, ktory nastepuje zaraz po nazwie naszego programu, np. "aqq.exe". Przewaznie oczywiscie nie wpisujemy zadnych parametrow programu (zwlaszcza, ze szczegolnie w Windows jest to bardzo utrudnione), ale mysle, ze o tym parametrze warto pamietac, bo moze sie kiedys przydac. int nCmdShow parametr ten mowi naszej aplikacji w jakiej postaci ma zostac uruchomiony nasz program (dokladniej mowiac jego okno), czy ma byc zminimalizowane na pasku zadan, czy wypelniac pelny ekran czy jeszcze jakos inaczej. Mozna zauwazyc, ze niejako bezposrednio mamy wplyw tylko na linie komend programu, do ktorej bezposredni dostep ma uzytkownik. Nie mozemy podac uchwytu realizacji przy uruchamianiu pliku exe ani sposobu pokazania okna (nie liczac parametrow linii komend). Tak wiec nasz glowny wysilek polozmy w tym momencie na zawartosc tej funkcji. Zanim jednak przystapimy do tego niezbedne jest wyjasnienie czym jest ten tajemniczy komunikat i scisle z nim zwiazana funkcja MainWndProc(). A wiec co to takiego ten komunikat??? Nie jest to nic innego jak informacja docierajaca do naszej aplikacji, ze cos z nia zrobilismy (lub mamy zamiar zrobic tylko jeszcze nie wiemy jak). Najprostsze przyklady to: klikamy myszka w naszym oknie, co robi aplikacja? NIC, naciskamy klawisze klawiatury, co robi aplikacja? NIC. Ale, np. gdy wcisniemy kombinacje Alt+F4 to co? Widac roznice w zachowaniu sie aplikacji? Gdy dwa razy klikniemy szybko na pasku tytulowy to cos sie dzieje? Oczywiscie rozmaitych typow komunikatow jest w Windows cale mnostwo, my nie bedziemy nich wszystkich omawiac bo nie ma oczywiscie takiej potrzeby. Po co nam takie komunikaty? Otoz aplikacje Windows sa tzw. aplikacjami sterowanymi zdarzeniami. W samym programie, jesli patrzac na niego z punktu widzenia programisty dos'a (czyli funkcji WinMain() przewaznie nie dzieje sie prawie nic, a zarazem bardzo wiele jak za chwile sie przekonamy). Cala brudna robota odwalana jest w drugiej funkcji czyli tzw. procedurze okna. Programy sterowane zdarzeniami same z siebie nie robia nic (przynajmniej tak ogolnie biorac). Po uruchomieniu sie i zainicjalizowaniu potrzebnych zmiennych (ktorych w Windows oczywiscie nie brakuje) oczekuja one na nadejscie jakiegos zdarzenia. Jesli takowe otrzymaja natychmiast zostaje wyslany komunikat do naszej procedury okna. W ten mniej wiecej sposob dzialaja wszystkie aplikacje. A co robi taka procedura okna? Jak potem zauwazymy w niej znajduje sie caly kod odpowiedzialny za reakcje na nasze komunikaty. Procedura okna to nic innego jak petla, ktora wykonuje sie przez caly czas dzialania programu bardzo szybko (w miare mozliwosci systemu itd. co nie zawsze jest prawda w Windows :)). Po nadejsciu jakiegos komunikatu nastepuje sprawdzenie czy mamy kod obslugi takiego komunikatu w naszej petli i jesli tak jest to jest to wykonywane. Jesli nie mamy kodu obslugi komunikatu, ktory nadszedl jest on przekazywany domyslnej funkcji obslugi komunikatow Windows, ktora przewaznie robi bardzo brzydko i po prostu go ignoruje (co bardzo ladnie nazywa sie "domyslna obsluga komunikatu"). No dobrze, skoro mamy juz niewielkie pojecie co to komunikat, po co nam funkcje WinMain() i MainWndProc(), ktora jak nie trudno sie chyba domyslic jest dla nas procedura okna, mozemy przystapic do bardziej szczegolowego omowienia naszego progamu. WinMain Co takiego znajdziemy w funkcji WinMain()? Przegladnijmy ja od samego poczatku. Jak kultura programowania nakazuje, na poczatku mamy deklaracje zmiennych wystepujacych w programie. No i zeby nie bylo nam za wesolo to patrzymy i widzimy co? Ano takie cos:
MSG msg;
Wielu w tym momencie lapie sie za glowy i pyta co to jest? Co to za zmienne??? I tu znowu kilka "slow pociechy" dla przyszlych asow programowania w API. Jesli chcemy programowac w Windows musimy sie niestety przyzwyczaic do ogromniastych struktur, ktore przyjdzie nam bez przerwy wypelniac i odczytywac. W Windows takich struktur jest od groma i troche i co gorsza nie mozna sie bez nich obejsc, trzeba sie niestety nauczyc ich uzywania. Dzieki bibliotekom obiektowym, takim jak MFC ich obecnosc jest moze troche mniej odczuwalna, ale sa one nieodlaczna czescia systemu i bez nich nie da rady. MSG jest to struktura komunikatu. W strukturze tej sa przechowywane wszystkie dane, dotyczace nadchodzacego komunikatu, czyli okno (uchwyt) do jakiego jest skierowany komunikat, jego typ i parametry (tak, tak komunikat moze miec parametry :)) i kilka innych mniej istotnych dla nas rzeczy. WNDCLASS bardzo wazna rzecz o ktorej juz za chwile dokladniej. HWND juz nam znany uchwyt (identyfikator okna), czyli jakis blizej nieokreslony na razie unikalny numer. Kilka linii wyzej mamy typ o nazwie WNDCLASS, a po kiego diabla nam to potrzebne?. Juz wyjasniam, Kazdego okno uruchomione w Windows, poczawszy od okien aplikacji a skonczywszy na klawiszach w oknach dialogowych (tak, tak to tez okna :)), musi posiadac zarejestrowana w systemie klase swojego okna. Okna takie jak klawisze, czy okienka dialogowe maja klase ustalona z gory przez Windows, co nie znaczy wcale, ze nie moga miec innej. Natomiast kazde okno aplikacji (okno glowne) musi miec zarejestrowana przez program klase okna w systemie. Aby zarejestrowac w systemie klase okna uzywamy funkcji RegisterClass(&wndclass), ktora pobiera jako parametr adres struktury klasy okna. Zanim jednak zarejestrujemy klase okna w systemie musimy oczywiscie wypelnic wyzej wspomniana ogromna strukture typu WNDCLASS jakimis wartosciami, ktore beda mialy okreslony sens. To wlasnie robimy min. w funkcji WinMain(). Jak widac w naszym przykladzie mamy cos takiego:
wndclass.style = CS_HREDRAW | CS_VREDRAW; Dwa najwazniejsze pola w naszej strukturze to pola drugie i ostatnie. Drugie pole: wndclass.lpfnWndProc = MainWndProc zawiera adres funkcji, ktora jest procedura okna dla wszystkich okien utworzonych na podstawie tej klasy (no bo mozemy oczywiscie tworzyc wiele okien tej samej klasy i kazde ma swoja petle obslugi komunikatow). Pole ostatnie: wndclass.lpszClassName = lpszAppName to nazwa naszej klasy, ktora mozemy sobie nazwac dowolnie, jest to tylko lancuch znakow. Po opis pozostalych pol jak zwykle odsylam do help'a, no chyba, ze zazyczycie sobie dokladnych opisow, wtedy cos pomyslimy na ten temat :). Pozostale wazne pola to: style, ktore zwykle przyjmuje wartosci takie jak w naszym przykladzie, a oznacza to mniej wiecej tyle, ze okno po kazdej zmianie rozmiarow ma byc odrysowywane w pionie i poziomie, hInstance, ktore przechowuje uchwyt realizacji (pamietamy co to uchwyt!!!), hIcon i hCursor chyba nietrudno sie domyslic. hbrBackground okresla styl pedzla, jakim jest malowane tlo okna, lpszMenuName, to nazwa identyfikujaca menu, umieszczone w pliku zasobow (co to zasoby i o ich rodzajach nastepnym razem). Tak w skrocie wyglada struktura WNDCLASS, ktora musimy wypelnic zanim zaczniemy tworzyc okno. Skoro mamy juz wypelniona strukture WNDCLASS, mozemy przystapic do bardzo waznej rzeczy, jaka jest rejestracja okna w systemie. Jak juz pisalem wyzej robi to funkcja RegisterClass(), pobierajaca jako parametr adres struktury WNDCLASS. W naszym przykladzie mamy cos takiego:
if(RegisterClass(&wndclass) == 0) Jak widac, jesli funkcja zwroci po wykonaniu wartosc zero, oznacza to, ze nie mozna utworzyc klasy okna z podanej struktury i w tym momencie konczy sie nasza krotka acz burzliwa kariera programisty w Windows, jednak badzmy dobrej mysli. Po naszym kursie nie ma prawa nic takiego sie zdarzyc :). Skoro mamy juz klase okna, mozemy przystapic do tak dlugo oczekiwanego momentu czyli utworzenia okna. Robimy to po prostu tak:
hWnd = CreateWindow(
Po wykonaniu funkcja zwraca nam uchwyt nowo utworzonego okna, lub NULL jesli nie utworzy okna (co moze sie oczywiscie zdarzyc :)). Pierwszy parametr to nazwa naszej zarejestrowanej klasy okna (ostatnie pole w strukturze WNDCLASS!), drugie to nazwa okna (to co pojawi sie na pasku tytulowym). Trzeci, straszliwie wygladajacy stwor to kombinacja styli okna. Jest ich tez cala masa, nie bede sie wglebial bo od tego macie dokumentacje, zaznacze tylko, ze style mozna laczyc uzywajac operatora sumy '|' (OR). Cztery nastepne argumenty okreslaja poczatkowe polozenie okna na ekranie. Pierwsze dwa to wspolrzedne lewego, gornego rogu, polozenia okna, natomiast dwa pozostale to odpowiednio szerokosc i wysokosc (liczona oczywiscie w dol !) od lewego gornego rogu. Nastepny parametr (u nas NULL) to uchwyt okna rodzica. Jesli nasze okno jest glownym oknem aplikacji to jest to oczywiscie NULL (bo nie ma rodzica), jesli nasze okienko mialoby byc 'dzieckiem' innego okna, wtedy nalezaloby tu podac uchwyt tegoz rodzica. Kolejny NULL to uchwyt menu lub identyfikator okna dziecka, w naszych zastosowaniach raczej nie uzywany, wiec nie zawracajmy sobie nim glowy. hInstance to oczywiscie uchwyt realizacji naszej aplikacji (patrz kilka stron wyzej :), natomiast ostatni to wskaznik do pewnej struktury. Ma ona min. zastosowanie przy aplikacjach wielodokumentowych (MDI), ale przeciez w grach nie bedziemy tego stosowac (no chyba, ze ktos wymysli jakis sensowny sposob wykorzystania MDI to wtedy to opiszemy :).
ShowWindow(hWnd, SW_SHOW);
Mysle, ze dla inteligentnych ludzi jest to choc odrobine zrozumiale. ShowWindow(), oczywiscie wiadomo, ze ma nam pokazac okno, ktore jest identyfikowane przez uchwyt hWnd a rodzajow tego pokazywania mamy...(niech no spojrze do help'a :), no tak, cos kolo trzynastu 13 !. Bardziej ambitni moga poeksperymentowac :).
while(GetMessage(&msg, NULL, 0, 0)) Jesli while to wiadomo, ze to...no tak petla ! I to nie byle jaka petla, ale petla komunikatow. GetMesage() rozpoczyna petle komunikatow. Pobiera ona komunikat z kolejki komunikatow i umieszcza dane komunikatu ( prametry :) w strukturze MSG. Pozostale parametry nie beda nas zbytnio obchodzic. Powiem tylko, ze drugi jest uchytem okna zawsze ustawianym na NULL, jesli przechwytujemy komunikaty dla naszego okna. Tajemnicza funkcja TranslateMessage() przekazuje strukture MSG z powortem do Windows w celu specjalnego przetwarzania komunikatow klawiatury. Kiedys jeszcze moze o tym powiemy, na razie przyjmijmy na wiare, ze tak ma byc i koniec :). DispatchMessage() zwraca strukture MSG do Windows i wtedy to wlasnie Windows wysyla wlasciwy komunikat do wlasciwej procedury okna (u nas MainWndProc()). Po obsludze tego komunikatu w naszej funkcji obslugi, nasteuje powrot na poczatek petli i opbierany jest nastepny komunikat z kolejki. Dzieje sie do czasu nadejscia komunikatu WM_QUIT. Kiedy takie cos ma miejsce, funkcja GetMessage() zwraca wartosc zero a to oznacza co? No wlasnie - koniec petli, a jak koniec petli to koniec obslugi komunikatow, a jak koniec obslugi komunikatow to koniec aplikacji, a jak koniec aplikacji to...prawie koniec lekcji na dzisiaj :). Na koncu programu nastepuje zwrocenie wartosc pola struktury MSG - wParam, a jako, ze ostatni obsluzony komunikat to WM_QUIT to parametr ten oznacza kod wyjscia z programu (patrz help). MainWndProc
Teraz dowiemy sie w jaki sposob obslugiwane sa komunikaty w naszej petli komunikatow. Jak widac w przykladzie funkcja obslugi komunikatow jest zdefiniowana u nas w sposob nastepujacy: LRESULT to znowu nowy specjalny typ (32 bitowa wartosc, ktora moze zwracac wlasnie procedura okna (min.). Funkcja CALLBACK oznacza dla nas tyle, ze funkcja ta jest wywolywana zupelnie bez naszej wiedzy. Zauwazmy, ze nigdzie w programie nie ma jawnego jej wywolania, robione jest to caly czas w trakcie jego dzialania. I coz takiego ta funkcja pobiera? Jak widzimy, po pierwsze uchwyt naszego okna (tzn. tego, do ktorego kierujemy komunikaty), czyli glowne okno naszej aplikacji. Po drugie uMsg, czyli komunikat (czym jest komunikat nie musimy chyba juz nikomu tlumaczyc?). Tutaj jest przekazywany po prostu numer komunikatu (kazdy ma swoj) jako liczba UINT. Parametry wParam i lParam to nic innego jak parametry komunikatu :). A coz takiego mamy w srodku naszej funkcji? Nic prostszego juz chyba wymyslic nie mozna:
switch (uMsg)
case WM_DESTROY:
case WM_KEYDOWN:
default:
Coz widzimy? Najzwyklejsza na swiecie instrukcja switch, dla ktorej parametrem jest numer nadchodzacego komunikatu ( uMsg). Po nadejsciu komunikatu, w naszej procedurze okna dzieki tej instrukcji jest wykonywany odpowiedni kod dla odpowiedniego komunikatu. W tym momencie omowimy sobie kilka podstawowych komunikatow, bez ktorych dzialanie naszej aplikacji byloby zgola utrudnione. WM_CREATE - wywolywany podczas tworzenia okna (funkcja CreateWindow). W obsludze tego komunikatu mozemy umiescic co nam sie tylko podoba (no, moze prawie wszystko :), ale najczesciej sa to inicjalizacje zmiennych, pobieranie kontekstow rysowania, itp. jednym slowem wszystko co powinno byc zrobione na poczatku. WM_DESTROY - jak sie slusznie domyslamy przeciwienstwo komunikatu WM_CREATE. Tutaj niszczymy lub zwalniamy wszystko co stworzylismy zanim zamkniemy aplikacje. Tutaj mamy tez przyklad uzycia specyficznej funkcji do wysylania komunikatow, a mianowicie PostQuitMessage(). Funkcja ta wysyla komunikat z procedury okna do... no wlasnie :), do procedury okna??? Przeplyw komunikatow w Windows jest troche dziwny, jednak ma swoja logike, ktora jeszcze bedziemy mieli okazje omowic. Na razie poznajmy podstawy, ktore potem posluza do poznania tajemnic Windows. Funkcja ta wysyla jeden jedyny komunikat o nazwie WM_QUIT, a co on powoduje to pamietamy chyba z opisu funkcji GetMessage()?
WM_KEYDOWN - tym razem jakis wiekszy stwor, przy okazji ktorego poznamy nastepna chora rzecz w Windows a mianowicie cos takiego jak parametry komunikatu. I to tyle. W naszej aplikacji obslugujemy tylko 3 komunikaty. A co sie dzieje z stekami innych? (bo naprawde sa ich setki a moze nawet tysiace? :). Te tajemnice z pomoze nam rozwiazac funkcja DefWindowProc(). Jak widac pobiera ona takie same parametry jak MainWndProc() i w rzeczywistosci jest tym samym. Jest to tez procedura obslugi okna, z ta mala roznica, ze wykonuje ona domyslna obsluge komunikatow. Co to oznacza? Otoz, wszystkie nie obsluzone przez nasza aplikacje komunikaty trafiaja wlasnie do DefWindowProc(), ktora obsluguje je wszystkie na sobie tylko znane, tajemnicze sposoby. Nas jednak cieszy to, ze nie obsluzone komunikaty nie psuja nam aplikacji, bo nimi aplikacja zajmuje sie juz sama bez naszej wiedzy. I to by bylo na tyle podstawowej wiedzy na ten raz. Wiemy jak dziala aplikacja Windows, co to sa komunikaty i ich petla, wiemy jak je obslugiwac i poznalismy pierwsze ogromniaste struktury. Nastepnym razem zajmiemy sie rysowaniem, czyli tym co tygrysy lubia najbardziej :) oraz dowiemy sie co to takiego timer i jak to dziala. Poznamy tez kilka nowych komunikatow. Oczywiscie w razie niejasnosci prosze o pytania na mail'a, a postaram sie odpowiedziec jesli tylko bede wiedzial ;). No to na dzisiaj tyle.
Autorem artykułu jest: |
||
[ Kurs API: Rysowanie, GDI ] |
||
No i znowu razem :), witam, witam. Pewnie chcielibyscie wiedziec co dzisiaj. Juz spiesze z odpowiedzia na to jakze wazne pytanie. Dzisiaj bedzie troche o rysowaniu (dowiemy sie miedzy innymi co to jest GDI, kontekst urzadzenia, omówimy komunikat WM_PAINT). Jak zwykle zaczynamy, bo nie lubie sciemniania (ale kto lubi)? GDI Wielu z was pewnie wiele razy slyszalo juz o czyms takim jak GDI i zadawalo sobie odwieczne pytanie co to jest za diabel? Teraz wlasnie powiem co to za diabel i mam nadzieje, ze utrwali wam sie to na tak dlugo, jak dlugo bedzie istnial Windows :). GDI (czyli Graphics Device Interface), to w wolnym tlumaczeniu "Interfejs urzadzenia graficznego", a na chlopski ( nie obrazajac kobiet :), rozum biorac jest to cos co pozwala nam rysowac w Windows. Mówiac "rysownie" mamy na mysli nie tylko to pojete przez wiekszosc rysowanie na ekranie w oknie, czy gdzie tam wam sie podoba. Rysownie to równie dobrze moze byc drukowanie na drukarce. Zapamietujemy od tego momentu raz na zawsze! Czy rysujemy na oknie czy drukujemy na drukarce dla Windows jest to wszystko jedno. A dlaczego sie tak dzieje? Przy projektowaniu Windows panowie z Microsoft'u mysleli tak bardzo, ze az niektorym robilo sie momentami za goraco, az w koncu wymyslili, ze "ulepsza" Windows. Skutki tego ulepszania widac dzisiaj, gdy przychodzi nam zlozyc do kupy komputer najnowszej generacji z PIII i innymi bajerami XXI - go wieku na pokladzie. W polowie przypadkow na pewno nie uda sie za pierwszym razem uruchomic tego jak nalezy. Ulepszenie Windows mialo polegac na tym, aby nam programistom piszacym aplikacje dla Windows umilic i tak juz skomplikowane zycie. Po to stworzono wlasnie cos takiego jak GDI. Jesli dzisiaj piszemy banalna aplikacje rysujaca na oknie trójwymiarowa animacje z renderingiem raytrace'owym, to w kazdym przypadku, niezaleznie od posiadanej karty graficznej wywolujemy zawsze te same funkcje, funkcje GDI. Tu wlasnie tkwi cala sila a zarazem slabosc tego interfejsu. Dzieki niemu nie musimy sie martwic o to jaka mamy karte grafiki. Karta taka, dzieki swoim sterownikom, przeznaczonym oczywiscie dla odpowiedniej wersji Windows wykonuje posrednio nasze polecenia wlasnie za pomoca GDI. Jest to interfejs, czyli... hmm, jakby tu powiedziec... o wiem, POMOST, tak to dobre slowo :), pomost pomiedzy nasza karta grafiki a nami - rysownikami. Taki interfejs udostepnia nam mnostwo funkcji, którymi mozemy dysponowac wedle wlasnego uznania. Dzieki GDI funkcje te sa zawsze te same, bowiem ich dostarcza system operacyjny, co jest chyba oczywiste, sa to oczywiscie funkcje API. A jak te funkcje sa wykonywane przez nasz a karte grafiki to juz slodka tajemnica interfejsu GDI i sterownika karty graficznej, nas to w ogole nie obchodzi, bo i nie powinno. Slabosc tego rozwiazania to oczywiscie jego powolnosc, ale tak bylo od zawsze i tak zawsze bedzie. Im cos jest bardziej uniwersalne tym jest wolniejsze. Ale tak mowimy o tym GDI i mowimy, ale ktos w koncu zada pytanie, co to jest to GDI ? Otoz GDI to (tu znowu brakuje slow :)) biorac najprosciej jak mozna to zestaw funkcji, ktore uzmozliwiaja:
Funkjce GDI umozliwiaja nam tworzenie grafiki raczej statycznej (wlasnie okinen, menu itp.) niz animacji. Do tego celu posluzy nam poznany w pozniejszym terminie wlasnie pakiet DirectX i OpenGL, ktore daja zacznie wieksze mozliwosci niz wspomniane funkcje GDI. Ale dobrze bedzie znac choc podstawy, gdyz niektore z funkcji mozemy z powodzeniem wykorzystywac, niektore nawet musimy przy uzyciu tych bibliotek. Gdy chcemy cos narysowac na ekranie komputera lub wydrukowac cos na drukarce musimy najpierw dobrac sie do naszego urzadzenia wyjsciowego. Do tego wlasnie sluzy kontekst urzadzenia. Wielu z was pewnie w tym momencie zadaje sobie pytanie co to takiego ten kontekst urzadzenia. Owo "cos" to wlasciwie struktura danych przechowywana przez GDI w ktorej zawarte sa wlasciwosci danego urzadzenia. W przypadku ekranu kontekst urzadzenia jest kojarzony z kazdym oknem osobno, innymi slowy kazde okno ma swoj kontekst W strukturze takiej przechowywane sa rozne informacje o atrybutach graficznych takich jak biezacy kolor rysowania, grubosc linii lub jej styl, rodzaj czcionki itp. Wlasciwie potrzebujemy nie samego kontekstu ale jego uchwytu. Majac uchwyt do kontekstu urzadzenia mozemy uzywac funkcji GDI. Wiekszosc (jesli nie wszystkie) funkcje GDI potrzebuja tego uchwytu aby moc poprawnie pracowac. Gdy uzyskujemy uchwyt kontekstu urzadzenia struktura powyzsza jest wypelniana odpowiednimi wartosciami, ktore oczywiscie mozemy pobierac i zmieniac wykorzystujac do tego odpowiednie funkcje GDI. Po narysowaniu czegokolwiek powinnismy zwolnic kontekst, ktorego uzywalismy. Gdy zwolnimy taki uchwyt to nie mozemy go juz dalej uzywac, wiec jesli najdzie nas nagle ochota jeszcze raz cos narysowac musimy go znowu pobrac, zrobic swoje, zwolnic i tak w kolko. Jak otrzymac uchwyt urzadzenia? Otoz juz przechodzimy do sedna sprawy. Musimy oczywiscie do tego celu wykorzystac funkcje GDI. Mozna to zrobic na kilka sposobow, ale podam ten, ktory potem przyda nam sie najbardziej. Jesli dobrze przegladneliscie podreczniki pomocy to juz pewnie wiecie co to za funkcja, natomiast tym, ktory sie nie chcialo pragne powiedziec o funkcji o jakze pieknie brzmiacej nazwie: GetDC(HWND). Funkcja ta jako swoj argument pobiera uchwyt okna, dla ktorego chcemy uzyskac jakze pozadany przez nas uchwyt kontekstu urzadzenia. Uzyskujemy uchwyt konekstu obszaru roboczego okna (roboczy obszar to cale okno bez belki tytulowe i ramek) i w tym momencie mozemy juz uruchomic niezglebione poklady naszej wyobrazni w celu wygenerowania pieknej animacji. Zanim jednak to nastapi powiem jeszcze kilka slow o kontekscie. Jak powiedzialem wczesniej po jego pobraniu i wykorzystaniu (brzmi troche nieladnie, ale coz...) powinnismy go zwolnic, a sluzy do tego funkcja ReleaseDC(), ktora tym razem pobiera dwa argumenty, jeden z nich to uchwyt naszego kontekstu a drugi to uchwyt okna, z ktorym jest zwiazany nasz kontekst. Funkcje te powinny zawsze byc wywolywane parami. Jesli kogos interesuje to istnieje taka funkcja jak GetWindowDC(), ktora tez zwraca nam kontekst urzadzenia dla danego okna, ale tym razem mozemy juz malowac na calym oknie, wlacznie z belka tytulowa itd. ale wymaga to obslugi dodatkowego komunikatu (jak kogos interesuje, niech napisze), adres ponizej. Oczywiscie zwalnianie tego uchwytu odbywa sie za pomoca funkcji ReleaseDC(). Pamietajmy wiec, mamy w sumie trzy funkcje:
Dobrze, skoro wiemy juz (mniej wiecej) co to GDI, kontekst urzadzenia, mozemy przystapic do tak dlugo wyczekiwanego momentu jak narysowanie pierwszej linii w naszym oknie :). Zaczynajmy wiec! Rysowanie Funkcji GDI jest cale mnostwo, my przyjrzymy sie jednak tylko kilku. Biblioteki takie jak DirectX czy OpenGL dysponuja wlasnymi funkcjami rysujacymi, ktore sa szybsze i bardziej uniwersalne wiec nie bedziemy sie wglebiac w GDI, bo nie ma to wiekszego sensu. Jesli chcecie poznac wiecej szczegolow to napiszczie o czym chcielibyscie przeczytac a postaram sie to zrobic w miare mozliwosci. Jesli chcemy malowac w Windows musimy wiedziec jeszcze co nieco o ukladzie wspolrzednych tam panujacym. Standardowo os X to os pozioma liczona od lewej do prawej, natomiast os Y to os pionwa liczona z gory na dol. Nie ma oczywiscie pikseli o wspolrzednych ujemnych! Malowac mozna od zera do maksymalnego zakresu liczby int (w wiekszosci przypadkow), jednak zazdroszcze temu kto potrafi wycisnac ze swojego sprzetu taka rozdzielczosc :). Orientacje ukladu wspolrzednych mozna oczywiscie zmieniac, ale dla nas nie ma to wiekszego znaczenia bowiem jak wszystko inne, nasze ukochane biblioteki maja wlasne uklady wspolrzednych a co najwazniejsze sa to uklady w trzech wymiarach!!! Dobrze, dobrze, nie wybiegajamy az tak w przyszlosc, zajmijmy sie na razie starym poczciwym GDI. Zalozmy wiec, ze naszla nas ochota na narysowanie np. w oknie prostokata. Co zrobic? Nic prostszego: pobrac uchwyt kontekstu urzadzenia, wywolac jedna funkcje GDI, zwolnic uchwyt i gotowe, prawda, ze proste? No wiec jaka funkcja? Nie moze byc chyba nic innego jak:
>Dokladny opis jak zwykle, w helpi'e :), my tylko napomknijmy, ze do narysowania prostokata wystarcza nam jedynie dwa punkty, lewy gorny i prawy dolny i w takiej tez kolejnosci podajemy wspolrzedne dla naszej funkcji. Po wywolaniu takiej funkcji powinnismy otrzymac prostokat o podanych wspolrzednych (liczonych oczywiscie w stosunku do wspolrzednych okna a nie ekanu). Do innych tego typu funkcji mozemy zaliczyc miedzy innymi:
Komunikat WM_PAINT Jedno co mozna powiedziec to nie ma rysowania w Windows bez komunikatu WM_PAINT (no prawie, ale to już inna bajka :)). Wszystko co chcemy narysowac, trzeba wywolac bezposrednio (lub posrednio, za pomoca funkcji) w obsludze tego komunikatu. Windows wysyla komunikat WM_PAINT do aplikacji za kazdym razem, gdy okno wymaga odmalowania. Dzieje się tak oczywiscie przy starcie aplikacji, po zmianie rozmiarow jej okna, po przykryciu jej przez inne okno i przywroceniu jej z powrotem, po zminimalizowaniu okna i jego ponownym zmaksymalizowaniu itp. Jeli chcemy wywolac na sile komunikat WM_PAINT to możemy to zrobic oczywicie na kilka sposobow. Pierwszy to wyslanie do aplikacji naszego komunikatu jawnie (SendMessage() lub PostMessage()), jednak nie uzywajmy tego brzydkiego nie eleganckiego sposobu. Lepszym będzie wywolanie metody InvalidateRect(). Co robi taka metoda ? Już wyjasniam. Otoz: powoduje ona dodanie pewnego prostokata do regionu okna, wymagajacego uaktualnienia. Co do czego ? Region uaktualnienia mowi o tym jaka czesc okna ma zostac odrysowana. Wbrew pozorom nie musi to być caly obszar klienta, co nas powinno bardzo cieszyc. Po wywolaniu obslugi komunikatu WM_PAINT zostanie odmalowany taki prostokat, jaki mamy aktualnie zawarty w obszarze uaktualniania, przewaznie cale okno. Funkcja InvalidateRect() powoduje wywolanie komunikatu WM_PAINT a co za tym idzie odmalowanie obszaru uniewaznionego. Po namalowaniu czegokolwiek powinnismy zatwierdzic obszar funkcja ValidateRect(). Bitmapy Dzialanie calego systemu Windows nie moze sie obejsc bez czegos takiego jak bitmapa. W Windows jest to pojecie troche blednie uzywane, ponieważ z definicji bitmapa jest jest obrazem bialo-czarnym (jeden bit to jeden piksel), ale przyjelo się tak a nie inaczej i już zostalo. W zasadzie kolorowe bitmapy to pixmapy a nie bitmapy, ale nie badzmy drobiazgowi, skoro już mamy jaki taki standard w nazewnictwie to niech już tak zostanie :). Oprocz funkcji rysujacych możemy oczywicie uzyc do malowania w Windowsie bitmap. Niech ktos sprobuje namalowac swoja podobizne funkcjami typu line czy rectangle :). Bitmapy uzywamy zawsze w ten sam sposób, ale mozemy ja pobierac do naszych celow w dwojaki sposób. Pierwszy to taki, ze dolaczamy nasza bitmape do naszego pliku zasobow, czego ja jednak nie pochwalam, no bo rosnie nam niepotrzebnie plik exe, jak sprobojemy sobie wlozyc do naszego programu bitmape 800x600x32 bity to nasz exec urosnie prawie jak programy microsoftu a na tym nam chyba nie zalezy. Ja wole ladowac je z dysku, z okreslonego katalogu, ale kazdy może robic jak mu wygodnie. Wiec co zrobic jeli mamy już bitmape ze zdjeciem naszej dziewczyny ;) i pragniemy umiescic ja w naszym oknie ? Pierwsze co musimy zrobic to zaladowac nasza bitmape do pamieci. Robi to funkcja LoadBitmap(). Ale zanim zaladujemy bitmape do pamieci musimy tam stworzyc kontekst urzadzenia, kompatybilny z naszym oknem. Sluzy do tego funkcja CreateCompatibleDC(HDC). Funkcja ta pobiera uchwyt aktualnego kontekstu urzadzenia i tworzy w pamieci taki sam kontekst z identycznymi atrybutami jak nasze urzadzenie wyswietlajace. Po co nam to ? Otoz do wyswietlania naszej bitmapy będziemy uzywac funkcji BitBlt (bit block transfer), która kopiuje (w zasadzie transferuje, ale mniejsza o to :)) bajty z pamieci do pamieci. Funkcja ta przenosi bajty z jednego kontekstu urzadzenia do drugiego, wykonujac po drodze dodatkowe operacje (jeli sobie tego zazyczymy oczywicie). Tak wiec aby moc wyswietlac bitmape musimy posiadac kontekst, którego będziemy kopiowac i kontekst na który będziemy kopiowac (ten mamy z funkcji GetDC() lub GetWindowDC()). Aby nie było zadnych niespodzianek ( he, he, nadzieja matka glupich :) tworzymy wlasnie kompatybilny kontekst urzadzenia z naszym posiadanym wyzej wymieniona funkcja. Jeli już mamy uchwyt do kontekstu kompatybilnego, możemy przystapic do ladowania bitmapy z pliku. Wywolajmy wiec dlugo oczekiwana funkcje LoadBitmap(). Po jej wywolaniu otrzymamy uchwyt do naszej bitmapy (typ HBITMAP) i jest to nic innego jak nadal numer (jak każdy uchwyt, jeli dobrze pamietamy). I tu w zaleznosci od tego czy mamy bitmape w pliku zasobow czy na dysku robimy tak:
natomiast jeli chcemy ja zaladowac z dysku to tak:
Jeli już mamy uchwyt bitmapy to możemy przystaic do jej wyswietlenia. Zanim jednak ujrzymy nasza dlugo wyczekiwana kobiete na ekranie monitora musimy jeszcze wyselekcjonowac jakiego obiektu pragniemy uzyc z kontekstu kompatybilnego, który wczesniej utworzylismy w pamieci. Robimy to funkcja SelectObject(HDC, HBITMAP). Pierwszy argument to adres kompatybilnego kontekstu, utworzonego przez nas, drugi argument to uchwyt bitmapy, która jest umieszczona w tym kontekscie. Jeli już mamy wyselekcjowana nasza bitmape to nie pozostaje nam nic innego jak zrobic: BitBlt(HDC, INT, INT, INT, INT, HDC, INT, INT, DWORD) - dokladny opis można znalezdz w helpie oczywicie. Pierwszy HDC to ten na który kopiujemy, czyli kontekst naszego okna, drugi to tez z którego kopiujemy (kompatybilny w naszym przypadku). Pierwsze cztery liczby to wspolrzedne obrazka, jakie będzie zajmowal po skopiowaniu go do okna (możemy go np. rozciagnac, scisnac, obrocic itp.), drugie to lewy gorny rog obrazu zrodlowego. Nader ciekawy jest ostani parametr, ktory okresla tzw, kod operacji ROP (raster operation code). Mowi on o tym w jaki sposób piksele obrazu zrodlowego zostana polaczone z pikselami obrazu docelowego. I tu mamy multum mozliwosci. Możemy po prostu skopiowac nasze piksele w miejsce starych (najczesciej ), wykonac jakies operacje (dodawanie, mnozenie itp.) sciemnic obraz lub rozjasnic i kilka innych, po szczegoly oczywicie należy się udac do nieocenionego helpa. No to skoro już wiecie z czym to sie wszystko je możecie sprobowac napisac prosciutka animacyjke, a może jeszcze cos wiecej. Oczywicie nie będzie to jeszcze żaden 3D ale zawsze to cos. W przyszlym odcinku powiemy sobie cos nie cos o timerze no i powoli zaczniemy zaglebiac się w istote grafiki 3D.
Autorem artykułu jest: |