LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows". ________________________________________________________________ Z tej lekcji dowiesz się, czym różnią się kompilatory przeznaczone dla pracy w środowisku Windows. ________________________________________________________________ W IDE i w sposobie zachowania zaszły istotne zmiany. Posługując się Turbo C++ z pakietu BORLAND C++ 3.0 lub BCW z pakietu 3.1 możemy korzystać z uroków i usług Windows szerzej niż do tej pory. Możemy otwierać wiele okien i uruchamiać bezpośrednio z poziomu IDE okienkowe aplikacje. W głównym menu kompilatora zaszły pewne zmiany (sygnalizujące obiektowo- i okienkowo - zorientowaną ewolucję pakietów Borlanda), na które warto zwrócić uwagę. Zniknęło menu Debug (co wcale nie oznacza, że nie możemy korzystać z Debuggera), pojawiło się natomiast nowe menu Browse (przeglądanie). Rozkazy, których tradycyjnie szukaliśmy w menu Debug zostały rozrzucone do innych menu. I tak: Menu Compile zawiera: Compile (kompilacja do *.OBJ), Make (kompilacja i konsolidacja do *.EXE), Link (konsolidacja bez powtórnej kompilacji), Build all (konsolidacja wszystkich modułów), Information... (informacja o przebiegu kompilacji), Remove messages (usuwanie komunikatów z pliku wynikowego) Menu Run zawiera: Run (uruchomienie i ewentualna rekompilcja), Arguments... (argumenty uruchomieniowe z wiersza rozkazu), Debugger (zamiast w Debug - TU!) Debugger arguments... (argumenty dla Debuggera) Menu Project zawiera: Open project - otwórz (nowy lub istniejący) plik projektu, Close project - zamknij projekt, Add item... - dodaj element (plik) do projektu, Delete item - usuń element (plik) z projektu, Include ˙˙files... ˙˙- ˙˙podaj ˙katalog ˙zawierający ˙dodatkowe dołączane do programu pliki nagłówkowe *.H W menu Options (zestaw znany już z Borland C++) warto zwrócić uwagę na pewną dodatkową możliwość. Jak wiemy z doświadczenia, uruchamiając program często dokonujemy zmian i korekt w pliku żródłowym *.C, czy *.CPP. Znacznie rzadziej jednak zmieniamy zestaw dołączanych do programu plików nagłówkowych *.H. Wiemy również, że kompilacja tych właśnie plików nagłówkowych zajmuje często lwią część czasu całej kompilacji i konsolidacji programu. Borland zauważył to i w okienku dialogowym: Options | Compiler | Code generation --> Code Generation Options umieścił opcję Pre-compiled headers (pliki nagłówkowe wstępnie skompilowane wcześniej - i tylko jeden raz). Szczególnie w przypadku aplikacji okienkowych może to znacznie przyspieszyć proces uruchamiania i "szlifowania" naszych programów. Nie ma jednak nic za darmo. Borland/Turbo C++ po skompilowaniu plików nagłówkowych tworzy na dysku roboczy plik *.SYM nadając mu nazwę zgodną z nazwą bieżącego projektu (jest to zwykle nazwa głównego modułu *.CPP) i do poprawnego działania wymaga kilkadziesiąt lub nawet kilkaset kilobajtów dodatkowej przestrzeni na dysku. [!!!]UWAGA ________________________________________________________________ Jeśli przenosisz projekt na dyskietkę i tam kontynuujesz pracę nad projektem, pamiętaj, że może zabraknąć miejsca na prekompilowany plik .SYM. ________________________________________________________________ Czytelnik zechce sam sprawdzić w jakim stopniu przyspieszy to kompilację naszego własnego programu proceduralno - zdarzeniowego WINPZ1.CPP: WINZ1.CPP. Jednomodułowa aplikacja proceduralno - zdarzeniowa dla Windows. ________________________________________________________________ #include #pragma argused long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { WNDCLASS Okno1; MSG komunikaty; HWND NrOkna; LPSTR LongPtr1 = "Okno 1"; LPSTR lp2 = "AM: BC++ 3..4/Reczne sterowanie (1)"; if (hPrevInstance == 0) { Okno1.style= CS_HREDRAW | CS_VREDRAW ; Okno1.lpfnWndProc= WndProc; Okno1.cbClsExtra = 0; Okno1.cbWndExtra= 0; Okno1.hInstance = hInstance; Okno1.hCursor = LoadCursor(0, IDC_CROSS ); Okno1.hbrBackground= GetStockObject(WHITE_BRUSH ); Okno1.lpszMenuName= 0; Okno1.lpszClassName= LongPtr1; if (!RegisterClass(&Okno1)) return 0; } NrOkna = CreateWindow(LongPtr1, lp2, WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX | WS_VSCROLL | WS_MAXIMIZEBOX, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); ShowWindow(NrOkna, nCmdShow); UpdateWindow(NrOkna); while (GetMessage(&komunikaty, 0, 0, 0)) { TranslateMessage(&komunikaty ); DispatchMessage(&komunikaty ); } return 0; } long FAR PASCAL WndProc (HWND NrOkna, unsigned KomunikatWindows, WORD wParam, LONG lParam) { HDC NrKontekstu; PAINTSTRUCT struktura_graficzna; RECT prostokat; switch(KomunikatWindows) { case WM_PAINT: { NrKontekstu = BeginPaint(NrOkna, &struktura_graficzna); GetClientRect(NrOkna, &prostokat); TextOut(NrKontekstu,80,50, ": Reczne sterowanie:", 20 ); TextOut(NrKontekstu, 5,70, "Tu -->", 6); TextOut(NrKontekstu, 5, 85, "Blad:", 5); TextOut(NrKontekstu,75,70, "-----------------------------", 40); TextOut(NrKontekstu,30,110, "Programowanie proceduralno - zdarzeniowe.", 41 ); TextOut(NrKontekstu,30,135, "Szablon moze zostac rozbudowany o inne funkcje.", 47 ); TextOut(NrKontekstu,30,180, "RECZNIE panujemy np. nad:", 25 ); TextOut(NrKontekstu,20,220, "paskiem tytulowym okna, tytulem ikonki...", 41); TextOut(NrKontekstu, 100, 250, "!KONIEC - [Alt]+[F4]", 20); EndPaint(NrOkna,&struktura_graficzna); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(NrOkna,KomunikatWindows,wParam,lParam); } return 0; } Program demonstruje opisane wyżej mechanizmy, może być uruchamiany wielokrotnie i sprowadzony do ikony. Z uwagi na brak zdefiniowanych dodatkowych zasobów (brak w projekcie plików: .RC - resources - zasoby .ICO - ikona .DEF - definicji .PRJ lub .IDE - projektu .DSK - konfiguracyjnego itp.) podczas kompilacji programu wystąpią dwa komunikaty ostrzegawcze. Komunikaty te można zignorować. A oto druga przykładowa aplikacja w tym samym stylu. Tym razem funkcja okienkowa reaguje na naciśnięcie lewego klawisza myszki, co powoduje wygenerowanie komunikatu WM_LEFTBUTTONDOWN. Program WINZ-2.CPP ________________________________________________________________ #include #include #pragma argused char napis[10]; int X, Y; LONG FAR PASCAL WndProc (HWND, WORD, WORD, LONG); int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { WNDCLASSwndClass; MSGmsg; HWNDhWnd; LPSTR Lp1 = "Mysza1"; LPSTR lp2 = "WINPZ2: Wykrywanie Lewego Klawisza Myszki"; if (!hPrevInstance) { wndClass.style= CS_HREDRAW | CS_VREDRAW ; wndClass.lpfnWndProc= WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra= 0; wndClass.hInstance = hInstance; wndClass.hIcon = 0; wndClass.hCursor= LoadCursor(0, IDC_ARROW ); wndClass.hbrBackground= GetStockObject(WHITE_BRUSH ); wndClass.lpszMenuName= 0; wndClass.lpszClassName= Lp1; if (!RegisterClass(&wndClass)) exit(1); } hWnd = CreateWindow(Lp1, lp2, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg ); DispatchMessage(&msg ); } return 0; } LONG FAR PASCAL WndProc (HWND hWnd, WORD Message, WORD wParam, LONG lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; switch(Message) { case WM_SIZE: hDC = GetDC( hWnd ); TextOut(hDC, 50, 100, "Wykrywanie nacisniecia", 22); TextOut(hDC, 50, 120, "lewego klawisza myszki.", 23); TextOut(hDC, 20, 140, "Komunikat o zdarzeniu: ", 22); TextOut(hDC, 20, 156, "Left Button Down - LBUTTONDOWN", 31); TextOut(hDC, 50, 170, "Po wcisnieciu klawisza,", 23); TextOut(hDC, 50, 190,"w biezacej pozycji kursora, pojawi sie napis <-- Tu!.", 52); ReleaseDC(hWnd, hDC); break; case WM_PAINT: hDC = BeginPaint(hWnd, &ps); TextOut(hDC, X,Y, napis, strlen(napis)); EndPaint(hWnd, &ps); break; case WM_LBUTTONDOWN: strcpy(napis,"<-- Tu !"); X = LOWORD(lParam); Y = HIWORD(lParam); InvalidateRect(hWnd, 0, TRUE); UpdateWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, Message, wParam, lParam); } return 0; } Plik nagłówkowy STRING.H pojawia się ze względu na obecność funkcji strlen() wyznaczającej długość napisu. Zmienne X i Y to bieżące (względne) współrzędne kursora myszki w momencie naciśnięcia klawisza. Program demonstruje następujące efekty: X = LOWORD(lParam); - przekazanie współrzędnej X przy pomocy parametru lParam (LOWORD to LOw WORD of the double word - młodsze słowo podójnego słowa). Y = HIWORD(lParam); Analogicznie - przekazanie współrzędnej Y (HIgh WORD of the double word). Funkcja InvalidateRect() powoduje uznanie prostokąnego pola za nieaktualne. Funkcja UpdateWindow() "odświeża" okno. Dzięki temu tandemowi napis znika i pojawia się w nowym miejscu. PROJEKT. Aby skompilować powyższe programy przykładowe należy: 1. Uruchomić kompilator C++. 2. Załadować do okienka edycyjnego (File | Open) plik z tekstem żródłowym programu. 3. Wybrać rozkaz Compile z menu Compile. Przed kompilacją i konsolidacją (jeśli był inny) ustawić sposób tworzenia kodu wynikowego [Windows EXE]. Kompilacja przebiegnie poprawnie (pamiętaj o Opcjach i Katalogach), mimo to pojawią się jednak dwa komunikaty ostrzegawcze. W okienku "Compile Status" (stan/przebieg kompilacji) pojawi się zawartość: Lines 3832 (znakomita większość to WINDOWS.H, prekompilacja byłaby celowa) Warnings: 1 Errors: 0 Jeśli wybierzesz klawisz [OK] w okienku "focus" (aktywność) zostanie przekazana do okienka komunikatów "Message" a tam pojawi się napis: Warning: Parameter 'lspzCmdLine' is never used. Wskaźnik do parametrów uruchomieniowych programu (Arguments) pobieranych z wiersza rozkazu nie został ani raz użyty w programie. Na to nic nie możemy poradzić. Po prostu argumenty uruchomieniowe nie są nam potrzebne. Wykonujemy więc "klik" (przekazanie "focusa") w okienku edycyjnym i możemy przejść do następnej czynności: 4. Konsolidacja: Compile | Link. W okienku "Message" znów pojawi się ostrzeżenie: Linker Warning: No module definition file specified: using defaults. (brak wyspecyfikowanego pliku definicji .DEF; stosuję wartości domyślne) I tu już możemy coś zaradzić. Możemy zatem pokusić się o stworzenie naszego pierwszego pliku definicji (nazwa jest trochę myląca - chodzi o zdefiniowanie sposobu wykorzystania zasobów środowiska Windows). Aby utworzyć plik .DEF (jest to plik ASCII) należy: 1. Otworzyć nowe okienko edycyjne (nie wychodząc z IDE): File | New Otworzy się okienko NONAMExx.CPP. Ta nazwa nie jest oczywiście najodpowiedniejsza, więc umieszczamy plik we właściwym katalogu (tym samym, co główny program *.CPP) przy pomocy rozkazu File | Save as... i nadajemy plikowi stosowną nazwę i rozszerzenie *.DEF. Okieno pozostaje puste, ma jednak "focus" i nową nazwę, np. C:\..\PR.DEF. 3. Redagujemy nasz pierwszy plik definicji, np. tak: NAME JAKAKOLWIEK // <-- nazwa aplikacji DESCRIPTION 'Opis: A. MAJCZAK, BC C++ 3...4' EXETYPE WINDOWS // <-- EXE dla Windows CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 4096 // <-- sterta 4 KB STACKSIZE 5120 // <-- stos 5 KB _______________________________________________________________ UWAGA: W przypadku tworzenia bibliotek .DLL dane muszą mieć status SINGLE (pojedyncze) zamiast MULTIPLE (wielokrotne). Użycie tu słowa MULTIPLE pozwoli nam na wielokrotne uruchamianie aplikacji. ________________________________________________________________ Możnaby tu zapytać - po co to robić, skoro używamy standardowych wartości i obecność tego pliku nie wnosi nic nowego do sposobu działania naszego programu? Odpowiedź jest prosta. Mając taki plik będziemy mogli prześledzić stadia tworzenia tzw. projektu (w BC++ 4 bez tego ani rusz). Zapisujemy zatem plik na dysk: 4. File | Save. (plik .DEF zostaje zapisany na dysku). Ponieważ pracujemy w środowisku Windows, okno edycji pliku *.DEF możemy traktować podobnie jak każde inne okno. Najwygodniej zatem przejść do okna edycji głównego pliku żródłowego *.CPP przy pomocy własnego menu systemowego tegoż okna. 5. Menu Systemowe [-] | Zamknij. I możemy przystąpić do tworzenia projektu składającego się z dwu plików: *.CPP i *.DEF. Jeśli, dla przykładu, przyjmiemy w tym miejscu, że nasze dwa moduły nazywają się: WINZ2.CPP i WINZ2.DEF i są przechowywane w katalogu głównym dysku C:\ , kolejność czynności powinna być następująca: 1. Rozwijamy menu Project ([Alt]+[P] lub myszką). 2. Wybieramy z menu rozkaz Open Project... (Utwórz projekt). Pojawia się okienko dialogowe Open Project File z domyślnym rozszerzeniem *.PRJ (w BC 4+ - *.IDE). 3. Do okienka File Name: wpisujemy nazwę pliku z opisem projektu: np. WINZ2.PRJ. W dolnej części ekranu otwiera się okienko Project: WINZ2 4. Wybieramy z menu Project rozkaz Add item... (dodaj element do projektu). Pojawia się okienko dialogowe "Add to Project List" (dodawanie do listy elementów projektu). 5. Do okienka File Name: wpisujemy nazwę głównego pliku projektu: WINZ2.CPP (*.cpp jest domyślnym rozszerzeniem). Plik możemy wybrać także z listy w okienku Files: . 6. Wybieramy w okienku dialogowym klawisz [+Add] (dodaj do projektu). 7. Wpisujemy nazwę kolejnego pliku wchodzącego w skład projektu (w tym przypadku WINZ2.DEF). 8. Wybieramy klawisz [+Add] w okienku. UWAGA: Czynności 7) i 8) w przypadku bardziej złożonych projektów będą powtarzane wielokrotnie. 9. Wybieramy klawisz [Done] w okienku (zrobione/gotowe). Konfigurowanie projektu zostało zakończone. 10. Przy pomocy rozkazów Compile, Link, Make, Build all, Run możemy teraz skompilować, skonsolidować i uruchomić nasz program w postaci projektu. Ostrzeżenie Linkera zniknie. [!!!]UWAGA ________________________________________________________________ W dolnej części ekranu w stadium tworzenia projektów ( i póżniej po załadowaniu pliku projektu [Open Project] pojawi się lista plików. Do trybu edycji pliku możesz przjść poprzez dwukrotne klinięcie pliku na tej liście. Zwróć uwagę, że pliki projektów .PRJ ( w Borland 4+ .IDE) przechowują również informacje o konfiguracji. Najważniejsza z nich to informacja o katalogach, z których korzysta kompilator: Options | Directories... | Include Options | Directories... | Library Options | Directories... | Output ________________________________________________________________ Najwygodniej przechowywać wszystkie pliki wchodzące w skład jednego projektu w odrębnym katalogu dyskowym. Dla wprawy załóż odrębny katalog i zapisz tam pliki: *.CPP *.DEF *.PRJ (lub *.IDE) dla swoich pierwszych dwóch projektów, które właśnie powstały. [!!!] UWAGA ________________________________________________________________ Ten sam plik definicji możesz wykorzystywać do tworzenia następnych przykładowych aplikacji typu Windows EXE. ________________________________________________________________