Dokumentacja Win32 znajduje się w pliku "win32.hlp", dostarczanym z "Borland C++", "Delphi" itp. Jest to nie tylko opis funkcji, ale także obszerny idei!
Do kompilowania przykładów użyj "Borland C++ Builder-a"; należy utworzyć nowy projekt oraz wybrać aplikację konsolową :
File | New | ikona Console Wizard; Window type = console, Execution type = exe
(tak przynajmniej jest w C++Builder 4.0 !)
należy zapisać projekt w osobnym katalogu; podane niżej przykłady [np
np01.cpp] najlepiej uruchamiać tak: zmienić nazwę funkcji main() w np01.cpp na
main2() i uruchamiać ją z funkcji main() w Project1.cpp; oczywiście w
Project1.cpp należy umieścić "extern void main2();".
Nowe obiekty tworzy się przy pomocy funkcji z rodziny Create*(). Obiekty często posiadają nazwę; istniejące obiekty posiadające nazwę otwiera się przy pomocy funkcji Open*().
Po utworzeniu lub otwarciu obiektu otrzymujemy uchwyt do obiektu, który jest zasadniczo ważny tylko w jednym procesie. (Funkcja DuplicateHandle() pozwala "dać" uchwyt niespokrewnionemu procesowi.)
Do zamykania obiektów służy CloseHandle(). Dopóki licznik odwołań do obiektu nie spadnie do 0, obiekt nie zostanie zniszczony.
Procesy potomne mogą dziedziczyć uchwyty do obiektów po procesie macierzystym (jeśli ten sobie tego życzy). Jest to uogólnienie idei znanej z Unix-a, w którym procesy potomne dziedziczyły otwarte deskryptory!
Obiekty mogą posiadać security descriptor, określający którzy
użytkownicy mają dostęp (i jaki) do obiektu.
Oprócz przestrzeni adresowej proces posiada "zasoby": kod, dane, uchwyty obiektów, zmienne środowiska, bazowy priorytet, jeden lub więcej wątków, min/max zbiór roboczy. Gdy proces się kończy, to jego zasoby są niszczone.
Istnieją 2 typy aplikacji, w Win32:
Przykład tworzenia procesu potomnego ("np01.cpp"):
#include <stdio.h> #include <windows.h> void main( VOID ) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); // Start the child process. if( !CreateProcess( NULL, // No module name (use command line). "notepad", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) ExitProcess(1); // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); /* każdy obiekt moze byc w stanie "signaled" lub "unsignaled"; funkcja "WaitForSingleObject()" czeka, aż obiekt przejdzie do stanu "signaled" ... */ // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); ExitProcess(0); }
Przejrzyj rozdział "win32.hlp" pod tytułem "Processes and Threads". Chodzi m.in. o to aby uzyskać ogólną orientację o układzie win32.hlp !.
Zadanie 100
Wypróbuj mechanizm "przekazywanie parametrów do procesu potomnego". W tym
celu napisz dwa programy PM i PP; PM będzie uruchamiał proces potomny z
programem PP; PP powinien wypisywać swoje parametry; po uruchomieniu PM
powinniśmy zobaczyć napisy wyświetlane przez PM i PP.
Wskazówka: Jak się przekazuje parametry do procesu potomnego? Patrz 2
parametr!
if( !CreateProcess( NULL, // No module name (use command line). "prog_PP 1 2 3 \"1 2 3\"", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) ExitProcess(1);
Proces potomny może odziedziczyć niektóre uchwyty po procesie macierzystym; aby to osiągnąć należy:
Zadanie 101 (**) [dla chętnych !]
Napisz przykładowy program ilustrujący dziedziczenie uchwytu do pliku,
przez proces potomny. Proces potomny powinien wykonywać operacje na pliku
za pośrednictwem odziedziczonego uchwytu. Proces macierzysty powinien pokazywać
zmiany w zawartości pliku, co jedną sekundę. Użyj funkcji: CreateFile(),
ReadFile(), WriteFile(), Sleep().
Przypomnijmy główne cechy wątków:
Przykład programu wielowątkowego ("np02.cpp"):
#include <stdio.h> #include <windows.h> DWORD WINAPI ThreadFunc( LPVOID lpParam ) { MessageBox( NULL, (char*)lpParam, "Thread created.", MB_OK ); printf("watek %s sie konczy!!!\n", (char*)lpParam); ExitThread(0); } main() { DWORD dwThreadId[10]; char chThreadParam[10][40]; HANDLE hThread[10]; for(int i=0; i<10; i++) { sprintf(chThreadParam[i], "Watek nr %i", i); hThread[i] = CreateThread( NULL, // no security attributes 0, // use default stack size ThreadFunc, // thread function chThreadParam[i], // argument to thread function 0, // use default creation flags &dwThreadId[i]); // returns the thread identifier // Check the return value for success. if (hThread[i] == NULL) ExitProcess(1); } for(int i=0; i<10; i++) CloseHandle( hThread[i] ); printf("22222222222222222\n"); printf("dowolny znak + enter "); getchar(); ExitProcess(0); }Ćwiczenie 102
.........................................................
Czym się różni "uchwyt obiektu wątkowego", od "identyfikatora obiektu wątkowego"? Odp: identyfikator jest poprawny w całym systemie, natomiast uchwyt (zasadniczo) tylko w procesie, w którym uzyskano ten uchwyt.
Wątek może zostać tymczasowo "uśpiony" przy pomocy funkcji SuspendThread(), Sleep(). ResumeThread() wznawia działanie wątku. Wątek może być także uruchomiony, od razu w stanie "suspended": patrz flaga CREATE_SUSPENDED, parametru dwCreationFlags, w funkcji CreateThread().
Przykład procesu wielowątkowego ("np03.cpp"), w którym wątki wyświetlają napisy na wspólnej konsoli. Wątek macierzysty czeka na zakończenie wątków potomnych przy pomocy funkcji WaitForSingleObject():
#include <stdio.h> #include <windows.h> DWORD WINAPI ThreadFunc( LPVOID lpParam ) { for(long i=0; i<300; i++) { printf("[%s] %li\n", (char*)lpParam, i); } ExitThread(0); } main() { DWORD dwThreadId[10]; char chThreadParam[10][40]; HANDLE hThread[10]; for(int i=0; i<10; i++) { sprintf(chThreadParam[i], "Watek nr %i", i); hThread[i] = CreateThread( NULL, // no security attributes 0, // use default stack size ThreadFunc, // thread function chThreadParam[i], // argument to thread function 0, // use default creation flags &dwThreadId[i]); // returns the thread identifier // Check the return value for success. if (hThread[i] == NULL) ExitProcess(1); } // czeka na zakonczenie wszystkich watkow for(int i=0; i<10; i++) WaitForSingleObject( hThread[i], INFINITE ); for(int i=0; i<10; i++) CloseHandle( hThread[i] ); ExitProcess(0); }Ćwiczenie 103
Przeczytaj rozdział win32.hlp: "Synchronization Objects".
Każdy "obiekt synchronizacyjny" (i w ogóle każdy obiekt)
może być w stanie :
Funkcja "WaitForSingleObject()" wstrzymuje działania tak długo, aż obiekt znajdzie się w stanie signaled. |
Co oznacza stan signaled dla różnych obiektów:
Zadanie 104
Przykład "np05b.cpp" zawiera program ze zmiennymi
konto1 i konto2 i z wątkami dokonującymi przelewów. Przelewy są chronione przy
pomocy semafora. Dodaj do tego przykładu wątek kontrolny pokazujący wartość sumy
konto1+konto2 co 1 sekundę. Zbadaj co się dzieje gdy wyłączymy ochronę sekcji
krytycznej (przelewów).
Zadanie 105
Zmodyfikuj rozwiązanie poprzedniego zadania - zamiast semaforów użyj
muteksów. Przeczytaj opis funkcji CreateMutex() w "win32.hlp".
Zadanie 106
(*)
Napisz program rozwiązujący problem producenta/konsumenta, ze
skończonym buforem, z dowolną liczbą producentów i konsumentów, przy pomocy semaforów.
Program powinien produkować wydruki po których można poznać że wszystko
działa prawidłowo (producent powinien produkować kolejne liczby, 5
konsumentów powinno wypisywać na ekranie co konsumują).
Zadanie 107 (**) [dla chętnych]
Przeczytaj opis funkcji:
BOOL SignalObjectAndWait( HANDLE hObjectToSignal, // handle of object to signal HANDLE hObjectToWaitOn, // handle of object to wait for DWORD dwMilliseconds, // time-out interval in milliseconds BOOL bAlertable // alertable flag );i spróbuj skonstruować monitor Bufor, rozwiązujący problem producenta/konsumenta, ze skończonym buforem. Monitor powinien być klasą C++, powinien używać muteksów i obiektów event (?). Obiekty event powinny (?) pełnić rolę zmiennych condition "prawdziwych" monitorów.
Przeczytaj rozdział win32.hlp "Named Pipes".
Zadanie 108
Napisz program, w którym proces macierzysty tworzy proces potomny i
komunikuje się z nim przy pomocy łączy, tworzonych przy pomocy
CreateNamedPipe(). Proces potomny powinien zamieniać małe litery
na duże. Do pisania i czytania użyj funkcji ReadFile() i WriteFile().
Zauważ, że prawa rządzące tymi funkcjami w odniesieniu do łączy są podobne
do tego co było pod Unixem !. Zwróć uwagę, że w Win32 nie można w prosty
sposób przekazać wartości uchwytów (dlatego używamy Named Pipes a nie
Anonymous)
Wątki można podzielić na grupy, zawierające wątki z tym samym priorytetem. Zawsze wybiera się grupę wątków (gotowych do wykonania) z najwyższym priorytetem. Taka grupa jest obsługiwana przy pomocy metody "Round Robin" (każdy wątek otrzymuje kwant czasu).
W WinNT priorytety przyjmują wartości od 0 do 31 (duża liczba oznacza duży priorytet). Priorytety są podzielone na 2 klasy:
Przeczytaj rozdział win32.hlp "Scheduling Priorities".
Zadanie 109
Zademonstruj, że priorytety wątków mają wpływ na ich działanie przy
pomocy programu tworzącego 2 wątki, które powinny mieć różne priorytety.
Wątki te zwiekszają o 1 wartości zmiennych zmA i zmB. Co 1 sekumdę 3 wątek
(kontrolny), powinien wyświetlać wartości tych zmiennych.
Wskazówka: priorytety ustawiamy następującymi funkcjami:
SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL); SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL);