Sunteți pe pagina 1din 126

Introducere

Scopul acestui curs este de a familiariza cititorul cu o parte din problemele cu care se confrunt cel ce dorete s nvee s programeze aplicaii de tip Windows. n acest sens am considerat necesar s prezint modul de abordare al construciei unei aplicaii Windows, fr a detalia funciile din interfaa de programare (API) i mai ales fr a incerca o abordare ct de ct sistematic a acestora. Recomand cu caldur cartea lui Charles Petzold Programare Windows, considerat de multi cititori biblia programrii sub Windows, precum si cartea lui J. Prossie Programming Windows with MFC Second Edition. Aceste cri precum si MSDN, stau la baza ntocmirii acestor note de curs. Totui nu uitai c Internet-ul. Site-uri precum codeguru sau codeproject merit a fi vizitate. Mediul de dezvoltare folosit n elaborarea exemplelor este VC++ 6.0 i Visual Studio .Net 2003. Exemple si alte explicatii la aceste note de curs le gasiti pe pagina http://www.infoiasi.ro/~iasimi/IDD. Tot aici gasiti si cursul complet, ce poate fi descrcat. Ce aduce nou sistemul de operare Windows? 1. Interfata grafica cu utilizatorul GUI (mediu grafic cu ferestre); toate tipurile de interfee grafice cu utilizatorul folosesc elemente grafice afiate ntr-o imagine de tip bitmap. La nceput ecranul era folosit numai pentru afiarea informaiilor pe care utilizatorul le introducea de la tastatur. Intr-o interfa grafic, ecranul devine chiar o surs pentru celelalte intrari ale utilizatorului. Pe ecran sunt afiate diferite obiecte grafice (butoane, bare de derulare, casete de editare, etc.). Folosind tastatura i/sau mouse-ul, utilizatorul poate gestiona aceste obiecte. In locul unui ciclu unidirectional al informatiilor de la tastatur la program i de la program la ecran (SO DOS), utilizatorul interacioneaz direct cu obiectele de pe ecran. Consecventa privind interfata cu utilizatorul. Multitasking-ul. Notiunea de proces si de fir de executie. Gestionarea memoriei.

2. 3. 4.

Windows i mesaje
Windows este referit adesea ca un sistem de operare bazat pe mesaje. Fiecare eveniment (apasarea unei taste, clic de mouse, etc.) este transformat ntr-un mesaj. In mod obinuit aplicaiile sunt construite n jurul unei bucle de mesaje care regsete aceste mesaje i apeleaz funcia potrivit pentru a trata mesajul. Mesajele, dei sunt trimise aplicaiilor, nu se adreseaz acestora, ci unei alte componente fundamentale a SO, fereastra (windows). O fereastra este mai mult dect o zon dreptunghiular afiat pe ecran; aceasta reprezint o entitate abstract cu ajutorul creia utilizatorul i aplicaia interacioneaz reciproc.

Aplicaii, fire i ferestre


O aplicaie Win32 const din unul sau mai multe fire (threads), care sunt ci paralele de execuie. Gndim firele ca fiind multitasking-ul din cadrul unei aplicaii. Observaie: Sub Win32s, poate rula o aplicaie cu un singur fir de execuie. O fereastr este totdeauna gestionat de un fir; un fir poate fi proprietarul uneia sau mai multor ferestre sau pentru nici una. In final, ferestrele sunt ntr-o relaie ierarhic; unele sunt la nivelul cel mai de sus, altele sunt subordonate prinilor lor, sunt ferestre descendente. Exista mai multe tipuri de ferestre in Windows; cele mai obinuite sunt asociate cu o aplicaie. Casetele de dialog (dialog boxes) din cadrul unei ferestre sunt de asemenea ferestre. Acelai lucru pentru butoane, controale de editatre, list box-uri, icoane, etc.

Clasa Window
Comportarea unei ferestre este definita de clasa fereastr (window class). Clasa fereastr menine informaii despre modul de afiare iniial, icoana implicit, cursor, resursele meniu i cel mai important lucru, adresa funciei ataat ferestrei procedura fereastr window procedure. Cnd o aplicaie proceseaz mesaje, aceasta se face n mod obinuit prin apelul funciei Windows DispatchMessage pentru fiecare mesaj primit; DispatchMessage la rndul ei apeleaz procedura fereastr corespunztoare, identificnd iniial crei ferestre i este trimis mesajul. n continuare procedura fereastr va trata mesajul. Exist mai multe clase fereastr standard furnizate de Windows. Aceste clase sistem globale implementeaz n general funcionalitatea controalelor comune. Orice aplicaie poate folosi aceste controale, de exemplu orice aplicaie poate implementa controale de editare, utiliznd clasa fereastra Edit. Aplicaiile pot de asemeni s-i defineasc propriile clase fereastr cu ajutorul funciei RegisterClass. Acest lucru se ntmpl n mod obinuit pentru fereastra principal a aplicaiei (icoana, resurse, etc.). Windows permite de asemeni subclasarea sau superclasarea unei ferestre existente. Subclasarea substituie procedura fereastr pentru o clas ferestr cu o alt procedur. Subclasarea se realizeaz prin schimbarea adresei procedurii fereastr cu ajutorul funciei SetWindowLong (instance subclassing) sau SetClassLong (subclasare global). Instance subclassing nseamn c se schimb numai comportarea ferestrei specificate. Global subclassing nseamn c se schimb comportarea tuturor ferestrelor de tipul specificat. Superclasarea creaz o nou clas bazat pe o clas existent, reinnd numai procedura fereastr. Pentru a superclasa o clas fereastr, o aplicaie regsete informaiile despre clasa fereastr utiliznd funcia GetClassInfo, modific structura WNDCLASS astfel recepionat i folosete structura modificat ntr-un apel al funciei RegisterClass. GetClassInfo returneaza de asemenea i adresa procedurii fereastr. Mesajele pe care noua fereastr nu le trateaz trebuie trecute acestei proceduri.

Tipuri de mesaje
Mesajele reprezint n fapt evenimente la diferite nivele ale aplicaiei. Exist o clasificare a acestor mesaje (din pcate nu prea exact): mesaje fereastr, mesaje de notificare i mesaje de comand, dar deocamdat nu ne intereseaz acest lucru. Mesajele windows constau din mai multe pri, descrise de structura MSG. typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; Descriere: hwnd identific n mod unic fereastra la care a fost transmis acest mesaj. Fiecare fereastr n Windows are un asemenea identificator (tipul este HWND). message reprezint identificatorul mesajului. Identificatorii mesajului sunt referii n mod obinuit cu ajutorul constantelor simbolice i nu prin valoarea lor numeric. Aceast descriere se gsete n windows.h. Urmtoarele elemente pot fi privite ca parametrii ai mesajului, care au valori specifice funcie de fiecare mesaj n parte. Marea majoritate a mesajelor ncep cu WM_. De exemplu WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP, etc. 2

Aplicaiile pot s-i defineasc propriile mesaje. Cerina major este ca identificatorul mesajului s fie unic. Pentru a defini un mesaj n sistem folosim funcia RegisterWindowMessage. Mesaje i multitasking In Windows 3.1, bucla de mesaje are rol important n interaciunea dintre aplicaii i sistemul de operare (SQ). Funcionarea corect a unui SO Windows 3.1 depinde de cooperarea dintre aplicaii. Aplicaiile sunt cele care permit SO s preia controlul. Acest neajuns este nlturat n Windows 95/98, NT, 2000, XP i cele ce vor urma. SO este cel care realizeaz programarea aplicaiilor pentru cuantele de timp necesare pe procesor. Dei aceast planificare a execuiei este diferit pe Windows 95/98 fa de NT, rolul primordial revine SO. Cozi de mesaje n Windows pe 16 bii, SO menine o singur coad de mesaje. Cnd o aplicaie ncearc s gseasc urmtorul mesaj din coada de mesaje prin funciile GetMessage sau PeekMessage, SO poate efectua un context switch i poate activa o alt aplicaie pentru care mesajele ateapt n coad. Mesajul din vrful cozii este extras i returnat aplicaiei via structura MSG. Dac aplicaia eueaz n apelul lui GetMessage, PeekMessage sau Yield, aceasta blocheaz sistemul. Coada de mesaje poate deveni plin! n Win32 (95/98, NT, 2000, XP) mecanismul cozii de mesaje este mult mai complicat. O singur coad de mesaje nu mai rezolv problema. Aici sistemul de operare d controlul unei aplicaii i tot SO permite multitaskingul. Dou sau mai multe fire pot accesa coada de mesaje n acelai timp i nu exist nici o garanie c mesajele extrase sunt ale lor. Acesta este unul motivele pentru care coada de mesaje a fost separat n cozi de mesaje individuale pentru fiecare fir din sistem. Procese i Fire ntr-un SO non-multifir, de exemplu UNIX, unitatea cea mai mic de execuie este task-ul sau procesul. Mecanismul de planificare (aparine SO) al task-urilor va comuta ntre acestea; multitasking-ul se realizeaz ntre dou sau mai multe procese (task-uri). Dac o aplicaie are nevoie s execute mai multe funcii simultan, atunci aceasta se va divide n mai multe task-uri. Din aceast tehnic decurg anumite neajunsuri, consum de resurse, timp de lansare a unui nou task, spaii de adrese diferite, probleme de sincronizare, etc. n contrast, ntr-un sistem multifir (multifilar, multithreaded) unitatea cea mai mic de execuie este firul, nu procesul. Un proces sau task poate conine mai multe fire, dintre care unul singur este firul principal, firul primar. Lansarea n execuie a unui nou fir cere mai puine resurse din partea SO, firul ruleaz n cadrul aceluiai proces, problemele de sincronizare sunt i nu sunt complicate. Oricum apar dou sincronizri: sincronizare ntre procese i sincronizare ntre fire. Fire i Mesaje Dup cum am mai spus proprietarul ferestrei este firul de execuie. Fiecare fir are coada proprie, privat, de mesaje n care SO depoziteaz mesajele adresate ferestrei. Aceasta nu nseamn c un fir trebuie neaprat s aib o fereastr proprie i o coad proprie de mesaje. Pot exista fire i fr fereastr i fr bucl de mesaje. n MFC, aceste fire se numesc worker threads (nu au ataat o fereastr, nu prezint interfa ctre utilizator), iar celelalte se numesc user-interface threads. Apeluri de funcii Windows Windows ofer un mare numr de funcii pentru a executa o mare varietate de task-uri, controlul proceselor, gestionarea ferestrelor, fiierelor, memoriei, servicii grafice, comunicaii, etc. Apelurile sistem pot fi organizate n trei categorii: 1. servicii nucleu (apeluri sistem pentru controlul proceselor, firelor, gestiunea memoriei, etc.); 3

2. 3.

servicii utilizator (gestiunea elementelor de interfa ale utilizatorului cum ar fi ferestre, controale, dialoguri, etc.); servicii GDI (Graphics Device Interface) (ieirea grafic independent de dispozitiv).

Sistemul Windows include de asemenea funcii API pentru alte funcionaliti MAPI (Messaging API), TAPI (Telephony API) sau ODBC (Open Database Connectivity). Servicii Nucleu Serviciile nucleu cuprind de obicei: getionarea fiierelor, memoriei, proceselor, firelor, resurselor. Gestionarea fiierelor nu ar trebui s se mai fac cu funcii din bibliotecile C sau prin iostream-urile din C++. Aplicaiile ar trebui s utilizeze conceptul Win32 de obiect fisier file object i funciile asociate cu acesta. De exemplu exist fiiere mapate n memorie care asigura comunicarea ntre task-uri. Referitor la gestionarea memoriei pe lng funciile cunoscute, SO Windows ofer funcii care pot manipula spaii de adrese de sute de MB alocndu-le dar nefcnd commiting. Cea mai important faet a proceselor i firelor este gestiunea sincronizrii. Problema este complet nou i nu a fost ntlnit n Windows 3.1. n Win32, sincronizarea se face cu ajutorul unor obiecte de sincronizare, pe care firele le pot utiliza pentru a informa alte fire despre starea lor, de a proteja zone senzitive de cod sau de a obtine informatii despre alte fire sau starea altor obiecte. In Win32 multe resurse nucleu sunt reprezentate ca obiecte obiecte nucleu: fiiere, fire, procese, obiecte de sincronizare, etc. Obiectele sunt referite prin manipulatori, identificatori (handlers); exist funcii pentru manipularea generic a obiectelor, pentru manipularea obiectelor de un anumit tip. Sub NT, obiectele au ataate proprieti de securitate. De exemplu, un fir nu poate accesa un obiect fiier dac nu are drepturile necesare care s coincid cu proprietile de securitate. Modulul nucleu furnizezz de asemeni funcii pentru gestionarea resurselor interfa-utilizator. Aceste resurse includ icoane, cursoare, abloane de dialog, resurse string, tabele de acceleratori, bitmap-uri, etc. Nucleul NT furnizeaz printre altele: atribute de securitate pentru obiectele nucleu, backup, funcionalitatea aplicaiilor de tip consol care pot utiliza funcii pentru memoria virtual sau pot utiliza mai multe fire de execuie. Servicii utilizator Modulul utilizator furnizeaz apeluri sistem care gestioneaz aspecte i elemente ale interfeei utilizatorului; sunt incluse funcii care manipuleaz ferestre, dialoguri, meniuri, controale, clipboard, etc. Se exemplific tipurile de operaii pentru fiecare resurs n parte (n general creare, modificare, tergere, mutare, redimensionare, etc.). Modulul utilizator furnizeaz funcii pentru managementul mesajelor i cozilor de mesaje. Aplicaiile pot utiliza aceste apeluri pentru a controla coninutul cozii de mesaje proprii, a regsi i a procesa mesajele, a crea noi mesaje. Noile mesaje pot fi trimise (sent) sau plasate (posted) la orice fereastr. Un mesaj plasat pentru o fereastr funcia PostMessage - nseamn pur i simplu intrarea acestuia n coada de mesaje nu i procesarea imediat a acestuia. Trimiterea unui mesaj (sent) implic tratarea lui imediat sau mai corect spus funcia SendMessage nu-i termin execuia pn cnd mesajul nu a fost tratat. Servicii GDI Funciile din GDI sunt utilizate n mod obinuit pentru a executa operaii grafice primitive independente de dispozitiv, pe contexte de dispozitiv. Un context de dispozitiv este o interfa la un periferic grafic specific (n fapt este o structur de date pstrat n memorie). Contextul de dispozitiv poate fi utilizat pentru a obine informaii despre periferic i pentru a executa ieirile grafice pe acest periferic. Informaiile care pot fi obinute printr-un context de dispozitiv, descriu n detaliu acest periferic. Ieirea grafic este executat printr-un context de dispozitiv prin pasarea (trecerea) unui manipulator (identificator) al contextului de dispozitiv funciilor grafice din GDI. 4

Contextele de dispozitiv pot descrie o varietate mare de periferice. Contextele de dispozitiv obinuite includ: contexte de dispozitiv display, contexte de dispozitiv memorie (pentru ieirea unui bitmap memorat n memorie) sau contexte de dispozitiv printer. Un context de dispozitiv foarte special este contextul de dispozitiv metafile care permite aplicaiilor de a nregistra permanent apelurile din GDI (fiierul pstreaz o serie de primitive grafice) care sunt independente de dispozitiv. Metafiierele joac un rol crucial n reperzentarea independent de dispozitiv a obiectelor OLE nglobate. Desenarea ntr-un context de dispozitiv se face cu ajutorul coordonatelor logice. Coordonatele logice descriu obiectele utiliznd msurtori reale independente de dispozitiv, de exemplu, un dreptunghi poate fi descris ca fiind lat de 2 inch i nalt de 1 inch. GDI furnizeaz funcionalitatea necesar pentru maparea coordonatelor logice n coordonate fizice. Diferene semnificative exist n modul cum aceast mapare are loc n Win32s, Windows 95 i Windows NT. Win32s i Windows 95 folosesc reprezentarea coordonatelor pe 16 biti. Windows NT, XP poate manipula coordonate pe 32 bii. Toatre cele trei sisteme suport mapri (transformri) din coordonate logice n coordonate fizice. Aceste transformri sunt determinate (influenate) de valorile ce specific originea coordonatelor i (signed extent) extensia cu semn a spaiului logic i al celui fizic. Originea coordonatelor specific deplasarea pe orizontal i vertical, iar extensia (extent) determina orientarea i scara obiectelor dup mapare (transformare). n plus, Windows NT ofer ceea ce se numete world transformation functions. Prin aceste funcii, orice transformare liniar poate fi folosit pentru transformarea spaiului de coordonate logice n spaiul de coordonate fizice; n plus pentru translaii i scalare ieirile pot fi rotite sau sheared. Exemple de funcii grafice: Rectangle, Ellipse, Polygon, TextOut, etc. Alte funcii de interes deosebit (bit blit functions: PatBlt, BitBlt, StechBlt) sunt cele legate de desenarea i copierea bitmap-urilor. Contextele de dispozitiv pot fi create i distruse, starea lor poate fi salvat i rencrcat. Un alt grup de funcii gestioneaz transformrile de coordonate. Funcii comune tuturor platformelor pot fi utilizate pentru a seta sau regsi originea i extent-ul unei ferestre (spaiul de coordonate logic) i viewport-ului (spaiul de coordonate al perifericului destinaie). NT posed funcii specifice pentru transformri matriceale. Funciile GDI pot fi folosite de asemenea pentru gestionarea paletelor, aceasta nseamn c prin gestionarea paletei de culori, aplicaiile pot selecta o mulime de culori care se potrivesc cel mai bine cu culorile din imaginea (gif, pcx.) care trebuie afiat. Gestionarea paletei poate fi utilizat i n tehnici de animaie. O alt trstur a GDI-ului este crearea i gestionarea obiectelor GDI (pensoane, penie, fonturi, bitmap-uri, palete) precum i a regiunilor i a clipping-ului. Alte API-uri Funcii pentru controale comune; Funcii pentru dialoguri comune; MAPI, (Messaging Applications Programming Interface); MCI (Multimedia Control Interface); OLE API; TAPI (Telephony API).

Raportarea erorilor Majoritatea funciilor Windows folosesc un mecanism pentru evidenierea erorilor. Cnd apare o eroare, aceste funcii seteaz o valoare a erorii pentru firul respectiv, valoare care poate fi regsit cu funcia GetLastError. Valoarile pe 32 bii, returnate de aceast funcie sunt definite in winerror.h sau n fiierul header al bibliotecii specifice. Valoarea erorii poate fi setat i din cadrul aplicaiei cu ajutorul funciei SetLastError. Codurile de eroare trebuie s aib setat bitul 29. Din pcate tratarea erorilor nu a fost foarte bine standardizat in Windows. Din acest motiv trebuie s citim documentaia funciei pe care dorim s o folosim pentru a vedea ce tip de valoare returnez in cazul unei erori. Folosirea funciilor din biblioteca C/C++ Aplicaiile Win32 pot folosi setul standard al funciilor din biblioteca C/C++ cu anumite restricii. Aplicaiile Windows nu au acces n mod normal la stream-urile stdin, stdout, stderr sau obiectele iostream din C++. Numai aplicaiile consol pot utiliza aceste stream-uri. Funciile relative la fiiere pot fi folosite, dar acestea nu suport toate facilitile oferite de SO Windows securitate, drepturi de acces. n locul funciilor din familia exec se va folosi CreateProcess. n ceeea ce privete gestionarea memoriei se folosesc cu succes funciile din C sau C++. Funciile din biblioteca matematic, pentru gestionarea stringurilor, a bufferelor, a caracterelor, a conversiilor de date pot fi de asemenea folosite. Aplicaiile Win32 nu trebuie s foloseasc ntreruperea 21 sau funcii IBM PC BIOS. Arhitectura unei aplicatii Windows. Forma vizual a unei aplicaii Windows 1. 2. 3. 4. 5. 6. 7. Bara de titlu Bara de meniu Toolbar Meniul sistem Barele de navigare (derulare) vertical si orizontal Bara de stare Zona client

Vom descrie n continuare elementele de care avem nevoie pentru a scrie prima aplicaie Windows (cu SDK). Pentru acest lucru mai avem nevoie de cteva noiuni.

Fereastra. Clasa fereastra. Fereastra se reprint pe ecran sub forma unui dreptunghi. Sistemul de operare pastreaz o serie de informaii despre fereastr, informaii ce sunt descrise de clasa fereastr, iar structura corespunztoare are numele de WNDCLASS(EX). Inainte de a crea fereastra cu funcia CreateWindow(Ex), trebuie s completm structura pentru clasa fereastr. Structura WNDCLASSEX - informatii despre clasa fereastra UINT UINT WNDPROC int int HANDLE HICON HCURSOR HBRUSH LPCTSTR LPCTSTR HICON cbSize; // sizeof(WNDCLASSEX) style; // incep cu CS_ lpfnWndProc; // procedura fereastra cbClsExtra; // bytes extra pt. clasa cbWndExtra; // bytes extra pt. fereastra hInstance; hIcon; hCursor; hbrBackground; lpszMenuName; lpszClassName; hIconSm; // numai in WNDCLASSEX;

Clasa fereastr menine informaii despre modul de afiare iniial al ferestrei, icoana implicit, cursor, resursele meniu i cel mai important lucru, adresa funciei ataat ferestrei procedura fereastr (window procedure). In Windows avem de-a face cu clase ferestre deja definite i clase ferestre ce pot fidefinite de utilizator. Clase fereastr standard - clase sistem globale (controale commune, EDIT, BUTTON, LISTBOX, COMBOBOX, etc.). Clase fereastr definite de utilizator, de obicei fereastra principala a aplicatiei, inregistrarea clasei se face cu ajutorul functiei RegisterClass(Ex). Exemplu de completare a unei structuri WNDCLASSEX: // WNDCLASS wndClass; memset(&wndClass, 0, sizeof(wndClass)); // stiluri de fereastra wndClass.style = CS_HREDRAW | CS_VREDRAW; // procedura fereastra wndClass.lpfnWndProc = WndProc; // instanta aplicatiei wndClass.hInstance = hInstance; // resursa cursor wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // resursa penson wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // nume fereastra wndClass.lpszClassName = "HELLO"; iar inregistrarea clasei: RegisterClassEx(&wndClass); O fereastr n Windows este caracterizat prin dou categorii de stiluri: stiluri de clas i stiluri de fereastr. Stilurile de clas sunt prefixate de obicei cu CS_, iar cele de fereastra cu WS_. Stilurile de clas sunt reinute de data membru 7

style din WNDCLASS(EX) (mai multe stiluri de clas se pot nregistra folosind operatorul OR (|) pe bii). Stilurile ferestrei (WS_) se completeaz ca valori ale parametrilor funciei CreateWindowEx sau CreateWindow. Observaie: CreateWindow i CreateWindowEx creaz o fereastr, returneaz un HWND, dar cea de-a doua form folosete stiluri extinse pentru fereastr. In general sufixul Ex provine de la Extended. Astfel exist i funciile RegisterClass i RegsiterClassEx, prescurtat scrise sub forma RegisterClass(Ex). Stiluri de clasa (CS_) Pot fi combinate folosind operatorul OR (|) pe bii. Funcii folosite GetClassLong, SetClassLong Cele mai importante stiluri: Value CS_CLASSDC CS_DBLCLKS CS_OWNDC Action Aloc un context de dispozitiv ce poate fi partajat de toate ferestrele din clas. Trimite mesajul dublu clic procedurii ferestr. Aloc un unic context de dispozitiv pentru fiecare fereastr din calas.

Stiluri fereastra (WS_) Pot fi combinate folosind operatorul OR (|) pe bii. Functii folosite: GetWindowLong, SetWindowLong Se furnizeaza ca parametri in functia CreateWindow(Ex). In continuare se descriu cteva stiluri de fereastr (lista complet poate consultat n MSDN). WS_BORDER Creaz o fereastr ce are margini. WS_CAPTION Creaz o fereastr ce are o bar de titlu. Nu poate fi utilizat cu stilul WS_DLGFRAME. Folosirea acestui stil implic i folosirea stilului definit anterior. WS_CHILD Creaz o fereastr descent (copil). Nu poate fi utilizat cu stilul WS_POPUP. WS_DISABLED Creaz o fereastr ce iniial este disabled. WS_DLGFRAME Creaz o fereastr cu margini duble, dar fr titlu. WS_GROUP Specific primul conttrol al unui grup de contraole. WS_HSCROLL Creaz o fereastr ce are bara de navigare orizontal. WS_MAXIMIZE Creaz o fereastr de mrime maxim. WS_MAXIMIZEBOX Creaz o fereastr ce are butonul Maximize. WS_MINIMIZE i WS_MINIMIZEBOX asemntoare cu cele dou de mai sus. WS_VISIBLE Creaz o fereastr ce iniial vizibil. O list complet a stilurilor de fereastr poate fi consultat n MSDN. Crearea unei ferestre. Funcia CreateWindow(Ex) n exemplul ce urmeaz se creaz o fereastra cu stiluri extinse. HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, // // // // // extended window style pointer to registered class name pointer to window name window style horizontal position of window 8

int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu, or child-window HINSTANCE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data ); lpClassName = numele clasei, vine din completarea structurii WNDCLASS(EX) si apoi inregistrarea clasei cu functia RegisterClass(Ex). HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hInstance, LPVOID lpParam ); // // // // // // // // // // // pointer to registered class name pointer to window name window style horizontal position of window vertical position of window window width window height handle to parent or owner window handle to menu or child-window identifier handle to application instance pointer to window-creation data

//iden

Returneaza un handle (tip HWND) la fereastra creat. Cu acest handle este identificat fereastra n aplicaie. Structura mesajului Mesajele reprezint n fapt evenimente la diferite nivele ale aplicaiei. Exist o clasificare a acestor mesaje : mesaje fereastr, mesaje de notificare i mesaje de comand. Mesajele windows constau din urmatoarele pri, descrise de structura MSG. typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; Explicatii. Exemplu de mesaj cu parametri: Mesaj cu doi parametri WM_LBUTTONDOWN fwKeys = wParam; // key flags xPos = LOWORD(lParam); // horizontal position of cursor yPos = HIWORD(lParam); // vertical position of cursor Observaie: 9

Acest mesaj este trimis cnd s-a fcut clic pe butonul stnga al mouse-ului. Parametrii acestui mesaj pstreaz coordonatele mouse-ului unde s-a fcut clic. ntr-o aplicaie SDK, wParam i lParam i gsii ca fiind parametrii numrul 3 i 4 al funciei ce trateaz mesajele pentru o caset de dialog, sau ai funciei procedur fereastr. Preferm s nu traducem n acest caz descrierea mesajelor pentru a ncerca s v obinuim cu MSDN. Parameters fwKeys Value of wParam. Indicates whether various virtual keys are down. This parameter can be any combination of the following values: Value Description MK_CONTROL Set if the ctrl key is down. MK_LBUTTON Set if the left mouse button is down. MK_MBUTTON Set if the middle mouse button is down. MK_RBUTTON Set if the right mouse button is down. MK_SHIFT xPos Value of the low-order word of lParam. Specifies the x-coordinate of the cursor. The coordinate is relative to the upper-left corner of the client area. yPos Value of the high-order word of lParam. Specifies the y-coordinate of the cursor. The coordinate is relative to the upper-left corner of the client area. Return Values If an application processes this message, it should return zero. An application can use the MAKEPOINTS macro to convert the lParam parameter to a POINTS structure. Mesaj cu un parametru WM_QUIT The WM_QUIT message indicates a request to terminate an application and is generated when the application calls the PostQuitMessage function. It causes the GetMessage function to return zero. WM_QUIT nExitCode = (int) wParam; // exit code Parameters nExitCode Value of wParam. Specifies the exit code given in the PostQuitMessage function. Return Values This message does not have a return value, because it causes the message loop to terminate before the message is sent to the application's window procedure. Mesaj fara parametri WM_CLOSE The WM_CLOSE message is sent as a signal that a window or an application should terminate. Parameters This message has no parameters. Return Values If an application processes this message, it should return zero. Default Action The DefWindowProc function calls the DestroyWindow function to destroy the window. 10 Set if the shift key is down.

WM_DESTROY The WM_DESTROY message is sent when a window is being destroyed. It is sent to the window procedure of the window being destroyed after the window is removed from the screen. This message is sent first to the window being destroyed and then to the child windows (if any) as they are destroyed. During the processing of the message, it can be assumed that all child windows still exist. Parameters Return Values This message has no parameters. If an application processes this message, it should return zero.

Exemplu de tratare a unui mesaj ntr-o procedur fereastr LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_PAINT: DeseneazaCeva(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; // trebuie sa intoarca totdeauna 0 (zero) } Categorii de mesaje Exista trei categorii de mesaje: 1) Mesaje fereastr (Windows messages ) Aceste mesaje sunt prefixate cu WM_ , excepie fcnd mesajul WM_COMMAND. Mesajele fereastra sunt tratate de ferestre si vizualizari (n fapt vizualizarile sunt tot ferestre, diferenta exist ntre fereastra principal main frame - si vizualizri). Aceste mesaje, n general, au parametri. Exemplu // cod in procedura fereastra case WM_LBUTTONDOWN: { // hDC = handle la Device Context HDC hDC; RECT clientRect; hDC = GetDC(hwnd); if (hDC != NULL) { GetClientRect(hwnd, &clientRect); DPtoLP(hDC, (LPPOINT)&clientRect, 2); DrawText(hDC, "Hello, World!", -1, &clientRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hwnd, hDC); } break; } 11

2) Notificri ale controlului (Control notifications ) Acestea includ mesajul de notificare WM_COMMAND din controale i alte ferestre descendente, ctre fereastra printe. Exemplu: Un control de editare trimite parintelui lui (in mod normal o caseta de dialog) un mesaj WM_COMMAND ce contine codul de notificare EN_CHANGE cind utilizatorul a alterat textul din cadrul controlului. Exceptie: Notificarea BN_CLICKED (un control push button a fost apasat) este tratata ca un mesaj de comanda. Cadrul de lucru (framework) ruteaza mesajele de notificare ale controalelor ca orice alt mesaj. Unele mesaje de notificare sunt trimise via mesajul WM_NOTIFY. Trebuie consultata documentatia Windows pentru a vedea notificarile suportate de controale via WM_NOTIFY (de exemplu consultati controlul list view). In SDK, codul se gaseste in procedura fereastra a casetei de dialog ce contine controlul. Exemplu: i) Creare caseta de dialog (din resurse) ce are ID-ul IDD_CURS // Codul se poate gasi in procedura fereastrei // principale a aplicatiei DialogBox(hInst, MAKEINTRESOURCE(IDD_CURS), hwndMain, (DLGPROC)DlgCurs); // hwndMain = HANDLE pentru fereastra principala // a aplicatiei, // obtinut ca rezultat al crearii ferestrei: // vezi functia CreateWindow // sau CreateWindowEx // DlgCurs = procedura fereastra a casetei // de dialog identificata de IDD_CURS ii) Prototipul pentru procedura fereastr (ataat unui dialog) este: BOOL CALLBACK DlgCurs(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { ... switch(msg) { ... case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_EDITCURS: // ID control de editare switch(HIWORD(wParam)) { case EN_CHANGE: // tratare notificare... break; ... } 12

case IDC_PUSHBUTTON: // ID buton // cod pentru tratare mesaj ... break; } ... } case WM_NOTIFY: { UINT code; UINT idCtrl; code = ((NMHDR*)lParam)->code; idCtrl = ((NMHDR*)lParam)->idFrom; switch(wParam) // wParam par. in procedura fereastra { case IDC_LISTVIEW: { UINT code = ((NMHDR*)lParam)->code; switch(code) { case NM_CLICK: { break; } } ... } } In MFC Mesajele de notificare sunt tratate de obicei de obiectele claselor derivate din CWnd (CFrameWnd, CMDIFrame, CMDIChildWnd, CView, CDialog) precum i clasele proprii derivate din aceasta. Aceste obiecte sunt caracterizate de faptul ca au un HWND, identificator al ferestrei Windows. 3) Mesaje de comanda (Command messages) Acestea includ mesajele de notificare WM_COMMAND din obiectele interfeei cu utilizatorul: meniu-uri, butoane din toolbar i taste acceleratoare. Cadrul de lucru proceseaza comenzile in mod diferit fa de celelalte mesaje i acestea pot fi tratate de mai multe tipuri de obiecte (documente, abloane de documente document templates -, obiectul aplicaie, vizualizri). A se vedea rutarea comenzilor si ierarhia de clase MFC. Recomand exersarea acestora intr-o aplicatie tip SDI si apoi puteti trece la MDI. Intr-o aplicatie bazata pe dialog, lucrurile sunt mai usoare. Exemplu // cod scris in procedura ferestrei principale a aplicatiei switch(nCode) { // ... nCode ID-ul optiunii din meniu 13

case IDM_WINDOWTILE: SendMessage(hwnd,WM_MDITILE,0,0); break; case IDM_WINDOWCASCADE: SendMessage(hwnd,WM_MDICASCADE,0,0); break; } Coada de mesaje Windows folosete dou metode pentru a ruta mesajele la procedura fereastr: (1) punnd mesajele ntr-o coad de ateptare (principiul FIFO), numita coada de mesaje, care este un obiect sistem, gestionat in memorie i pastreaza mesajele; aceste mesaje se numesc queued messages. (2) trimind mesajele n mod direct procedurii fereastr. Acestea sunt numite nonqueued messages (ex. WM_ACTIVATE, WM_SETFOCUS, etc.). Aceste mesaje sunt trimise pentru a notifica o fereastra despre aparitia unui eveniment care o afecteaz. Sistemul mentine o singura coada de mesaje numita coada de mesaje sistem i un numr de cozi de mesaje pentru fiecare fir cu interfa. Driver-ul pentru mouse sau tastatur convertete intrrile (evenimentele) in mesaje i le plaseaz n coada de mesaje a sistemului. Evenimentele genereaz mesaje. SO preia cte un singur mesaj la un moment dat din coada de mesaje a sistemului, determin crei ferestre i este adresat i apoi pune mesajul n coada de mesaje a firului respectiv. O schem simplificat a acestui mecanism este Windows recepioneaz evenimentul (aciunea utilizatorului, scurgerea unui interval de timp, etc) Windows transform aciunea n mesaj Fereastra programului primete mesajul Programul execut o bucl care preia i distribuie mesajele.

Firul preia mesajul (functia GetMessage) l redirecteaz ctre sistem (functia RegisterClass nregistreaz n sistem o clas fereastr o structur WNDCLASS / WNDCLASSEX deci are informaii despre adresa procedurii fereastra; CreateWindow / CreateWindowEx va folosi aceasta clasa inregistrat pentru a crea fereastra i ca atare va avea informaii despre adresa procedurii fereastr) iar acesta va apela funcia fereastr corespunztoare pentru a-l trata (funcia DispatchMessage face acest lucru, dar nu completeaz informaiile legate de timpul cnd a aprut mesajul sau de poziia cursorului mouse-ului). In cadrul procedurii fereastr exist codul pentru tratarea mesajului. Procedura fereastr este de tip CALLBACK, deci este o functie ce va fi apelata de catre sistemul de operare. Programatorul poate considera ca functia DispatchMessage apeleaza procedura fereastra furnizindu-i mesajul ce trebuie tratat (in realitate SO procedeaz un pic altfel). Exceptie: Mesajul WM_PAINT este mentinut in coada de mesaje si va fi procesat cind nu mai exista alte mesaje. Daca apar mai multe mesaje WM_PAINT, se retine ultimul mesaj de acest tip, celelalte mesaje WM_PAINT fiind 14

sterse din coada de mesaje. Acest algoritm reduce efectul neplacut datorat multiplilelor redesenari ale ferestrei precum si timpul procesor necesar redesenarii. Functii utile ce lucreaza cu mesaje (pentru o lista completa consultati MSDN) Un fir poate pune un mesaj in coada proprie de mesaje folosind funcia PostMessage. Funcia PostThreadMessage va pune un mesaj n coada de mesaje a altui fir. Un mesaj poate fi examinat, fr a fi eliminat din coada de mesaje, cu ajutorul funciei PeekMessage. Aceasta funie completeaza o structur de tip MSG cu informaii despre mesaj. Informatiile legate de timpul cind a aparut mesajul i pozitia cursorului mouse-ului pot fi obinute cu ajutorul funciilor GetMessageTime, respectiv GetMessagePos. Un fir poate folosi funcia WaitMessage pentru a da controlul altui fir cnd nu mai exist mesaje n coada proprie de mesaje. Un mesaj este insotit in sistem de dou valori date de parametrii WPARAM si LPARAM ai functiei SendMessage. Daca analizati prototipul procedurii fereastra veti observa ca aceasta contine ID-ul mesajului si doi parametri de tipul indicat mai sus. Un mesaj poate contine informatii aditionale, informatii furnizate pe o valoare pe 32 de biti (poate fi pointer la o structura). Aceste informatii se completeaza cu functia SetMessageExtraInfo si pot fi regasite cu functia GetMessageExtraInfo. Informatia ataat cozii de mesaje a firului este valid pna la urmtorul apel al functiei GetMessage sau PeekMessage. Aproape toate ferestrele din Windows pot pstra informaii suplimentare, informaii pstrate pe o valoare pe 32 biti, deci putem pastra pointeri la structuri (listbox, combo box vezi functiile SetItemData, SetItemDataPtr, GetItemData, GetItemDataPtr, etc.) Pentru trimiterea unui mesaj se pot utiliza functiile SendMessage, PostMessage, PostQuitMessage, SendDlgItemMessage, SendMessageCallback, SendNotifyMessage, PostMessage, PostThreadMessage. Functia SendMessage blocheaz firul apelant ateptnd tratarea mesajului. Celelalte functii, enumerate mai sus, prezint diverse variaii de la aceasta tehnica general. Observatie. Trebuie sa facem dictinctie intre coada de mesaje, harta de mesaje, bucla de mesaje. Notiunea de harta de mesaje exista in ierarhia de clase MFC. Bucla de mesaje Bucla de mesaje cea mai simpl consta din apelul uneia din functiile: GetMessage, TranslateMessage, si DispatchMessage. Exemplu: MSG msg; while( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } Acest cod se poate gasi in cadrul functiei WinMain. Functia GetMessage regsete un mesaj din coad i l copie n structura de tip MSG. Aceast funcie returneaz valori diferite de zero atta timp ct nu extrage din coada mesajul WM_QUIT, caz in care returneaza zero (FALSE) i bucla de mesaje se termin. Intr-o aplicatie cu un singur fir, terminarea buclei de mesaje este echivalent cu inchiderea aplicatiei. O aplicatie poate forta terminarea buclei de mesaje utilizind functia PostQuitMessage, in mod obisnuit ca rspuns la mesajul WM_DESTROY a ferestrei principale a aplicatiei. Vezi mai jos descrierea functiei GetMessage. 15

Daca specificm un handle la fereastra (al doilea parametru din GetMessage), vor fi extrase numai mesajele pentru fereastra identificata de acel handle. Ultimii doi parametri din GetMessage realizeaza o filtrare pentru mesaje (valaorea minima si valoarea maxima pentru mesaje). TranslateMessage. Daca firul primeste caractere de la tastatur, atunci trebuie folosita functia TranslateMessage. Sistemul genereaza mesajele WM_KEYDOWN si WM_KEYUP (mesaje de chei virtuale) ori de cite ori utilizatorul apas o tast. Aceste mesaje nu contin codul caracterului apasat. TranslateMessage traduce mesajul de cheie virtuala intr-un mesaj caracter (WM_CHAR) i il pune napoi n coada de mesaje (evident in procedura fereastra trebuie sa existe un handler (functie, portiune de cod) care sa trateze mesajul WM_CHAR)), iar la urmtoarea iteraie a buclei de mesaje acesta va ajunge la DispatchMessage. DispatchMessage trimite un mesaj la procedura fereastr a ferestrei identificat de handle ferestrei din mesaj. Daca handle este HWND_TOPMOST, DispatchMessage trimite mesajul procedurii ferestrei de la nivelul cel mai de sus (top level) din sistem. Dac handle este NULL, nu se intmpl nimic. TranslateAccelerator O aplicatie care folosete taste de accelerare trebuie s fie n stare s tanslateze (traduc) mesajele de la tastur n mesaje de comand. Pentru a realiza acest lucru, bucla de mesaje trebuie sa includ un apel la funcia TranslateAccelerator. IsDialogMessage Daca un fir folosete casete de dialog amodale, bucla de mesaje trebuie s includ un apel la funcia IsDialogMessage. In acest mod caseta de dialog poate primi intrri de la tastatur. GetMessage Functia GetMessage regaseste un mesaj din coada de mesaje a firului apelant i il plaseaz n structura specificat. Aceast funcie poate regsi mesaje asociate cu o anumit fereastr (identificat de HWND) i mesaje "puse" cu ajutorul funciei PostThreadMessage. Funcia regsete mesajele care se afl ntr-un anumit interval de valori. Functia GetMessage nu regsete mesaje pentru ferestre ce aparin altor fire sau aplicaii. Sintaxa este: BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ); Parametrii lpMsg = Pointer la o structur de tip MSG, structur ce va fi completat cu informaiile despre mesajul extras din coada de mesaje a firului. hWnd = Handle la fereastra ale carei mesaje vor fi extrase. O valoare are o semnificaie speciala: Valoare NULL Semnificatie GetMessage regaseste mesajele pentru orice fereastra ce apartine firului din care este apelata si mesaje "puse" (in coada de mesaje) cu ajutorul functiei PostThreadMessage. // // // // address of structure with message handle of window first message last message

16

WinMain Un program sub windows ncepe cu funcia WinMain (un program sub DOS incepe cu functia main()) care are patru parametri: primii doi sunt de tip HINSTANCE, al treilea reprezinta un pointer la un ir de caractere ce reprezint linia de comand, iar ultimul este un intreg i reprezint modul de afisare al ferestrei (minimizata, maximizata, normal, etc.). Al doilea parametru este pstrat pentru compatibilitate cu Windows pe 16 biti, deci in Windows pe 32 biti nu se folosete i totdeauna va fi NULL. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, UINT nCmdShow); Clasele fereastr care se completeaz i inregistreaz se fac n cadrul acestei funcii WinMain (direct sau indirect prin apelul altei funcii). Data membru hInstance din WNDCLASS(EX) se va completa cu hInstance din WinMain. lpfnWndProc (adresa procedurii fereastra) se va completa cu adresa funciei definit n cadrul aplicaiei. In acest mod stabilim funcia unde vom scrie codul ce va trata mesajele ce vin la aceasta fereastr. Adresa acestei funcii va fi inregistrat n sistem, iar apelul functiei DispatchMessage va identifica funcia corect care trebuie s trateze mesajul. Procedura fereastra este apelata de catre SO (nu ma apela tu, te apelez eu). (vezi tipul CALLBACK) Observatie: Trebuie facut distincie ntre clasa fereastr i o clas din POO i de asemenea ntre stil clas si stil fereastr (de altfel stilurile clasei sunt prefixate cu CS_ iar stilurile ferestrei cu WS_). Exemplu complet in SDK. #include <windows.h> // ---------- Apelata pe mesajul WM_PAINT void Deseneaza(HWND hwnd) { HDC hDC; PAINTSTRUCT paintStruct; RECT clientRect; hDC = BeginPaint(hwnd, &paintStruct); if (hDC != NULL) { GetClientRect(hwnd, &clientRect); DPtoLP(hDC, (LPPOINT)&clientRect, 2); DrawText(hDC, "Hello, World!", -1, &clientRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(hwnd, &paintStruct); } } // --------- Procedura fereastra LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 17

{ switch(uMsg) { case WM_PAINT: Deseneaza(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; // trebuie sa intoarca totdeauna 0 (zero) } // --------------- Programul principal int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR d3, int nCmdShow) { MSG msg; HWND hwnd; WNDCLASS wndClass; memset(&wndClass, 0, sizeof(wndClass)); // stiluri de fereastra wndClass.style = CS_HREDRAW | CS_VREDRAW; // procedura fereastra wndClass.lpfnWndProc = WndProc; wndClass.hInstance = hInstance; // instanta aplicatiei // resursa cursor wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // resursa penson wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // nume fereastra wndClass.lpszClassName = "HELLO"; // inregistrare fereastra if (!RegisterClass(&wndClass)) return FALSE; hwnd = CreateWindow("HELLO", "HELLO", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); // stabilire atribute pentru afisarea ferestrei ShowWindow(hwnd, nCmdShow); // desenarea ferestrei se trimite mesajul WM_PAINT UpdateWindow(hwnd); // bucla de mesaje while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; } Explicaii: 1. se creaz fereastra prin completarea structurii WNDCLASS; 18

2. 3. 4. 5. 6.

se nregistreaz fereastra RegisterClass(&wndClass); se creaz fereastra CreateWindow; se stabileste modul de afiare al ferestrei ShowWindow(hwnd, nCmdShow); se afieaz fereastra propriu zis UpdateWindow(hwnd); urmeaz bucla de mesaje.

Alte functii folositoare in gestiunea ferestrei GetWindowLong - Regsete informaii despre fereastra specificat. LONG GetWindowLong( HWND hWnd, // handle of window int nIndex // offset of value to retrieve); Adresa procedurii fereastr poate fi obinut cu funcia GetWindowLong cu parametrul GWL_WNDPROC sau DWL_DLGPROC. Valori posibile pentru parametrul nIndex Valoare GWL_EXSTYLE GWL_STYLE GWL_WNDPROC Actiune Regsete stilurile extinse ale ferestrei. Regsete stilurile ferestrei. Regsete adresa procedurii fereastr, sau un handle ce reprezint adresa procedurii fereastr. Trebuie s folosim CallWindowProc pentru a apela procedura fereastr. Regste handle la fereastra printe, dac exist. Regsete identificatorul ferestrei.

GWL_HWNDPARENT GWL_ID Mai multe detalii n MSDN.

Cnd hWnd identifica o caseta de dialog, pot fi folosite si urmtoarele valori: Valoare DWL_DLGPROC DWL_MSGRESULT Actiune Regsete adresa procedurii casetei de dialog. Regsete valoare de retur a unui mesaj procesat in procedura ataat casetei de dialog.

SetWindowLong - modific atributele unei ferestre. Prototip LONG SetWindowLong( HWND hWnd, // handle of window int nIndex, // offset of value to set LONG dwNewLong // new value ); nIndex - Valori posibile sunt n general aceleai ca la funcia anterioar, deosebirea fiind n aceea c aceste valori se vor modifica. CallWindowProc (fr traducere) 19

Putem folosi functia CallWindowProc pentru a apela procedura fereastr. LRESULT CallWindowProc( WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam ); // // // // // pointer to previous procedure handle to window message first message parameter second message parameter

Parameters lpPrevWndFunc Pointer to the previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a handle representing that address. hWnd Handle to the window procedure to receive the message. Msg Specifies the message. wParam Specifies additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. lParam Specifies additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. Return Values The return value specifies the result of the message processing and depends on the message sent. Trimiterea unui mesaj SendMessage Trimite mesajul specificat la o fereastr sau la ferestre. Funcia asteapt terminarea execuiei codului corespunztor mesajului transmis. Functia PostMessage, plaseaza un mesaj in coada de mesaje a unui fir i nu asteapt terminarea prelucrarii. LRESULT SendMessage( HWND hWnd, // UINT Msg, // WPARAM wParam, // LPARAM lParam // ); handle of destination window message to send first message parameter second message parameter

20

Observatie: Dac hWnd are valoarea HWND_BROADCAST, mesajul este trimis tuturor ferestrelor top-level din sistem, incluzand ferestre disabled sau invizibile, ferestre pop-up, dar nu ferestrelor copil. Valoarea returnata depinde de mesajul trimis. Observaie Aplicaiile ce doresc s comunice folosind HWND_BROADCAST ar trebui s foloseasc functia RegisterWindowMessage pentru a obine un mesaj unic n vederea comunicrii ntre aplicaii. Exemplu // int i; are o valoare corecta // HANDLE hwndCtl = handle la un control de editare // plasat intr-o caseta de dialog SendMessage(hwndctl, EM_SETSEL, (WPARAM)(INT)i, (LPARAM)(INT)(i+1)); SendMessage(hwndctl, EM_REPLACESEL, (WPARAM)(BOOL)FALSE, (LPARAM)(LPCTSTR)"");

21

n continuare sunt prezentate cteva aplicaii Windows, ce par anormale la prima vedere din cauz c nu au toate elementele descrise pn acum (bucla de mesaje, procedur fereastr, etc.). Urmrii-le! Sunt foarte interesante. Bucla de mesaje ascuns #include <windows.h> int WINAPI WinMain(HINSTANCE d1, HINSTANCE d2, LPSTR d3, int d4) { MessageBox(NULL, "Hello, World!", "", MB_OK); } Bucla de mesaje i procedura fereastr sunt ascunse. MessageBox afieaz o caset (box) de dialog care conine procedura fereastr i deoarece boxa de dialog este modal (nu poate fi prsit fr a se da clic pe ...) practic se cicleaz pe bucla de mesaje. Bucla de mesaje exist Un program windows obinuit, n timpul iniializrii, nregistreaz mai nti clasa fereastr apoi creaz fereastra principal utiliznd noua clas nregistrat. n exemplul ce urmeaz folosim deja clasa nregistrat, BUTTON. #include <windows.h> int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE d2, LPSTR d3, int d4) { MSG msg; HWND hwnd; hwnd = CreateWindow("BUTTON", "Hello, World!", WS_VISIBLE | BS_CENTER, 100, 100, 100, 80, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { if (msg.message == WM_LBUTTONUP) { DestroyWindow(hwnd); PostQuitMessage(0); } DispatchMessage(&msg); } return msg.wParam;

Explicaii: Dup ce se creeaz fereastra, programul intr n bucla while, unde se apeleaz GetMessage. Cnd aplicaia primete un mesaj, GetMessage ntoarce acel mesaj; valoarea ntoars este FALSE numai dac mesajul primit a fost WM_QUIT. La tratarea mesajului WM_LBUTTONDOWN se distruge fereastra aplicaiei i apoi se pune n coda de mesaje, mesajul WM_QUIT, pentru a se realiza terminarea buclei while. Orice alt mesaj diferit de WM_LBUTTONDOWN nu este tratat de aplicaie, este preluat de DispatchMessage care va apela procedura fereastr a clasei BUTTON. n marea majoritate a cazurilor procedura nu execut nimic special, unul din rolurile ei fiind acela de a goli coada de mesaje a aplicaiei i de a respecta principiul n Windows nici un mesaj nu se pierde. 22

n afar de GetMessage, mai existi funcia PeekMessage care se utilizeaz de obicei cnd aplicaia dorete s execute anumite aciuni i nu are nici un mesaj de procesat. Proceduri fereastr #include <windows.h> // ---------------- Apelata pe mesajul WM_PAINT void DrawHello(HWND hwnd) { HDC hDC; PAINTSTRUCT paintStruct; RECT clientRect; hDC = BeginPaint(hwnd, &paintStruct); if (hDC != NULL) { GetClientRect(hwnd, &clientRect); DPtoLP(hDC, (LPPOINT)&clientRect, 2); DrawText(hDC, "Hello, World!", -1, &clientRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(hwnd, &paintStruct); } } // --------------------------- Procedura fereastra LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_PAINT: DrawHello(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam);

} return 0; // trebuie sa intoarca totdeauna 0 (zero)

// --------------- Programul principal int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR d3, int nCmdShow) { MSG msg; HWND hwnd; WNDCLASS wndClass; if (hPrevInstance == NULL) // valabil numai pentru Windows 3.1 { memset(&wndClass, 0, sizeof(wndClass)); 23

// stiluri de fereastra wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; // procedura fereastra wndClass.hInstance = hInstance; // instanta aplicatiei wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // resursa cursor wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // resursa penson wndClass.lpszClassName = "HELLO"; // nume fereastra // inregistrare fereastra if (!RegisterClass(&wndClass)) return FALSE; // terminat if hwnd = CreateWindow("HELLO", "HELLO", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); // Ar trebui testat daca CreateWindow s-a executat cu succes! ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);

return msg.wParam;

Explicaii: se creaz fereastra prin completarea structurii WNDCLASS; se nregistreaz fereastra RegisterClass(&wndClass); se creaz fereastra CreateWindow; se stabileste modul de afiare al ferestrei ShowWindow(hwnd, nCmdShow); se afieaz fereastra propriu zis UpdateWindow(hwnd); urmeaz bucla de mesaje. Codul ce trebuie urmrit este cel din WndProc, procedura fereastr. Ce mesaje sunt tratate? Care sunt rspunsurile aplicaiei la aceste mesaje? WndProc trateaz dou mesaje: WM_PAINT i WM_DESTROY. Alte mesaje dect cele indicate sunt tratate de ctre DefWindowProc. La mesajul WM_DESTROY se plaseaz n coada de mesaje, mesajul WM_QUIT care are ca efect terminarea buclei de mesaje, i deci terminarea aplicaiei. La mesajul WM_PAINT se apeleaz funcia DrawHello. Dar cnd este trimis mesajul WM_PAINT i de cine? Mesajul WM_PAINT este trimis prima dat de funcia UpdateWindow, adic atunci cnd fereastra devine vizibil prima dat. ncercai opiunile Size i Move din meniul sistem. Ce se ntmpl? Trebuie reinut urmtorul lucru: dac n coada de mesaje apar mai multe mesaje WM_PAINT, sistemul va trata numai ultimul mesaj. n fapt ultima redesenare a ferestrei rmne vizibil, restul afirilor ar fi consumatoare de timp i n plus ar crea i un efect neplcut datorat rdesenrilor succesive. 24

S explicm codul din DrawHello. hDC = BeginPaint(hwnd, &paintStruct); BeginPaint ncearc s completeze variabila paintStruct i ca rspuns obine un context de dispozitiv care va trebui folosit de funciile din GDI. Ieirile grafice au nevoie de acest context de dispozitiv. GetClientRect(hwnd, &clientRect); Se obin dimensiunile zonei client, completate n clientRect. Observai parametrii funciei: hwnd va indica pentru ce fereastr se dorete acest lucru. DPtoLP(hDC, (LPPOINT)&clientRect, 2); Coordonatele fizice sunt transformate n coordonate logice. Primul parametru, hDC, indic pentru ce context de dispozitiv se face acest lucru. DrawText(hDC, "Hello, World!", -1, &clientRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); Se afieaz n zona client, Hello, World!, folosind contextul de dispozitiv obiunut de BeginPaint. EndPaint(hwnd, &paintStruct); Se termin desenarea, i se distrug informaiile din paintStruct. La terminarea funciei, hDC se distruge, fiind local. n fapt dup EndPaint hDC-ul nu mai este valid. Procedura fereastr nu este nimic altceva dect o structura mare switch. Mai multe bucle de mesaje i proceduri fereastr Aplicaiile pot avea cte bucle de mesaje doresc. De exemplu o aplicaie care are propria bucl de mesaje i face apel la MessageBox va avea cel puin dou bucle de mesaje. Pentru exemplificare vom considera cazul desenrii libere realizat cu o captur a mouse-lui. Aplicaia trebuie s fie n stare s trateze mesajele WM_LBUTTONDOWN, WM_LBUTTONUP i WM_MOUSEMOVE pentru a realiza aceast desenare. Vom avea tot timpul n minte faptul c un eveniment de mouse, n zona client, va genera un mesaj care va fi nsoit de coordonatele punctului unde acesta a avut loc. Logica aplicaiei este urmtoarea: bucla de mesaje va trata mesajul WM_LBUTTONDOWN. n cadrul funciei ce trateaz acest mesaj se va realiza capturarea mouse-lui, astfel aplicaia este informat de orice micare a mouse-lui prin tratarea mesajelor WM_MOUSEMOVE i WM_LBUTTONUP. Ieirea din cea de-a doua bucl de mesaje se face la tratarea mesajului WM_LBUTTONUP, caz n care i capturarea mouse-lui nceteaz. De reinut c n cadrul acestei a doua bucle de mesaje controlm mereu dac mouse-ul este capturat pentru zona client. Acest lucru nseamn c dac facem clic stnga n afara zonei client i inem butonul stng al mouse-lui apsat i ne micam prin zona client nu se va desena nimic. Mouse-ul nu a fost capturat de aceast fereastr. Funcii noi n acest cod. GetMessagePos() = obine coordonatele punctului unde se afl mouse-ul, coordonate relative la ecran. Coordonatele sunt obinute ntr-un DWORD, care conine n primii doi octeti valoarea lui x, iar n ultimii doi octei valoarea lui y. (Numrtoarea octeilor se face de la stnga la dreapta.) Macro-ul MAKEPOINTS transform valoarea unui DWORD ntr-o structur de tip POINTS. Cum zona client (fereastra) este plasat n cadrul ecranului, va trebui s translatm aceste coordonate n zona client. 25

ScreenToClient() = transform coordonate ecran n zona client. DPtoLP() = transform coordonatele fizice de dispozitiv n coordonate logice, necesare pentru a desena n zona client. LineTo() = deseneaz un segment de la origine (sau punctul stabilit cu MoveTo, MoveToEx) pn la punctul curent. GetCapture() = testeaz dac mouse-ul a fost capturat de fereastra aplicaiei. SetCapture(HWND ) = realizeaz capturarea mouse-ului pentru fereastra cu handler-ul specificat. ReleaseCapture() = elibereaz capturarea mouse-ului. GetDC() = obine un context de dispozitiv pentru a desena n fereastr (zona client). ReleaseDC() = elibereaz contextul de dispozitiv obinut cu GetDC. #include <windows.h> void AddSegmentAtMessagePos(HDC hDC, HWND hwnd, BOOL bDraw) { DWORD dwPos; POINTS points; POINT point; dwPos = GetMessagePos(); points = MAKEPOINTS(dwPos); point.x = points.x; point.y = points.y; ScreenToClient(hwnd, &point); DPtoLP(hDC, &point, 1); if (bDraw) LineTo(hDC, point.x, point.y); else MoveToEx(hDC, point.x, point.y, NULL); } void DrawHello(HWND hwnd) { HDC hDC; MSG msg; if (GetCapture() != NULL) return; hDC = GetDC(hwnd); if (hDC != NULL) { SetCapture(hwnd); AddSegmentAtMessagePos(hDC, hwnd, FALSE); while(GetMessage(&msg, NULL, 0, 0)) { if (GetCapture() != hwnd) break; switch (msg.message) { case WM_MOUSEMOVE: AddSegmentAtMessagePos(hDC, hwnd, TRUE); break; case WM_LBUTTONUP: goto ExitLoop; default: DispatchMessage(&msg); } } ExitLoop: ReleaseCapture(); 26

ReleaseDC(hwnd, hDC);

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_LBUTTONDOWN: DrawHello(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR d3, int nCmdShow) { MSG msg; HWND hwnd; WNDCLASS wndClass; if (hPrevInstance == NULL) { memset(&wndClass, 0, sizeof(wndClass)); wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.hInstance = hInstance; wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wndClass.lpszClassName = "HELLO"; if (!RegisterClass(&wndClass)) return FALSE; } hwnd = CreateWindow("HELLO", "HELLO", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; } Observaie. Mesajul WM_MOUSEMOVE poate fi tratat i n bucla de mesaje din WinMain, dar pentru a realiza aceeai funcionalitate codul i logica aplicaiei trebuiesc schimbate.

27

Concluzii Fiecare aplicaie Windows este construit n jurul unei bucle de mesaje. O bucl de mesaje face apeluri repetate la funciile GetMessage sau PeekMessage i regsete mesajele pe care le dispecereaz procedurilor fereastr prin funcia DispatchMessage. Procedurile fereastr sunt definite pentru clasele fereastr n momemntul cnd clasa fereastr a fost nregistrat prin RegisterClass. Mesajele adresate aplicaiei sunt tratate de procedura fereastr sau sunt trimise procedurii implicite DefWindowProc sau DefDlgProc n situaia cnd nu sunt tratate de procedura fereastr. Orice mesaj windows trebuie tratat, nu trebuie pierdut. Mesajele pot fi plasate sau trimise unei aplicaii. Mesajele plasate sunt depozitate n coada de unde sunt regsite cu GetMessage sau PeekMessage. Fa de un mesaj plasat, un mesaj trimis (SendMessage) implic imediat un apel al procedurii fereastr. Cu alte cuvinte nu se termin execuia funciei SendMessage pn cnd mesajul nu a fost tratat. O aplicaie poate avea mai multe bucle de mesaje.

28

Clase de baz in MFC


Programarea Windows pe care ncercm s o nvm n cadrul acestui curs se bazeaz pe biblioteca de clase MFC Microsoft Foundation Classes. Fr a ntelege ns mecanismul programrii sub Windows, aceste clase nu ne ajut foarte mult. Dei exemplele ce urmeaz vor fi cu MFC, tot vom face trimeteri la SDK i n cele ce urmeaz. Pentru nceput vom descrie cele mai importante clase din MFC (cu care ne ntlnim in exemplele noastre) i chiar dac nu vom reine multe lucruri la prima lectur, cel puin nu ne vom speria cd vom gsi prin cod referine la aceste clase sau clase derivate. Observaie: O fereastr n SDK este identificat de o variabil de tip HWND. Acest lucru rmne valabil i n MFC, dar n general vom lucra mai mult cu pointeri la structuri ce reprezint ferestre. Data membru de tip HWND ce identific fereastra (de exemplu) va fi nglobat n obiectul ce reprezint fereastra, i de aici rezult c funciile ce aveau un parametru de tip HWND (in SDK) nu-l vor mai avea in MFC. In loc de UpdateWindow(hWnd, nCmdShow); vom ntlni un cod de forma m_pMainWnd->UpdateWindow(nCmdShow);. CObject #include <afx.h> CObject este clasa de baz principal pentru MFC. Majoritatea claselor din MFC sunt drivate din aceast clas. CObject furnizeaz urmtoarele servicii: suport de serializare; informaii despre clas n timpul execuiei; diagnosticare obiecte; compatibilitate cu clasele colecie (CArray, CList, etc.). CObject nu suporta motenirea multipl i CObject trebuie s fie cel mai din stanga ierarhiei n cazul derivrii. Dac folosim CObject atunci n clasele derivate putem beneficia de macro-urile: DECLARE_DYNAMIC i IMPLEMENT_DYNAMIC, permite accesul in timpul execuiei la numele clasei i poziia acesteia n ierarhie. DECLARE_SERIAL i IMPLEMENT_SERIAL, includ toat funcionalitatea macrourilor de mai sus, i permit unui obiect s fie serializat (scris/citit n/din arhiv). Exemplu pentru serializare class XPersoana : public CObject { pubic: // Interfata private: CString m_Nume; WORD m_Varsta; protected: virtual void Serialize(CArchive& ar); }; Implementarea funciei Serialization() pentru aceasta clas ar putea fi: void XPersoana::Serialize(CArchive& ar) { if (ar.IsStoring()) ar << m_Nume << m_Varsta; 29

else }

ar >> m_Nume >> m_Varsta;

Cnd avem nevoie s memorm o instan a clasei XPersoana pe disc sau s o citim din disc, vom apela funcia Serialize(). Exemplu class CMyDocument : public CDocument {... XPersoana m_persoane[100]; ... }; void CMyDocument::Serialize(CArchive& ar) { for (int i=0;i<100;i++) m_persoane[i].Serialize(ar); } Considerm in continuare c printre aceste persoane exist una care este manager i deci are in subordine alte persoane. Vom construi clasa class XManager : public CPersoana { public: // Interface private: CList<XPersoana*, XPersoana*> m_subordonati; }; unde subordonaii sunt constituiti intr-o list simplu nlnuit. Schimbm i implementarea clasei CMyDocument astfel: class CMyDocument : public CDocument { ... CList<XPersoana*, XPersoana*> m_persoane; ... }; In timpul execuiei, lista nlnuit ar putea arta astfel: m_persoane -> XPersoana(Popescu, 20) -> XManager(Zetu,20) -> XPersoana(Zoe,12) -> etc. Deci nu mai avem un proces simplu de serializare. Adaugand macro-ul DECLARE_SERIAL in definitia clasei si IMPLEMENT_SERIAL in implementarea clasei, un pointer la o instan a clasei poate fi memorat i realocat dintr-o arhiv. In concluzie implementarea completa pentru aceasta clasa este: class XPersoana : public CObject { public: 30

// Interfata private: CString m_Nume; WORD m_Varsta; protected: virtual void Serialize(CArchive& ar); DECLARE_SERIAL(XPersoana) }; class XManager : public XPersoana { public: // Interfata private: CList<XPersoana*, XPersoana*> m_subordonati; protected: void Serialize(CArchive& ar); DECLARE_SERIAL(XManager) }; IMPLEMENT_SERIAL(XPersoana, CObject, 1) IMPLEMENT_SERIAL(XManager, XPersoana, 1) // // Aceasta este o functie helper pentru clasa colectie // template CList si ne spune cum memoreaza obiecte // de tipul XPersoana* // void SerializeElements(CArchive& ar, XPersoana** pElemente, int nCount) { for (int i=0;i < nCount; i++) { if (ar.IsStoring()) ar << pElemente[i]; else ar >> pElemente[i]; } } void XPersoana::Serialize(CArchive& ar) { if (ar.IsStoring()) ar << m_Nume << m_Varsta; else ar >> m_Nume >> m_Varsta; } void XManager::Serialize(CArchive& ar) { XPersoana::Serialize(ar); m_subordonati.Serialize(ar); } void CMyDocument::Serialize(CArchive& ar) { } m_persoane.Serialize(ar);

31

CCmdTarget CCmdTarget este clasa de baz pentru arhitectura de tratare a mesajelor din biblioteca MFC. Dac se dorete crearea unei noi clase ce trebuie s trateze mesaje, aceasta trebuie derivat din CCmdTarget sau dintr-un descendent al acesteia. Metoda OnCmdMsg ( ) este folosit pentru rutarea, distribuirea mesajelor i tratarea acestora. n plus clasa CCmdTarget mai gestioneaz trecerea cursorului n starea de ateptare (cursor cu clepsidr) i ieirea din aceast stare folosind metodele BeginWaitCursor ( ), EndWaitCursor ( ) i RestoreWaitCursor ( ). Clase derivate din CCmdTarget: CView, CWinApp, CDocument, CWnd si CFrameWnd. Pentru a lucra cu comenzi va trebui sa completam harta de mesaje (se face corespondenta intre mesaj si functia ce-l trateaza) iar in majoritatea cazurilor acest lucru este facut de ClassWizard. Bine-nteles codul din functia ce trateaza mesajul trebuie scris de noi. In general mesajele sunt trimise ferestrei cadru principale, dar comenzile sunt rutate apoi catre alte obiecte. In mod normal o clasa ruteaza comenzile la alte obiecte pentru a le da sansa sa trateze comanda. Daca comanda nu este trata de nici un obiect atunci se cauta in harta de mesaje a clasei pentru a vedea daca mesajul are asociata o functie de tratare. In situatia cind clasa nu trateaza comanda, aceasta este rutata catre clasa de baza a clasei curente. Vom explica pe larg aceasta rutare intr-un curs viitor. Urmatorul exemplu este din MSDN. Se explica sintaxa metodei OnCmdMsg i apoi se d un exemplu. CCmdTarget::OnCmdMsg virtual BOOL OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo ); Return Value Nonzero if the message is handled; otherwise 0. Parameters nID Contains the command ID. nCode Identifies the command notification code. pExtra Used according to the value of nCode. pHandlerInfo If not NULL, OnCmdMsg fills in the pTarget and pmf members of the pHandlerInfo structure instead of dispatching the command. Typically, this parameter should be NULL. Remarks Called by the framework to route and dispatch command messages and to handle the update of command userinterface objects. This is the main implementation routine of the framework command architecture. At run time, OnCmdMsg dispatches a command to other objects or handles the command itself by calling the root class CCmdTarget::OnCmdMsg, which does the actual message-map lookup. Observatie: 1. In concluzie, aceast funcie este apelata de cadrul de lucru (framework), asta nsemnnd c nu vom gsi n cod un apel explicit la aceast funcie. Funcia este folosit pentru a ruta mesajele de comand. 2. Fiecare comand are un ID (identificator) de exemplu IDM_FILE_NEW, poate fi ID-ul pentru comanda de meniu, File->New. Example // This example illustrates extending the framework's standard command // route from the view to objects managed by the view. This example // is from an object-oriented drawing application, similar to the // DRAWCLI sample application, which draws and edits "shapes". 32

BOOL CMyView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // Extend the framework's command route from the view to // the application-specific CMyShape that is currently selected // in the view. m_pActiveShape is NULL if no shape object // is currently selected in the view. if ((m_pActiveShape != NULL) && m_pActiveShape->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // If the object(s) in the extended command route don't handle // the command, then let the base class OnCmdMsg handle it. return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

// The command handler for ID_SHAPE_COLOR (menu command to change // the color of the currently selected shape) was added to // the message map of CMyShape (note, not CMyView) using ClassWizard. // // // // // The menu item will be automatically enabled or on whether a CMyShape is currently selected in depending on whether CMyView::m_pActiveView is necessary to implement an ON_UPDATE_COMMAND_UI or disable the menu item. disabled, depending the view, that is, NULL. It is not handler to enable

BEGIN_MESSAGE_MAP(CMyShape, CCmdTarget) //{{AFX_MSG_MAP(CMyShape) ON_COMMAND(ID_SHAPE_COLOR, OnShapeColor) //}}AFX_MSG_MAP END_MESSAGE_MAP() CWinThread Un obiect din clasa CWinThread reprezint un fir de execuie dintr-o aplicaie. Firul de execuie principal este un obiect al clasei CWinApp ce este o clas derivat din CWinThread. Pe lng firul de execuie principal se mai pot folosi i alte fire de execuie, folosind obiecte CWinThread. Exista doua tipuri de fire de executie suportate de CWinThread: 1. fire de lucru (fara interfata utilizator, deci nu au bucla de mesage); 2. fire cu interfata utilizator. O problema important legat de firele de execuie o constituie sincronizarea acestora (executie sincronizat). Pentru o documentare asupra mebrilor clasei CWinThread consultati MSDN.

CWinApp
Derivat din CWinThread Clasa CWinApp este clasa de baz pentru obiectul aplicaie. Clasa aplicaie din MFC ncapsuleaz iniializarea, execuia i terminarea unei aplicaii Windows.

33

Fiecare aplicaie MFC poate conine doar un singur obiect derivat din CWinApp. Acest obiect este global i este construit naintea construirii ferestrelor, fiind disponibil atunci cnd sistemul de operare Windows apeleaz funcia WinMain, care este oferit de biblioteca MFC. Cnd se deriveaz o clas aplicaie din CWinApp, se suprascrie funcia membru InitInstance ( ) pentru a se construi i iniializa noua aplicaie. Pe lng funciile membru ale lui CWinApp, MFC ofer funcii globale pentru a obine informaii despre obiectul aplicaie curent: AfxGetApp ( ) returneaz un pointer la obiectul aplicaie curent; AfxGetInstanceHandle ( ) handle la instana aplicaie curent; AfxGetResourceHandle ( ) handle la resursele aplicaiei; AfxGetAppName ( ) numele aplicaiei. Exemplu class Cmfc1App : public CWinApp { public: Cmfc1App(); // Overrides public: virtual BOOL InitInstance(); // Implementation afx_msg void OnAppAbout(); DECLARE_MESSAGE_MAP() }; iar in implementare BOOL Cmfc1App::InitInstance() { // InitCommonControls() is required on Windows XP if an application // manifest specifies use of ComCtl32.dll version 6 or later to enable // visual styles. Otherwise, any window creation will fail. InitCommonControls(); CWinApp::InitInstance(); // Initialize OLE libraries if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; } AfxEnableControlContainer(); // // // // // // Standard initialization If you are not using these features and wish to reduce the size of your final executable, you should remove from the following the specific initialization routines you do not need Change the registry key under which our settings are stored TODO: You should modify this string to be something appropriate 34

// such as the name of your company or organization SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(4); // Load standard INI file options (including MRU) // Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(Cmfc1Doc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(Cmfc1View)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate); // Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line. Will return FALSE if // app was launched with /RegServer, /Register, /Unregserver or /Unregister. if (!ProcessShellCommand(cmdInfo)) return FALSE; // The one and only window has been initialized, so show and update it m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); // call DragAcceptFiles only if there's a suffix // In an SDI app, this should occur after ProcessShellCommand } return TRUE;

CWnd
Clasa CWnd este clasa de baz pentru toate celelalte clase fereastr oferite de MFC. Aceast clas are o mare parte din funciile necesare n obiectele frame, view, controale, etc. Obiectul fereastr este un obiect al clasei CWnd sau derivat din clasa CWnd care este creat direct de ctre program. Fereastra, pe de alt parte este un handle ctre o structur intern din Windows care conine resursele ferestrei respective. CFrameWnd Clasa CFrameWnd conine o implementare implicit pentru urmtoarele funcii ale ferestrei principale dintr-o aplicaie Windows: menine ordinea cu ferestrele de vizualizare active; comenzile i mesajele de notificare le trimite ferestrei de vizualizare active; modificarea textului ce apare pe bara de titlu a ferestrei n funcie de fereastra de vizualizare activ; se ocup de poziionarea barelor de control, a ferestrelor de vizualizare i a altor ferestre copil, n zona client a ferestrei principale; bara de meniu; meniul sistem al aplicaiei; 35

acceleratori (tratarea tastelor speciale); starea iniial a aplicaiei (minimizat, maximizat); help senzitiv la context; bara de stare; se ocup de perechile document-view.

Exemplu class CMainFrame : public CFrameWnd { protected: // create from serialization only CMainFrame(); DECLARE_DYNCREATE(CMainFrame) // Attributes public: // Operations public: // Overrides public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; // Generated message map functions protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); DECLARE_MESSAGE_MAP() }; iar in implementare avem (cod partial pentru crearea ferestrei) int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) 36

{ }

TRACE0("Failed to create toolbar\n"); return -1; // fail to create

if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want // the toolbar to be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); } return 0;

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return TRUE; } Structura CREATESTRUCT este asemntoare cu structura WNDCLASSEX. typedef struct tagCREATESTRUCT { LPVOID lpCreateParams; HANDLE hInstance; HMENU hMenu; HWND hwndParent; int cy; int cx; int y; int x; LONG style; LPCSTR lpszName; LPCSTR lpszClass; DWORD dwExStyle; } CREATESTRUCT;

37

Document templates - abloane de document Aplicaiile MFC folosesc implicit un model de programare ce separ datele programului de partea de afiare a acestor date i de majoritatea interaciunilor dintre utilizator i date. n acest model, un obiect document citete i scrie datele pe disc i mai poate oferi nite funcii de lucru cu aceste date. Un obiect distinct se ocup de partea de vizualizare a datelor ntr-o anumit fereastr i trateaz interaciunea utilizatorului cu acestea. Obiectul de vizualizare poate citi datele din document i le poate modifica la aciunea utilizatorului. Modelul document/view este aplicabil i n situaia n care exist mai multe ferestre de vizualizare pentru un document, lsnd libertatea fiecrui obiect de vizualizare s-i afieze datele, n timp ce partea de cod comun tuturor ferestrelor de vizualizare (cum ar fi partea de calcule) se poate implementa n document. Documentul se mai ocup i de reactualizarea ferestrelor de vizualizare dac datele au fost modificate (de ctre document sau de ctre una din ferestrele de vizualizare). Arhitectura MFC document/view suport o implementare uoar a ferestrelor de vizualizare multiple, tipuri de documente diferite, ferestre mprite (splitter windows), i alte tipuri de caracteristici ale interfeelor. La baza modelului document/view stau urmtoarele patru clase: CDocument (sau COleDocument); CView; CFrameWnd; CDocTemplate. Prile din MFC cele mai vizibile, att pentru utilizator ct i pentru programator, sunt documentul i ferestrele de vizualizare. Cea mai mare parte din munca investit ntr-un proiect const n scrierea claselor document i view. Clasa CDocument ofer funciile de baz pentru clasele document specifice aplicaiei. Un document reprezint un bloc de date pe care utilizatorul l poate deschide cu comanda Open, salva cu comanda Save, etc. Clasa CView st la baza claselor view ale aplicaiei. Un view este ataat unui document i funcioneaz ca un intermediar ntre document i utilizator: view-ul construiete n fereastr o imagine a documentului i interpreteaz aciunile utilizatorului i le transmite documentului. n figura urmtoare este prezentat relaia dintre document i view: Documentele, ferestrele de vizualizare asociate i ferestrele cadru care conin ferestrele de vizualizare pentru un document sunt create de un template document. Acesta este responsabil de crearea i gestionarea tuturor documentelor de acelai tip. Orice aplicaie MFC SDI creat cu AppWizard are cel puin un document template. Acest template creaz i definete relaiile dintre document, fereastra cadru i fereastra de vizualizare. Cnd este creat un nou element sau cnd este deschis un document, acest template este folosit pentru a crea cele trei elemente n urmtoarea ordine: documentul, fereastra cadru i fereastra de vizualizare. Pentru o aplicaie MDI mai apare un pas n plus fa de aplicaia SDI: crearea ferestrei cadru principale a aplicaiei naintea crerii documentului. Template-ul document mapeaz documentul, fereastra cadru i fereastra de vizualizare pe clasele proprii aplicaiei. Crearea unui template (creat implicit de AppWizard): CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CSdiDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CSdiView)); AddDocTemplate(pDocTemplate); Secvena de apeluri la crearea documentului: CMyApp::InitInstance ( ); dac aplicaia este MDI atunci se creaz i afieaz fereastra cadru principal; parcurge i proceseaz linia de comand (dac nu exist se apeleaz OnFileNew ( )); se selecteaz template-ul document; 38

se creaz un document gol prin CDocTemplate::OpenDocumentFile ( ); CDocTemplate::CreateNewDoc ( ); constructorul pentru CMyDocument; CMyDocument::OnNewDocument ( ). Crearea ferestrei cadru: CDocTemplate::CreateNewFrame ( ) constructorul pentru CMainFrame; CMainFrame::LoadFrame ( ); CMainFrame::PreCreateWindow ( ); CMainFrame::OnCreate ( ); CMainFrame::OnCreateClient ( ); Crearea ferestrei de vizualizare: CMainFrame::CreateView ( ); constructorul pentru CMyView; CMyView::PreCreateWindow ( ); CMainFrame::InitialUpdateFrame ( ); CMyView::OnInitialUpdate ( ); CMyView::OnActivateFrame ( ); CMainFrame::ActivateFrame ( ); CMyView::OnActivateView ( );

CDocument
Clasa CDocument furnizeaza functionalitatea de baza in arhitectura document-view implementata in MFC si are ca scop gestionarea documentului aplicatiei. CDocument suporta operatii standard de creare, incarcare si salvare a documentului. O aplicatie poate suporta mai mult decat un document. Fiecare tip are asociat un document template (sablon). Acest sablon specifica resursele utilizate pentru acel tip de document. Utilizatorul interactioneaza cu un document prin intermediul unui obiect CView. Un obiect CView reprezinta o vizualizare a documentului in zona client. Un document poate avea asociate mai multe vizualizari. Un document primeste comenzi forward-ate de vizualizarea activa precum si comenzi din meniu (Open, Save). CView Clasa CView (obiecte instantiate direct sau indirect) furnizeaza vizualizarea documentului. O vedere actioneaza ca un intermediar intre document si utilizator. O vedere este o fereastra descendenta din fereastra cadru.O vedere este responsabila de tratarea mai multor tipuri de intrari: tastatura, mouse, operatii de drag & drop, comenzi din meniu, toobar sau bare de defilare (scroll bars). Metoda cea mai importanta din aceasta clasa este OnDraw, responsabila pentru desenarea in zona client. O alta metoda folosita este cea care face legatura intre document si vizualizare: GetDocument(), functie ce returneaza un pointer la obiectul de tip document. Alte clase de vizuallizare: CCtrlView, CDaoRecordView, CEditView, CFormView, CListView, CRecordView, CRichEditView, CScrollView, CTreeView.

39

Harta de mesaje
Hrile de mesaje sunt pri ale modelului MFC de programare Windows. n loc de a scrie funcia WinMain() care trimite mesaje la procedura fereastr (funcia) WindProc() i apoi s controlm ce mesaj a fost trimis pentru a activa funcia corespunztoare, vom scrie doar funcia care trateaz mesajul i vom aduga mesajul la harta de mesaje a clasei. Cadrul de lucru va face operaiunile necesare pentru a ruta acest mesaj n mod corect. Construirea hrii de mesaje Hrile de mesaje se construiesc n dou etape. Declararea hrtii de mesaje (macro DECLARE_MESSAGE_MAP()) se face n fiierul .h al clasei, iar implementarea se face in fiierul .cpp al clasei (BEGIN_MESSAGE_MAP() ... END_MESSAGE_MAP()). Exemplu //{{AFX_MSG(CShowStringApp) afx_msg void OnAppAbout(); //the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_MSG DECLARE_MESSAGE_MAP() Se declar funcia OnAppAbout() care este prefixat cu afx_msg ce constituie un comentariu pentru compilatorul de C++, dar care indic vizual c aceast funcie trateaz un mesaj. Aceast funcie o vom gsi i n cadrul macroului BEGIN_MESSAGE_MAP(), ca un parametru al macro-ului ON_COMMAND(). Primul parametru al acestui din urm macro este ID-ul mesajului (comenzii n acest caz), iar al doilea numele funciei ce trateaz acest mesaj. Cod in .cpp BEGIN_MESSAGE_MAP(CShowStringApp, CWinApp) //{{AFX_MSG_MAP(CShowStringApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) //DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard file based document commands ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP() Macro-ul DECLARE_MESSAGE_MAP() adaug date mebru i funcii la clasa respectiv. Practic se declar o tabel cu un numr de intrri variabil (sfritul tabelei este marcat (completat) de END_MESSAGE_MAP()) i funcii care opereaz cu acest elementele acestui tabel. Macro-uri pentru harta de mesaje BEGIN_MESSAGE_MAP i END_MESSAGE_MAP sunt macro-uri care ca i macro-ul DECLARE_MESSAGE_MAP din fiierul .h, declar anumite variabile membru i funcii pe care cadrul de lucru le va utiliza pentru a naviga prin hrile de mesaje ale tuturor obiectelor din sistem. Dintre macro-urile folosite n hrile de mesaje, enumerm: DECLARE_MESSAGE_MAPfolosit n fiierul .h pentru a declara c va exista o hartesaje in .cpp BEGIN_MESSAGE_MAPMarcheaz nceputul hrii de mesaje n fiierul surs. END_MESSAGE_MAPMarcheaz sfritul hrii de mesaje n fiierul surs. ON_COMMANDFolosit pentru a face legtura ntre comenzi i funciile care trateaz aceste comenzi. 40

ON_COMMAND_RANGEFolosit pentru a face legtura ntre un grup de comenzi i funcia care le trateaz. ON_CONTROLFolosit pentru a face legtura ntre un mesaj de notificare al unui control i funcia ce-l trateaz. ON_CONTROL_RANGEFolosit pentru a face legtura ntre un grup de mesaje de notificare al unui control i funcia corespunztoare. ON_MESSAGEFolosit pentru a realiza legtura ntre un mesaj definit de utilizator i funcia care-l trateaz. ON_REGISTERED_MESSAGEFolosit pentru a realiza legtura ntre un mesaj defint de utilizator, dar nregistrat i funcia care-l trateaz. ON_UPDATE_COMMAND_UIFolosit pentru a indica funcia care va face actualizarea pentru o comand specific. ON_COMMAND_UPDATE_UI_RANGECa mai sus, dar pentru un grup de comenzi. ON_NOTIFYFolosit pentru a indica funcia ce va aduga informaii suplimentare, pentru un mesaj de notificare al unui control. ON_NOTIFY_RANGECa mai sus, dar pentru un grup de mesaje de notificare al unui control. ON_NOTIFY_EXCa la ON_NOTIFY, dar funcia va ntoarce TRUE sau FALSE pentru a indica dac notificarea poate fi trecut altui obiect pentru tratri suplimentare. ON_NOTIFY_EX_RANGECa mai sus, dar se refer la un grup de comenzi de notificare.

n plus la aceste macro-uri, exist peste 100 de macro-uri, unul pentru fiecare din cele mai comune mesaje. De exemplu macro-ul ON_CREATE pentru mesajul WM_CREATE, etc. n mod obinuit aceste macro-uri sunt adugate la clas de ctre ClassWizard. Cum lucreaz harta de mesaje ? Fiecare aplicaie are un obiect motenit din clasa CWinApp i o funcie membru Run(). Aceast funcie apeleaz funcia CWinThread::Run(), care apeleaz GetMessage(), TranslateMessage() i DispatchMessage(). Funcia fereastr (n SDK) tie handler-ul ferestrei pentru care este trimis mesajul. Fiecare obiect fereastr folosete acelai stil al clasei Windows i aceeai funcie WindProc, numit AfxWndProc(). MFC menine ceva asemntor, numit handle map, o tabel cu handler-ii ferestrelor i pointeri la obiecte, i framework-ul folosete aceasta pentru a trimite un pointer la obiectul C++, un CWnd*. n continuare el apeleaz WindowProc(), o funcie virtual a acestui obiect. Datorit polimorfismului, indiferent c este vorba de un Button sau o vizualizare se va apela funcia corect. WindowProc() apeleaz OnCmdMsg(), funcia C++ care efectiv manipuleaz mesajul. Mai nti caut dac acesta este un mesaj, o comand sau o notificare. Presupunnd c este un mesaj. caut n harta de mesage a clasei, folosind funciile i variabilele membru adugate la clas de DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP i END_MESSAGE_MAP. Modul de organizare al acestei tabele permite cutarea mesajului, dac este nevoie, n toat arborescena clasei. AfxWndProc()->WindowProc()->OnCmdMsg()

nelegerea comenzilor
O comand este un tip special de mesaj. Windows genereaz comenzi cnd utilizatorul alege un articol de meniu, apas un buton, sau altfel spune sistemului s fac ceva. Pentru un articol de meniu se primete mesajul WM_COMMAND iar pentru notificarea unui control WM_NOTIFY, cum ar fi selectarea dintr-un list box. Comenzile i notificrile sunt trecute prin SO ca orice alt mesaj, pn cnd acestea ajung la OnWndMsg(). n acest punct pasarea mesajului windows nceteaz i se starteaz rutarea comenzilor n MFC. Mesajele de comand au ca prim parametru, ID-ul articolului din meniu care a fost selectat sau a butonului care a fost apsat. Rutarea comenzilor este mecanismul pe care OnWndMsg() l folosete pentru a trimite comanda (sau notificarea) la obiectele care pot trata acest mesaj. Numai obiectele care sunt motenite din CWnd pot primi mesaje, dar toate obiectele care sunt motenite din CCmdTarget, incluznd CWnd i CDocument, pot primi 41

comenzi sau notificri. Aceasta nseman c o clas motenit din CDocument poate avea o hart de mesaje. Pot s nu existe mesaje n aceast hart ci numai pentru comenzi i notificri, dar tot hart de mesaje se numete. Comenzile i notificrile ajung la clas prin mecanismul de rutare al comenzilor. OnWndMsg() apeleaz CWnd::OnCommand() sau CWnd::OnNotify(). OnCommand() apeleaz OnCmdMsg(). OnNotify() apeleaz de asemenea OnCmdMsg(). Bineneles c ambele funcii efectueaz anumite controale nainte de a face aceste apeluri. OnCmdMsg() este virtual, ceea ce nseamn c diferite comenzi au implementri diferite. Implementarea pentru fereastra cadru trimite comanda vizualizrilor i documentelor pe care le conine. Comanda pentru actualizri Folosit n special pentru actualizarea articolelor din meniu. De exemplu cnd se selecteaz text in vizualizare i opiunile de Copy, Cut, Paste, Undo sunt implementate aceste articole de menu vor fi afiate n starea enable sau disable (funcie de logica programului). Exist dou posibiliti de a face acest lucru: continuous update approach i update-on-demand approach. Continuous update approach presupune existena unei tabele mari ce conine cte o intrare pentru fiecare meniu i un flag care va indica dac opiunea este disponibil sau nu. Cea de-a doua posibilitate presupune controlarea tuturor condiiilor asupra unui articol de meniu nainte ca meniul s fie afiat. n acest caz obiectul care are meniul va ti mai multe despre acesta, n schimb nu toat aplicaia va avea acces la aceste informaii. Tehinca MFC-ului este de a utiliza un obiect numit CCmdUI, o comand a interfeei utilizatorului, i de a da acest obiect cnd se trimite mesajul CN_UPDATE_COMMAND_UI. n harta de mesaje va aprea macro-ul ON_UPDATE_COMMAND_UI. Ce se ntmpl n realitate? SO trimite mesajul WM_INITMENUPOPUP; clasa CFrameWnd va construi un obiect CCmdUI, seteaz variabilele membru ce corespund primului articol din meniu i apeleaz funcia membru DoUpdate(). DoUpdate() trimite mesajul CN_COMMAND_UPDATE_UI la ea nsi, cu un pointer la obiectul CCmdUI. Se vor seta variabilele membru ale obiectului CCmdUI cu articolul doi din meniu i procesul continu pn cnd este parcurs tot meniul. Obiectul CCmdUI este folosit pentru a valida (enable) sau invalida (disable) [gray(disable) sau ungray(enable) ] articolele din meniu sau butoane. CCmdUI are urmtoarele funcii membru: Enable() Are un parametru care poate lua valorile TRUE sau FALSE (implicit TRUE). SetCheck() Marcheaz sau demarcheaz articolul. SetRadio() Seteaz sau nu unul din butoanele radio al unui grup. SetText()Seteaz textul unui meniu sau buton. DoUpdate()Genereaz mesajul. Exemplu: BEGIN_MESSAGE_MAP(CWhoisView, CFormView) ... ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste) ... END_MESSAGE_MAP() void CWhoisView::OnUpdateEditPaste(CCmdUI* pCmdUI) { pCmdUI->Enable(::IsClipboardFormatAvailable(CF_TEXT)); } Exemplu complet de aplicatie (SDI) generata de AppWizard l gsiti pe pagina http://www.infoiasi.ro/~iasimin/IDD Urmrii codul colorat n rou, cod adaugat cu ajutorul classwizard-ului. 42

Incercai s generai acest proiect pe calculatoarele dv. Inserati cu classwizard-ul codul scris cu rou sau introducei-l manual. Observai ce se ntmpl. Observaie: Exist ClassWizard i AppWizard, wizard-uri diferite. Primul este pentru clase, al doilea pentru aplicaii.

Crearea unei aplicaii Windows


Pentru generarea unei aplicaii se folosete AppWizard. n primul rnd aici se lucreaz cu proiecte. Un proiect poate conine mai multe aplicaii. Pentru a crea un proiect vom folosi comenzile File->New->Projects. Din lista prezentat n pagina (tab-ul) Projects vom selecta MFC AppWizard (EXE), selectm locul unde va fi memorat pe HDD i apoi completm numele proiectului. Dup acest lucru va trebui s completm o serie de informaii grupate pe etape. Etapa 1. Tip aplicaie n etapa 1 (pas 1) vom selecta tipul aplicaiei (se alege interfaa cu utilizatorul). Avem urmtoarele posibiliti: O aplicaie single document interface (SDI), are numai un document deschis la un moment dat. Cnd selectm File->Open, fiierul existent i care este deschis va fi nchis nainte ca s se deschid noul document. O aplicaie multiple document interface (MDI), cum ar fi Excel sau Word, poate deschide mai multe documente odat. Dac dorim vizualizri multiple pentru un document va trebui s construim o aplicaie MDI. O aplicaie dialog-based, cum ar fi utilitarul Character Map. Aplicaiile nu au meniu.

OBSERVAIE:: Aplicaiile bazate pe dialog sunt diferite de cele de tip SDI sau MDI. Vor fi tratate n mod separat.

Mai exist un checkbox care ne d posibilitatea de a indica dac dorim suport pentru arhitectura Document/View. Opiunea se folosete n special pentru portarea aplicaiilor dintr-un alt sistem de dezvoltare. Nu o vom folosi. Etapa 2. Baze de date n aceast etap vom alege nivelul pentru suportul bazelor de date. Exist patru posibiliti: Pentru aplicaii fr baze de date vom selecta None. Dac dorim s avem acces la baze de date dar nu dorim s derivm vizualizarea din CFormView sau s nu avem meniu Record vom selecta Header Files Only. Dac dorim s derivm vizualizarea din CFormView i s avem meniul Record dar nu dorim s serializm documentul, vom selecta Database View Without File Support. Dac dorim suport pentru baze de date i n plus dorim i salvarea documentului vom selecta Database View With File Support. Dac selectm ultima opiune, va trebui s indicm sursa de date butonul Data Source. Etapa 3. Suport pentru documente compuse Tehnologia ActiveX i OLE este referit ca fiind tehnologia documentului compus (compound document technology). n aceast etap exist cinci posibiliti: Dac nu scriem o aplicaie ActiveX, alegem None. 43

Dac dorim o aplicaie care s conin obiecte ActiveX nglobate sau legate, cum ar fi aplicaia Word, alegem Container. Dac dorim ca aplicaia noastr s furnizeze obiecte, care pot fi nglobate, pentru alte aplicaii, dar aplicaia s nu poat fi executat separat (stand alone), vom alege Mini Server. Dac dorim ca aplicaia noastr s furnizeze obiecte, care pot fi nglobate, pentru alte aplicaii, i aplicaia s poat fi executat separat (stand alone), vom alege Full Server. Dac dorim ca aplicaia s ncorporeze opiunile 3 i 4 vom selecta Both Container and Server. Dac alegem suport pentru documentele compuse vom avea i suport pentru fiiere compuse (compound files). Fiierele compuse conin unul sau mai multe obiecte ActiveX i sunt salvate ntr-un mod special astfel nct un obiect poate fi modificat fr a rescrie ntregul fiier. Pentru acest lucru facem o selecie pe unul din butoanele radio Yes, Please, sau No, Thank You. Tot n aceast pagin ne hotrm dac aplicaia suport automatizare sau va folosi controale ActiveX. OBSERVAIE: Dac dorim ca aplicaia s fie un control ActiveX, nu trebuie s crem o aplicaie .exe obinuit. Crearea controlului ActiveX se face selectnd o alt opiune din Projects. Etapa 4. Opiuni pentru interfa. Alte Opiuni Urmtoarele opiuni afecteaz modul de afiare al interfeei: Docking Toolbar. AppWizard pregtete un toolbar. Acesta poate fi editat (adugare, modificare, tergere). Initial Status Bar. AppWizard creaz o bar de stare care afieaz mesajele ataate comenzilor din meniu sau alte mesaje specifice aplicaiei. Printing and Print Preview. Aplicaia va avea opiunile Print i Print Preview din meniul File, i o parte din codul necesar va fi generat de AppWizard. Context-Sensitive Help. Meniul Help va avea opiunile Index i Using Help, i o parte din codul necesar pentru a fi implementat Help va fi generat de AppWizard. Aceast decizie este dificil de luat mai trziu pentru c pri din cod sunt generate n diverse locuri din cadrul aplicaiei. 3D Controls. Aplicaia va arta ca o aplicaie obinuit Windows 95. Dac nu selectm aceast opiune, boxele de dialog vor avea background alb i nu vor fi umbre n jurul boxelor de editare, checkbox-urilor i alte controale. MAPI(Messaging API). Aplicaia va avea posibilitatea de trimite fax-uri, email-uri i alte mesaje. Windows Sockets. Aplicaia va putea accesa Internet-ul n mod direct, folosind protocoale ca FTP i HTTP (protocolul World Wide Web). Putem seta de asemenea numrul fiierelor din lista MRU. Implicit acest numr este 4. Butonul Advanced activeaz dou tab-uri (Document Template Strings, Window Styles) unde putem schimba numele aplicaiei, titlul ferestrei cadru, extensia fiierelor folosite n File->Open, etc. Prporpietile care pot fi setate pentru ferestrele cadru: Thick Frame. Dac nu o selectm se previne redimensionarea. Minimize Box. Maximize Box. System Menu. Minimized. Cadrul este minimizat cnd aplicaia este lansat n execuie. Pentru aplicaiile SDI, aceast opiune va fi ignorat cnd aplicaia ruleaz sub Windows 95. Maximized. The frame is maximized when the application starts. For SDI applications, this option will be ignored when the application is running under Windows 95. Alte Opiuni Dorim biblioteca MFC s fie legat ca un DLL partajabil sau n mod static? Un DLL este o colecie de funcii utilizate de diferite aplicaii. Folosirea DLL-urilor face ca programul s fie mai mic dar mai greu de instalat. Dac legtura este static crete mrimea programului dar nu mai sunt probleme deosebite cu instalarea.

44

Etapa 6. Numele fiierelor i al claselor Ultima etap stabilete numele claselor i fiierelor create de AppWizard. Putem schimba aceste nume. Dac aplicaia include o clas pentru vizualizare, care n mod obinuit este derivat din CView, putem schimba clasa de baz, de exemplu CScollView sau CEditView care ofer o funcionalitate sporit vizualizrii. Vom apsa butonul Finish pentru terminarea generrii aplicaiei. Urmeaz exemple de creare aplicaii pentru fiecare tip n parte. Crearea DLL-urilor, Aplicaiilor de tip Consol Alte opiuni ce se regsesc n tab-ul Projects: ATL COM AppWizard Custom AppWizard Database Project DevStudio Add-In Wizard Extended Stored Procedure AppWizard ISAPI Extension Wizard Makefile MFC ActiveX ControlWizard MFC AppWizard (dll) Utility Project Win32 Application Win32 Console Application Win32 Dynamic Link Library Win32 Static Library

nelegerea codului generat de AppWizard


Aplicaie SDI O aplicaie SDI are meniu pe care utilizatorul poate s-l foloseasc pentru a deschide documente i s lucreze cu ele. AppWizard genereaz cinci clase. Numele proiectului este FirstSDI. CAboutDlg, o clas de dialog pentru About CFirstSDIApp, o clas CWinApp pentru ntraga aplicaie (obiectul aplicaie) application CFirstSDIDoc, o clasa document CFirstSDIView, o clas vizualizare CMainFrame, o clas cadru Fiierul FirstSDI.h pentru aplicaie // FirstSDI.h : main header file for the FIRSTSDI application // #if !defined(AFX_FIRSTSDI_H__CDF38D8A_8718_11D0_B02C_0080C81A3AA2__INCLUDED_) #define AFX_FIRSTSDI_H__CDF38D8A_8718_11D0_B02C_0080C81A3AA2__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef __AFXWIN_H__ #error include `stdafx.h' before including this file for PCH #endif #include "resource.h" // main symbols ///////////////////////////////////////////////////////////////////////////// // CFirstSDIApp: // See FirstSDI.cpp for the implementation of this class // class CFirstSDIApp : public CWinApp 45

{ public: CFirstSDIApp(); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CFirstSDIApp) public: virtual BOOL InitInstance(); //}}AFX_VIRTUAL // Implementation //{{AFX_MSG(CFirstSDIApp) afx_msg void OnAppAbout(); // NOTE - The ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG DECLARE_MESSAGE_MAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Developer Studio will insert additional declarations // immediately before the previous line. #endif //!defined(AFX_FIRSTSDI_H__CDF38D8A_8718_11D0_B02C_0080C81A3AA2__INCLUDED_) #ifndef acioneaz ca n codul care urmeaz,i are ca efect includerea fiierului ce urmeaz o singur dat. #ifndef test_h #include "test.h" #define test_h #endif #pragma once este de asemenea pentru a preveni definiiile multiple dac acest fiier este inclus de dou ori. Clasa CFirstSDIApp derivat din CWinApp, furnizeaz cea mai mare funcionalitate a aplicaiei. Instana acestei clase constituie obiectul aplicaie. AppWizard a generat anumite funcii pentru aceast clas care reacoper funciile motenite din clasa de baz. Aceast seciune de cod ncepe cu //Overrides. De asemenea sunt generate annumite comentarii care ajut la nelegerea codului. n aceast seciune vom gsi declaraia pentru funcia InitInstance(). Urmtoarea seciune de cod este pentru harta de mesaje. AppWizard genereaz cod pentru constructorul CFirstSDIApp, i funciile InitInstance() i OnAppAbout() n fiierul firstsdi.cpp. Codul generat pentru constructor arat astfel: CFirstSDIApp::CFirstSDIApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } Ca regul general trebuie reinu c Microsoft construiete obiectele n doi pai. Se creaz obiectul unde se scrie cod care sigur se execut corect, iar iniializrile se fac cu ajutorul unei funcii membru, care poate indica dac acestea au fost efectuate cu succes sau nu. Constructorul nu ntoarce nici o valore, construiete obiectul. n continuare prezentm listingul pentru CFirstSDIApp::InitInstance() BOOL CFirstSDIApp::InitInstance() { AfxEnableControlContainer(); // Standard initialization // If you are not using these features and want to reduce the size 46

// of your final executable, you should remove from the following // the specific initialization routines you don't need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif // Change the registry key under which our settings are stored. // You should modify this string to be something appropriate, // such as the name of your company or organization. SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(); // Load standard INI file options (including // MRU) // Register the application's document templates. Document templates // serve as the connection between documents, frame windows, and views. CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CFirstSDIDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CFirstSDIView)); AddDocTemplate(pDocTemplate); // Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE;

Comentariile sunt incluse de AppWizard. InitInstance este apelat prima la lansarea apliaciei. AfxEnableControlContainer() permite aplicaiei de a conine controale ActiveX. Se permite afiarea controalelor cu aspect 3D. Se completeaz regitrii sub care va fi nregistrat aceast aplicaie. InitInstance() nregistreaz n continuare ablonul de document care este SDI n acest caz. InitInstance() creaz un obiect vid (neiniializat) CCommandLineInfo pentru a menine parametrii pasai aplicaiei prin linia de comand cnd aceasta a fost lansat n execuie i apoi apeleaz ParseCommandLine() pentru a completa acest obiect. n final se proceseaz linia de comand printr-un apel la ProcessShellCommand(). De exemplu dac lansm aplicaia din linia de comand astfel: FirstSDI curs, aplicaia va deschide fiierul curs. Parametrii din linia de comand pe care ProcessShellCommand() i suport sunt urmtorii: Parameter None Filename /p filename /pt filename printer driver port Action Start app and open new file. Start app and open file. Start app and print file to default printer. Start app and print file to the specified printer. 47

/dde /Automation /Embedding

Start app and await DDE command. Start app as an OLE automation server. Start app to edit an embedded OLE item.

Dac dorim s implementm o alt comportare, vom construi o clas derivat din CCommandLineInfo pentru a pstra linia de comand, i apoi rescriem funciile CWinApp:: ParseCommandLine() i CWinApp::ProcessShellCommand(). Se completeaz variabila m_pMainWnd cu adresa obiectului aplicaiei, variabil ce este defint n CWinThread, ce este clas de baz pentru CWinApp. La sfrit funcia ntoarce TRUE pentru a indica c restul aplicaiei poate rula. Harta de mesaje indic c funcia OnAppAbout() va trata un mesaj. Care este? Va trata comanda de meniu care are ID-ul ID_APP_ABOUT. Acest ID corespunde comenzii de meniu Help->About. Descrierea hrii de mesaje este: BEGIN_MESSAGE_MAP(CFirstSDIApp, CWinApp) //{{AFX_MSG_MAP(CFirstSDIApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // NOTE - The ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard file-based document commands ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP() OnAppAbout() arat aa: void CFirstSDIApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } Se construiete obiectul aboutDlg, care este o box de dialog, i apoi se execut acest dialog. Aplicaia Multiple Document Interface O asemenea aplicaie are meniu i permite utilizatorului de a deschide mai multe documente odat. AppWizard a generat cinci clase. Numele proiectului este FirstMDI. Clasele generate sunt: CAboutDlg, o clas de dialog pentru About CFirstMDIApp, o clas CWinApp pentru aplicaie CFirstMDIDoc, o clas document CFirstMDIView, o clas pentru vizualizare CMainFrame, o clas cadru Descrcati exemplul de pe pagina http://www.infoiasi.ro/~iasimin/IDD.

Componentele unei aplicaii bazate pe dialog


AppWizard genereaz trei clase: CAboutDlg, o clas dialog pentru About CFirstDialogApp, o clas CWinApp pentru ntreaga aplicaie CFirstDialogDlg, o clas de dialog pentru ntreaga aplicaie. Descrcati codul de pe aceeai pagin. 48

Controale clasice
Windows pune la dispoziie 6 (ase) controale clasice. Un control nu este altceva dect o fereastr cu stiluri speciale. Trebuie retinut faptul c a lucra cu un asemea control este ca i cum am lucra cu o fereastr. Tipurile controalelor, structurile WNDCLASS si clasele corespondente MFC sunt date in tabela urmatoare: Controale clasice Tip Control Butoane List box-uri Controale de editare Combo box-uri Scroll bar-uri Controale statice WNDCLASS "BUTTON" "LISTBOX" "EDIT" "COMBOBOX" "SCROLLBAR" "STATIC" Clasa MFC CButton CListBox CEdit CComboBox CScrollBar CStatic

Un control este creat prin instantierea clasei respective din MFC urmat apoi de apelul functiei Create din acea clasa. MFC creaza un obiect in doi pasi (am mai spus acest lucru pna acum?). Daca m_wndPushButton este un obiect CButton, instructiunea: m_wndPushButton.Create (_T ("Start"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, rect, this, IDC_BUTTON); creaza un control push button ce contine textul Start. Descriere parametrii pentru functia Create: primul parametru specific textul controlului. al doilea parametru este stilul ferestrei, ce reprezint o combinaie ntre stilurile ferestrei si stiluri specifice controlului. Controlul creat este o fereastra descendent (copil) al ferestrei identificata de al patrulea parametru (in SDK se furnizeaza un HWND la fereastra printe). al treilea parametru specific marimea si pozitia (in pixeli) controlului, data printr-un obiect CRect; pozitia este relativa la coltul din stanga sus al ferestrei parinte, ultimul parametru este ID-ul butonului (controlului - un intreg), folosit in diverse functii pentru a avea access la el; acest ID trebuie sa aiba o valoare unica in interiorul ferestrei date pentru a putea identifica corect controlul si functiile care trateaza mesajele de notificare. Unele controale (ListBox, Edit) pentru a se alinia la noile stiluri, au o noua functie membru CreateEx. Stilurile extinse se scriu numai in cadrul acestei functii (vezi CreateWindow si CreateWindowEx). Daca m_wndListBox este un obiect CListBox, urmatoarea instructiune creaza un control list box cu stilul extins WS_EX_CLIENTEDGE: m_wndListBox.CreateEx (WS_EX_CLIENTEDGE, _T ("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD, rect, this, IDC_LISTBOX); Ca o alternativa, putem deriva clasa noastra din CListBox, si apoi rescriem functia PreCreateWindow in clasa derivata, si aplicam stilul de fereastra WS_EX_CLIENTEDGE: BOOL CMyListBox::PreCreateWindow (CREATESTRUCT& cs) { if (!CListBox::PreCreateWindow (cs)) return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE; return TRUE;

49

Un control trimite notificari parintelui sub forma de mesaje WM_COMMAND. Tipurile de notificari depind de tipul controlului, dar in fiecare caz, informatia din parametrii mesajului, wParam si lParam, identifica controlul care trimite mesajul si actiunea ceruta de mesaj. De exemplu, cind un push button este apasat (clic), codul de notificare este BN_CLICKED care se regaseste in HIWORD(wParam) si ID-ul controlului este in LOWORD(wParam), iar handler-ul ferestrei controlului in lParam. Cadrul de lucru MFC, insereaza in harta de mesaje acest mesaj de notificare, ascunzand detaliile de implementare pentru tratarea mesajului WM_COMMAND (se face legatura intre ID-ul controlului si functia care trateaza mesajul de notificare): ON_BN_CLICKED (IDC_BUTTON, OnButtonClicked) ON_BN_CLICKED este un macrou, si asemenea macro-uri exista pentru fiecare mesaj de notificare de la fiecare control. Exista un macro generic ON_CONTROL, care manipuleaza toate notificarile si toate tipurile de controale, si ON_CONTROL_RANGE, care mapeaza notificari in mod identic de la doua sau mai multe controale la o functie comuna. Comunicarea intre controale si parinti (proprietarii controlului) este in ambele directii. De exemplu parintele poate trimite mesajul BM_SETCHECK unui control check box cu parametrul wParam setat pe BST_CHECKED. MFC simplifica interfata controlului bazata pe mesaje prin construirea de functii membru in clasa controlului care ascund in fapt mesajul BM_SETCHECK si alte mesaje ale controlului. De exemplu: m_wndCheckBox.SetCheck (BST_CHECKED); plaseaza un check mark in interiorul check box-ului reprezentat de un obiect CButton, numit m_wndCheckBox. Din cauza ca un control este o fereastra, anumite functii membru pe care controlul le mosteneste din CWnd sunt folositoare pentru controlul programarii. De exemplu aceeasi functie care modifica titlul unei ferestre, SetWindowText, modifica textul (eticheta) unui push button, sau schimba continutul unui control de editare (box edit). Alte functii: GetWindowText, EnableWindow, SetFont. Daca vrem sa facem ceva in control si nu gasim o functie corespunzatoare in clasa controlului va trebui sa cautam o asemenea functie in clasa CWnd din care sunt derivate toate controalele.

Clasa CButton CButton reprezinta controale de tip button bazate pe clasa WNDCLASS "BUTTON". Controalele button exista in patru variante: push buttons, check boxes, radio buttons, si group boxes. Cand cream un control button, vom specifica tipul acestuia prin includerea unuia din urmatoarele flag-uri in stilul ferestrei butonului: Style BS_PUSHBUTTON BS_DEFPUSHBUTTON BS_CHECKBOX BS_AUTOCHECKBOX BS_3STATE BS_RADIOBUTTON BS_AUTORADIOBUTTON Description Creaza un control standard de tip push button Creaza un buton implicit de tip push button; folosit in casetele de dialog pentru a identifica un push button care primeste mesajul BN_CLICKED cand s-a apasat tasta Enter Creaza un control de tip check box Creaza un buton de tip check box care se autoseteaza / reseteaza atunci cand este facut clic pe el (este de tip On/Off) Creaza un check box cu trei stari Creaza un control de tip radio button Creaza un control de tip radio button control care se autoseteaza / reseteaza atunci cand este facut clic pe el 50

Creaza un control de tip group box BS_GROUPBOX In plus, putem adauga urmatoarele valori (OR pe biti) la stilul ferestrei controlului privitoare la alinierea textului ce insoteste controlul: Style BS_LEFT BS_CENTER BS_RIGHT Description Aliniere text la stanga Centrare text Aliniere text la dreapta

Exista si alte tipuri de stiluri de butoane, dar care sunt folosite mai putin. De ex., BS_NOTIFY, programeaza un buton sa trimita notificarile BN_DOUBLECLICKED, BN_KILLFOCUS, si BN_SETFOCUS. BS_OWNERDRAW creaza un buton owner-draw (desenat de proprietar programatorul va scrie cod pentru acest lucru) infatisarea (aparenta) butonului este gestionata de parintele butonului. Butoane Push Un push button este un control de tip button creat cu stilul BS_PUSHBUTTON. Cand este apasat, controlul trimite parintelui notificarea BN_CLICKED printr-un mesaj WM_COMMAND. In absenta stilului BS_NOTIFY, un asemenea control nu trimite nici un alt tip de notificare. Macroul ON_BN_CLICKED din MFC leaga notificarile BN_CLICKED de functia membru din clasa fereastra parinte: ON_BN_CLICKED(IDC_BUTTON, OnButtonClicked) Functiile pentru BN_CLICKED nu au parametri si nu intorc valori. Check Boxes Check boxes sunt butoane create cu stilul BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, sau BS_AUTO3STATE. Stilurile BS_CHECKBOX si BS_AUTOCHECKBOX pot presupune doua stari: checked si unchecked. Un check box trece in starea checked sau unchecked cu CButton::SetCheck: m_wndCheckBox.SetCheck (BST_CHECKED); // Check m_wndCheckBox.SetCheck (BST_UNCHECKED); // Uncheck Pentru a determina daca un check box este in starea checked, folosim CButton::GetCheck. O valoare de retur egala cu BST_CHECKED inseamna ca chcek box-ul este in starea checked, iar BST_UNCHECKED este pt unchecked. Check boxes trimit notificarile BN_CLICKED parintilor lor cand facem clic in zona lor. Stilul BS_AUTOCHECKBOX face ca acest control sa lucreze ca un switch on/off automatizat in raspuns la evenimentul click mouse. Stilul BS_CHECKBOX nu face acelasi lucru. Un exemplu de cod pentru un check box cu stilul BS_CHECKBOX si ce trebuie sa facem la BN_CLICKED: void CMainWindow::OnCheckBoxClicked () { m_wndCheckBox.SetCheck (m_wndCheckBox.GetCheck () == BST_CHECKED ? BST_UNCHECKED : BST_CHECKED); } Stilurile BS_3STATE si BS_AUTO3STATE creaza un check box care presupune o a treia stare numita nedeterminata (indeterminate), si controlul intra in aceasta stare cind facem clic pe un asemenea buton iar starea lui curenta este checked sau cind apelam SetCheck cu parametrul BST_INDETERMINATE: m_wndCheckBox.SetCheck (BST_INDETERMINATE); Un check box in starea indeterminate contine a grayed check mark. 51

Butoane Radio
Un buton radio este un control de tip buton cu stilul BS_RADIOBUTTON sau BS_AUTORADIOBUTTON. In mod normal butoanele radio lucreaza in grup, si reprezinta o lista de optiuni mutual exclusive. Cand selectam un buton radio cu stilul BS_AUTORADIOBUTTON va ramine activ numai butonul selectat, celelalte butoane din grup devenind inactive in mod automat. Daca folosim stilul BS_RADIOBUTTON, va trebui sa scriem noi cod pentru a dezactiva celelalte butoane, folosind functia CButton::SetCheck. Butoanele radio trimit notificarile BN_CLICKED parintilor lor, la fel ca mai sus. Urmatorul cod trateaza BN_CLICKED: void CMainWindow::OnRadioButton1Clicked () { m_wndRadioButton1.SetCheck (BST_CHECKED); m_wndRadioButton2.SetCheck (BST_UNCHECKED); m_wndRadioButton3.SetCheck (BST_UNCHECKED); m_wndRadioButton4.SetCheck (BST_UNCHECKED); } Pentru butoanele radio ce au stilul BS_AUTORADIOBUTTON nu este necesara scrierea unei functii ce trateaza mesajul BN_CLICKED. Pentru butoane radio cu stilul BS_AUTORADIOBUTTON pentru a deselecta corect alte butoane din grup, trebuie sa grupam butoanele in momentul crearii, astfel incat Windows sa stie care butoane apartin grupului. Pentru a crea un grup de butoane radio cu stilul BS_AUTORADIOBUTTON urmam urmatoarea procedura (tehnica): 1. In codul aplicatiei, cream butoanele unul dupa altul fara a intercala intre acestea alte controale de alt tip. 2. Pentru a marca inceputul grupului, atribuim stilul WS_GROUP primului buton radio pe care il cream. 3. Daca cream controale aditionale dupa ultimul buton radio din grup, atribuim stilul WS_GROUP primului control aditional pe care il cream. Cream doua grupuri de cite 4 respectiv 3 butoane radio cu stilul BS_AUTORADIOBUTTON cu un control check box intre ele: m_wndRadioButton1.Create (_T ("COM1"), WS_CHILD WS_GROUP BS_AUTORADIOBUTTON, rect1, this, m_wndRadioButton2.Create (_T ("COM2"), WS_CHILD BS_AUTORADIOBUTTON, rect2, this, IDC_COM2); m_wndRadioButton3.Create (_T ("COM3"), WS_CHILD BS_AUTORADIOBUTTON, rect3, this, IDC_COM3); m_wndRadioButton4.Create (_T ("COM4"), WS_CHILD BS_AUTORADIOBUTTON, rect4, this, IDC_COM4); m_wndRadioButton1.SetCheck (BST_CHECKED); WS_VISIBLE IDC_COM1); WS_VISIBLE WS_VISIBLE WS_VISIBLE

m_wndCheckBox.Create (_T ("Save settings on exit"), WS_CHILD WS_VISIBLE WS_GROUP BS_AUTOCHECKBOX, rectCheckBox, this, IDC_SAVESETTINGS); m_wndRadioButton5.Create (_T ("9600"), WS_CHILD WS_VISIBLE WS_GROUP BS_AUTORADIOBUTTON, rect5, this, IDC_9600); m_wndRadioButton6.Create (_T ("14400"), WS_CHILD WS_VISIBLE BS_AUTORADIOBUTTON, rect6, this, IDC_14400); m_wndRadioButton7.Create (_T ("28800"), WS_CHILD WS_VISIBLE BS_AUTORADIOBUTTON, rect7, this, IDC_28800); m_wndRadioButton5.SetCheck (BST_CHECKED); Butoanele radio nu sunt niciodata checked implicit. Este responsabilitatea programatorului. 52

Group Boxes Un control group box este creat cu stilul BS_GROUPBOX. Acest control nu primeste si nici nu trimite mesaje. Singurul rol al lor este de a grupa anumite controale in interfata destinata utilizatorului. Clasa CStatic CStatic, reprezinta un control static creat din "STATIC" WNDCLASS. Exista trei tipuri de controale statice: text (folosit pentru a eticheta alte controale), dreptunghiuri si imagini. Exemplu m_wndStatic.Create (_T ("Name"), WS_CHILD WS_VISIBLE SS_LEFT, rect, this, IDC_STATIC); SS_LEFT = aliniaza text in stanga. Daca textul nu incape se continua pe linia urmatoare. Pentru a preveni trecerea textului pe linia urmatoare putem folosi stilul SS_LEFTNOWORDWRAP in locul stilului SS_LEFT. Alte stiluri: SS_CENTER (centrare text) sau SS_RIGHT (text aliniat la dreapta). Stilul SS_SIMPLE este asemanator cu SS_LEFT dar creaza un control al carui text poate fi modificat cu CWnd::SetWindowText. Pentru a centra vertical textul facem OR pe flagul SS_CENTERIMAGE. Urmatorul cod m_wndStatic.Create (_T (""), WS_CHILD WS_VISIBLE SS_ETCHEDFRAME, rect, this, IDC_STATIC); creaza un control static asemanator cu un group box. Un control static de tip dreptunghi nu afiseaza text. Controale statice pentru imagini formate din bitmap-uri, icoane, cursoare sau metafisiere GDI. Stilurile folosite in acest caz sunt: Style SS_BITMAP SS_ENHMETAFILE SS_ICON Description Afiseaza un bitmap Afiseaza un metafisier Afiseaza o icoana sau un cursor

Dupa ce se creaza un control static imagine, asociem bitmap, icoana sau cursor cu una din functiile SetBitmap, SetEnhMetaFile, SetIcon sau SetCursor. Urmatorul cod m_wndStatic.Create (_T (""), WS_CHILD WS_VISIBLE SS_ICON, rect, this, IDC_STATIC); m_wndStatic.SetIcon (hIcon); creaza un control static care afiseaza o icoana (atasam icoana cu ajutorul functiei SetIcon). Dreptughiul este marit automat pentru a cuprinde imaginea. Exista o serie de falg-uri care pot fi folosite pentru a controla modul de afisare al imaginii in control (SS_CENTERIMAGE = are urmatoarea cerinta majora: dreptunghiul de afisare trebuie sa fie destul de mare pentru a cuprinde imaginea). Implicit, un control static nu trimite mesaje de notificare. Daca se creaza un control static cu stilul SS_NOTIFY, atunci acesta trimite urmatoarele notificari: Notificare Trimis cand Message-Map Macro STN_CLICKED S-a facut clic pe control. ON_STN_CLICKED STN_DBLCLK S-a facut dublu clic pe control ON_STN_DBLCLK STN_DISABLE Controlul este in starea disabled ON_STN_DISABLE STN_ENABLE Controlul este enabled ON_STN_ENABLE 53

Notificarile STN_CLICKED si STN_DBLCLK permit crearea de controale statice ce raspund la clic-uri de mouse, ca in exemplul urmator: // In harta de mesaje din CMainWindow ON_STN_CLICKED (IDC_STATIC, OnClicked) // In CMainWindow::OnCreate m_wndStatic.Create (_T ("Click me"), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_CENTERIMAGE | SS_NOTIFY | SS_SUNKEN, rect, this, IDC_STATIC); void CMainWindow::OnClicked() m_wndStatic.PostMessage(WM_CLOSE, 0, 0);

Clasa CEdit (CE)


Clasa CEdit din MFC incapsuleaza functionalitatea unui control de editare folosit pentru a edita text, pe o singura linie sau pe mai multe linii. Zona client din Notepad este un control de editare multilinie. Crearea unui control Edit Daca m_wndEdit este un obiect CEdit codul de mai jos m_wndEdit.Create (WS_CHILD WS_VISIBLE WS_BORDER ES_AUTOHSCROLL, rect, this, IDC_EDIT); creaza un control single line care face scroll orizontal automat, daca textul nu incape in zona de afisare. Incluzind stilul ES_MULTILINE vom crea un CE multilinie: m_wndEdit.Create (WS_CHILD WS_VISIBLE WS_BORDER WS_HSCROLL WS_VSCROLL ES_MULTILINE, rect, this, IDC_EDIT); WS_HSCROLL si WS_VSCROLL adauga barele de scroll vertical si orizontal. Putem folosi CEdit::SetRect sau CEdit::SetRectNP pentru a defini zona editabila a controlului, independent de marginile controlului. O utilizare pentru aceste functii este de a defini marimea paginii care ramine constanta chiar daca controlul este redimensionat. Putem folosi de asemenea CEdit::SetMargins pentru a specifica latimea (in pixeli) marginii din stanga si dreapta. Implicit latimile marginilor sunt 0. Cand este prima data creat, un CE va accepta numai 30,000 caractere. Putem modifica acest lucru cu ajutorul metodelor CEdit::LimitText sau CEdit::SetLimitText. Urmatoarea instructiune seteaza numarul maxim de caractere la 32: m_wndEdit.SetLimitText (32); Cand folosim un CE multilinie, SetLimitText limiteaza cantitatea totala de text din control, deci nu lungimea fiecarei linii. In acest caz putem controla lungimea liniei numai manual. O metoda este de a folosi SetFont pentru a comuta fontul CE la un font fixed-pitch si CEdit::SetRect pentru a specifica dreptunghiul de formatare a carui latime este un pic mai mare decit latimea caracterelor din font inmultita cu numarul de caractere dorit a se afisa pe o linie. Stiluri pentru controlul de editare Style Description ES_LEFT Aliniere la stanga a textului din control ES_CENTER Centrarea textului in control ES_RIGHT Aliniere la dreapta a textului din control ES_AUTOHSCROLL Permite CE de a defila textul orizontal fara a atasa bara de navigare orizontala. Bara de navigare orizontala se adauga folosind stilul WS_HSCROOL. 54

ES_AUTOVSCROLL ES_MULTILINE ES_LOWERCASE ES_UPPERCASE ES_READONLY

Defilarea textului se face pe verticala fara a avea atasata bara de navigare. Bara de navigare se ataseaza folosind stilul WS_VSCROLL. Creaza un CE multilinie Afiseaza toate caracterele in lowercase Ca mai sus, dar uppercase Creaza un CE read only

Pentru alte stiluri recomandam a consulta MSDN.

Inserarea si Regasirea Textului


Textul se insereaza cu SetWindowText si se regaseste cu GetWindowText. CEdit mosteneste ambele functii din clasa de baza CWnd. Codul de mai jos m_wndEdit.SetWindowText (_T ("Hello, MFC")); insereaza textul "Hello, MFC" in controlul m_wndEdit, iar m_wndEdit.GetWindowText (string); regaseste textul intr-un obiect CString numit string. GetWindowText si SetWindowText lucreaza cu ambele tipuri de controale, single line si multiline. Textul inserat cu SetWindowText inlocuieste textul existent, iar GetWindowText returneaza tot textul din control, chiar daca acesta este pe mai multe linii. Pentru a sterge textul din control, apelam SetWindowText cu un sir nul: m_wndEdit.SetWindowText (_T ("")); Putem insera text fara a stege cel existent cu CEdit::ReplaceSel. Daca unul sau mai multe caractere sunt selectate cand apelam ReplaceSel, textul care se insereaza inlocuieste textul selectat, in caz contrar textul este inserat la pozitia curenta a cursorului (caret-ului). Un control multiline insereaza line break automat. Daca dorim sa determinam line break-urile dintr-un text folosim CEdit::FmtLines pentru a face enable soft line breaks inainte de apelul lui GetWindowText: m_wndEdit.FmtLines (TRUE); Cu soft line breaks enabled, fiecare linie este delimitata cu doua CR (0x13) urmat de un LF (0x10). Pentru a invalida soft line break folosim FmtLines( FALSE): m_wndEdit.FmtLines (FALSE); CR introdus in text la apasarea tastei <Enter> sunt semnificate de o pereche CR/LF. FmtLines nu afecteaza modul de afisare al textului intr-un CE multilinie, ci afecteaza numai modul cum este memorat intern textul si formateaza textul regasit cu GetWindowText. Pentru a citi exact o linie de text dintr-un control multilinie folosim CEdit::GetLine. GetLine copie continutul unei linii intr-un buffer pe care trebuie sa-l alocam si apoi furnizam functiei adresa acestuia. Linia este identificata de un index 0-based. Instructiunea: m_wndEdit.GetLine (0, pBuffer, nBufferSize);

55

copie prima linie din control in zona data de pBuffer, iar parametrul al 3 lea indica dimensiunea bufferului in bytes. GetLine returneaza numarul de octeti copiati in buffer. Putem determina dinaninte marimea necesara a bufferului cu ajutorul functiei CEdit::LineLength, iar numarul de linii din control il determinam cu ajutorul functiei CEdit::GetLineCount. GetLineCount nu returneaza niciodata 0, chiar daca nu exista text, valoarea returnata este 1. Clear, Cut, Copy, Paste, and Undo CEdit furnizeaza functii pentru operatiile enumerate mai sus. m_wndEdit.Clear (); sterge textul selectat fara a afecta continutul clipboard-ului. m_wndEdit.Cut (); sterge textul selectat si il copie in clipboard. m_wndEdit.Copy (); copie textul selectat in clipboard fara a-l sterge. Putem interoga CE pentru selectia curenta cu un apel al functiei CEdit::GetSel, care returneza o valoare DWORD ce contine doi intregi pe 16 biti ce specifica indexul de inceput si indexul de sfarsit al selectiei. Daca indecsii sunt egali nu exista text selectat. Exista o forma a functiei GetSel care copie indecsii in doi intregi ale caror adrese sunt pasate ca parametrii prin referinta. Putem adauga urmatoare functie IsTextSelected, la clasa controlului de editare derivat din CEdit pentru a determina daca exista sau nu text selectat in control: BOOL CMyEdit::IsTextSelected () { int nStart, nEnd; GetSel (nStart, nEnd); return (nStart != nEnd); } CEdit::Cut and CEdit::Copy nu fac nimic daca nu este text selectat. Textul poate fi selectat prin program cu CEdit::SetSel. Instructiunea: m_wndEdit.SetSel (100, 150); selecteaza 50 de caractere incepind cu al 101-lea caracter si o face vizibila in view daca aceasta nu este vizibila (se face scroll automat). Pentru a preveni defilarea (scrolling), vom folosi si al 3-lea parametru al functiei cu valoarea TRUE. Cind facem selectii prin program intr-un control multilinie, este necesar adesea sa convertim un numar de linie si posibil un offset din interiorul acestei linii intr-un index pe care-l vom folosi in SetSel. Functia CEdit::LineIndex accepta un numar de linie 0-based si returneaza indexul primului caracter din acea linie. In exemplul ce urmeaza se determina index-ul primului caracter din linia 8 (LineIndex), apoi determinam lungimea liniei si selectam tot textul care se gaseste in acea linie (SetSel): int nStart = m_wndEdit.LineIndex (7); int nLength = m_wndEdit.LineLength (nStart); m_wndEdit.SetSel (nStart, nStart + nLength); CEdit furnizeaza functia LineFromChar pentru a calcula numarul liniei plecand de la index-ul unui caracter. 56

CEdit::Paste pastes text intr-un CE. m_wndEdit.Paste (); Daca clipboard-ul nu contine text, CEdit::Paste nu are efect. Daca exista text selectat cand se apeleaza Paste atunci se insereaza textul din clipboard la pozitia curenta a caret-ului. Daca exista o selectie, atunci textul din clipboard inlocuieste selectia existenta. Putem determina din timp daca exista text in clipboard printr-un apel al functiei. ::IsClipboardFormatAvailable. Codul de mai jos BOOL bCanPaste = ::IsClipboardFormatAvailable (CF_TEXT); seteaza bCanPaste la o valoare diferita de 0 daca exista text in clipboard sau 0 in caz contrar. O alta trasatura a unui CE este posibilitatea rollback-ului (undo) , reface ultima stergere: m_wndEdit.Undo (); Se poate determina din timp daca am putea apela Undo prin apelul functiei CEdit::CanUndo. O alta functie., CEdit::EmptyUndoBuffer, reseteaza manual flag-ul pentru undo, a.i., urmatoarele apeluri la Undo nu vor face nimic. Notificarile Controlului de Editare In aplicatiile cu MFC, notificarile sunt mapate cu macro-uri de forma ON_EN in harta de mesaje a clasei. In exemplul urmator se trateaza notificarea (mesaj) EN_CHANGE a unui CE. Un control de tip push buton (m_wndPushButton) este facut enable/disable dupa cum exista/nu exista text in CE ce are ID egal cu IDC_EDIT si dat de obiectul (m_wndEdit) : // In harta de mesaje din CMainWindow ON_EN_CHANGE(IDC_EDIT, OnUpdatePushButton) void CMainWindow::OnUpdatePushButton () { m_wndPushButton.EnableWindow (m_wndEdit.LineLength ()); } Notificari ale controlului de editare Notification Sent When EN_UPDATE Textul din control este pe cale sa se schimbe EN_CHANGE Textul din control a fost schimbat EN_KILLFOCUS Controlul de editare a pierdut focusul de intrare EN_SETFOCUS Controlul de editare a primit focusul de intrare EN_HSCROLL The edit control is scrolled horizontally using a scroll bar. Pentru alte notificari ale CE consultati MSDN. Message-Map Macro ON_EN_UPDATE ON_EN_CHANGE ON_EN_KILLFOCUS ON_EN_SETFOCUS ON_EN_HSCROLL

Clasa CListBox
Clasa CListBox din MFC incapsuleaza controalele list box, care afiseaza o lista de stringuri numite articole. Optional, un list box poate sorta articolele pe care le contine. Cand pe un item (articol) facem clic sau dublu clic , list box-urile (care au stilul LBS_NOTIFY) notifica parintilor lor printr-un mesaj WM_COMMAND. MFC simplifica 57

procesarea acestor mesaje furnizind macro-ul ON_LBN in harta de mesaje, care ruteaza notificarile list box-ului la functii din clasa fereastra parinte.. Un list box standard afiseaza stringuri intr-o coloana verticala si permite ca un singur articol sa fie selectat la un moment dat. Articolul curent selectat este afisat in video invers cu culoarea sistem COLOR_HIGHLIGHT. Windows suporta un numar de variatii de la standardul initial, variatii ce permit selectii multiple, afisarea pe mai multe coloane, list box-uri desenate de proprietar, afisare de imagini in locul textului. Crearea unui List Box Urmatoarea instructiune creaza un list box din obiectul CListBox numit m_wndListBox: m_wndListBox.Create(WS_CHILD IDC_LISTBOX); | WS_VISIBLE | LBS_STANDARD, rect, this,

LBS_STANDARD combina stilurile WS_BORDER, WS_VSCROLL, LBS_NOTIFY, si LBS_SORT pentru a crea un list box care are margini, o bara de scroll verticala, care notifica parintilor sai cind selectia s-a schimbat sau s-a facut dublu clic pe un articol, si ca articolele vor fi sortate in ordine alfabetica. Implicit bara de scroll este vizibila numai cind articolele nu pot fi afisate in intregime in fereastra controlului. Pentru a face ca bara de scroll sa fie afisata tot timpul va trebui sa includem stilul LBS_DISABLENOSCROLL. Putem crea list box-uri care cuprind toata zona client. List box-urile au incapsulata interfata cu tastatura (tastele sageti, page up, down, apasarea unui caracter muta selectia pe articolul care incepe cu acel caracter). Apasarea barei de spatiu face sa avem selectie multipla sau nu (on/off). Putem programa interfata cu tastatura prin includerea stilului LBS_WANTKEYBOARDINPUT si procesarea mesajelor WM_VKEYTOITEM si WM_CHARTOITEM. O aplicatie MFC poate mapa aceste mesaje cu functiile OnVKeyToItem si OnCharToItem folosind macro-urile ON_WM_VKEYTOITEM si ON_WM_CHARTOITEM. O clasa list box derivata poate manipula aceste mesaje singura prin suprascrierea functiilor virtuale CListBox::VKeyToItem si CListBox::CharToItem. Description Creaza un listbox standard ce are margini si bara de scroll vertical, notifica ferestrei parinte cand s-a schimbat selectia sau cand s-a facut dublu clic pe un articol si sorteza articolele. LBS_SORT Sorteaza articolele ce sunt adaugate la list box. LBS_NOTIFY Creaza un list box ce notifica ferestrei parinte cand s-a schimbat selectia sau s-a facut dublu clic pe un articol. LBS_HASSTRINGS Stil implicit. List box-ul pastreaza string-urile adaugate. Pentru ca fontul implicit pe care Windows il foloseste pentru list box-uri este proportional spatiat, virtual este imposibil de a alinia coloanele prin spatii. O modalitate de a crea liste ce contin informatii pe mai multe coloane este sa folosim SetFont pentru a aplica un font fixed-pitch la un list box. O solutie mai buna este de a asigna list boxurilor stilul LBS_USETABSTOPS si de a separa coloanele de informatii cu tab. Un list box cu stilul LBS_USETABSTOPS trateaza caracterele tab ca un procesor de texte. Implicit tab este de marimea a 8 caractere pentru latime. Putem schimba acest lucru cu functia CListBox::SetTabStops. SetTabStops masoara distanta in unitati de dialog = o patrime din latimea unui caracter in fontul sistem si 1/8 din cel mai inalt caracter. Instructiunea: m_wndListBox.SetTabStops (64); pune spatiul dintre tab-uri la 64 unitati de dialog , si int nTabStops[] = { 32, 48, 64, 128 }; m_wndListBox.SetTabStops (4, nTabStops); 58 Stiluri pentru list box Style LBS_STANDARD

plaseaza stop tab-uri la 32, 48, 64, si 128 unitati de dialog fata de marginea din stanga. Implicit un list box se redeseneaza singur cind este adaugat/sters un articol. Pentru a impiedica acest lucru putem seta stilul LBS_NOREDRAW. O asemenea lista va fi redesenata cand zona ei client va fi invalidata. O alta alternativa este de a inhiba procesul de actualizare cu LBS_NOREDRAW si a-l reactiva dupa ce ultimul articol din list box a fost adaugat. Putem face redesenarea enable/disable prin trimiterea mesajului si nu mai este necesar Invalidate() // disable redesenarea m_wndListBox.SenMessage(WM_SETREDRAW, FALSE, 0); // permite desenarea m_wndListBox.SendMessage(WM_SETREDRAW, TRUE, 0); Stilul LBS_MULTIPLESEL este folosit pentru selectii multiple. Cele mai multe list box-uri sunt create cu stilul LBS_EXTENDEDSEL, care permite selectii extinse. Cu un asemenea stil se fac selectii cu ajutorul mouse-ului si a tastei Ctrl (pe sarite) sau Shift (selectie contigua) (se poate combina Ctrl si Shift). Stilul LBS_MULTICOLUMN creaza un list box cu mai multe coloane (implicit 16 caractere per articol), care in mod normal au si stilul WS_HSCROLL pentru defilare orizontala. List Box-urile multicoloana nu pot avea bara verticala pentru scroll. Latimea coloanei se ajusteaza cu functia CListBox::SetColumnWidth. Adaugarea si Stergerea articolelor Articolele sunt adaugate cu functiile CListBox::AddString si CListBox::InsertString. Exemplu m_wndListBox.AddString (string); adauga un obiect CString la list box. Daca stilul include LBS_SORT, atunci articolul e pozitionat corespunzator ordinii de sortare alfabetice, altfel este adaugat la sfirsitul listei. InsertString adauga articolul la o pozitie indicata de primul parametru al functiei (zero-based index): m_wndListBox.InsertString (3, string); LBS_SORT nu are efect asupra stringurilor adaugate cu InsertString. Ambele functii AddString si InsertString intorc pozitia stringului din list box. In caz de esec se returneaza LB_ERRSPACE pentru a indica ca un list box este plin sau LB_ERR pentru a indica ca s-a intimplat altceva din diverse motive. Capacitatea unui list box este limitata numai de memoria disponibila. Functia CListBox::GetCount returneaza numarul art. dintr-un list box. Fct. CListBox::DeleteString elimina un articol dintr-un list box, articol identificat prin indexul sau. Intoarce numarul articolelor ramase in list box. Pentru a sterge toate articolele folosim functia CListBox::ResetContent. Daca dorim sa asociem un pointer pe 32 biti sau o valoare DWORD cu un articol din list box putem folosi functia CListBox::SetItemDataPtr sau CListBox::SetItemData. Un pointer sau un DWORD asociat cu un articol poate fi regasit cu ajutorul functiei CListBox::GetItemDataPtr sau CListBox::GetItemData. O folosire a acestei trasaturi este de exemplu de a asocia o structura de date ce contine nr. de telefon pentru persoanele dintr-un list box. Din cauza ca GetItemDataPtr intoarce un pointer la void trebuie facuta conversia. O alta tehnica este de a asocia extra date in particular text cu articolele dintr-un list box , sa cream un list box cu stilul LBS_USETABSTOPS, sa setam primul tab stop la o pozitie din afara marginii drepte a list box-ului si din a adauga stringuri ce contin caractere tab urmate de extra data (text). Textul de la dreapta tab-ului va fi invizibil, dar CListBox::GetText va returna intregul text, deci si cel extra. Cautarea si regasirea articolelor CListBox::GetCurSel intoarce indexul (0-based) al articolului care este selectat. Daca valoarea returnata este LB_ERR inseamna ca nu s-a selectat nimic. GetCurSel este adesea apelata ca urmare a unei notificari ce semnifica ca selectia s-a schimbat sau a fost facut dublu clic pe un articol. Un program poate seta selectia curenta cu SetCurSel. Pasind valoarea 1 pt. SetCurSel vom deselecta toate articolele. 59

Pentru a gasi daca un articol particular este selectat putem folosi functia CListBox::GetSel. SetCurSel identifica un articol prin indexul sau, dar articolele pot fi selectate si dupa continut cu functia CListBox::SelectString care realizeaza o singura selectie pentru un articol ce incepe cu textul specificat si selecteaza articolul daca se gaseste unul care satisface conditia. Codul m_wndListBox.SelectString (-1, _T ("Times")); incepe cautarea cu primul articol din list box si va selecta primul articol care incepe cu Times. Cautarea nu este case senzitive. Primul parametru indica indexul de unde incepe cautarea; -1 inseamna de la inceput. Indiferent de unde incepe cautarea aceasta poate sa parcurga circular intreaga lista asociata list box-ului daca este necesar. Pentru a cauta pentru un articol particular fara a schimba selectia vom folosi CListBox::FindString sau CListBox::FindStringExact. FindString face cautare numai pe primele caractere din articol. Daca se intoarce LB_ERR inseamna ca nu s-a gasit acel articol, altfel se intoarce indexul articolului. FindStringExact adauga in plus cautarea exacta. Cu indexul obtinut anterior putem aobtine textul articolului cu CListBox::GetText. In exemplul urmator, se copie textul articolului in variabila string. CString string; int nIndex = m_wndListBox.GetCurSel (); if (nIndex != LB_ERR) m_wndListBox.GetText (nIndex, string); Al doilea parametru este un pointer la char. Putem folosi CListBox::GetTextLen pentru a determina marimea zonei necesare pentru a primi textul articolului inainte de a apela GetText. Selectiile multiple sunt tratate diferit. GetCurSel, SetCurSel, si SelectString nu pot fi folosite in acest caz. Articolele sunt selectate (deselectate) cu functiile SetSel si SelItemRange. In continuare se selecteaza articolele cu indecsii 0, 5, 6, 7, 8, si 9 si deselecteaza articolul cu indexul 3: m_wndListBox.SetSel (0); m_wndListBox.SelItemRange (TRUE, 5, 9); m_wndListBox.SetSel (3, FALSE); Alte functii: GetSelCount pt. determinarea numarului de articole selectate, GetSelItems pentru regasirea indecsilor articolelor selectate. Intr-un list box cu selectie multipla, dreptunghiul ce reprezinta articolul cu focus-ul asupra lui, poate fi mutat fara a schimba selectia curenta. Dreptunghiul care are focusul poate fi mutat sau obtinut cu ajutorul functiei SetCaretIndex si GetCaretIndex. Multe din functiile ce lucreaza cu o singura selectie sunt disponibile si pentru list box-urile cu selectie multipla: GetText, GetTextLength, FindString, si FindStringExact. Notificarile List Box Notificarile sunt trimise via mesajul WM_COMMAND. In aplicatiile MFC, notificarile list box-urilor sunt mapate la functiile din clasa cu macro-ul ON_LBN.Vezi tabelul de mai jos. Notificrile LBN_DBLCLK, LBN_SELCHANGE, si LBN_SELCANCEL sunt trimise numai daca list box-ul a fost creat cu stilul LBS_NOTIFY sau LBS_STANDARD. List Box Notifications Notification LBN_SETFOCUS LBN_KILLFOCUS LBN_DBLCLK LBN_SELCHANGE Sent When List box-ul obtine focusul de intrare List box-ul pierde focusul de intrare S-a facut dublu clic pe un articol S-a schimbat selectia Message-Map Macro ON_LBN_SETFOCUS ON_LBN_KILLFOCUS ON_LBN_DBLCLK ON_LBN_SELCHANGE LBS_NOTIFY Required? No No Yes Yes 60

LBN_SELCANCEL S-a anulat selectia ON_LBN_SELCANCEL Yes Notificarile cele mai folosite sunt: LBN_DBLCLK si LBN_SELCHANGE. Pentru a determina indexul articolului pe care s-a facut dublu clic intr-un list box cu o singura selectie folosim CListBox::GetCurSel. Urmariti exemplul urmator: // In harta de mesje din CMainWindow ON_LBN_DBLCLK(IDC_LISTBOX, OnItemDoubleClicked) void CMainWindow::OnItemDoubleClicked () { CString string; int nIndex = m_wndListBox.GetCurSel (); m_wndListBox.GetText (nIndex, string); MessageBox (string); } Pentru un LB cu selectie multipla folosim GetCaretIndex in locul functiei GetCurSel pentru a determina articolul pe care s-a facut dublu clic. Notificarea LBN_SELCHANGE este trimisa cind utilizatorul schimba selectia, dar nu si in cazul cind selectia este schimbata automat prin program. Un LB cu selectie simpla trimite notificarea LBN_SELCHANGE cind selectia se muta din cauza unui clic sau a apasarii unei taste. Intr-un LB cu selectie multipla notificarea LBN_SELCHANGE este trimisa cind se face clic pe un articol, cind starea selectiei articolului este modificata (on/off) si cind dreptunghiul care are focus-ul este mutat.

Clasa CComboBox (control combo box)


Un CB este format dintr-un control de editare si un list box. Tipuri de combo box-uri: simple, drop-down, si drop-down list. CB simple (stil CBS_SIMPLE) sunt cele mai putin folosite. Trasatura principala a acestora este ca sunt permanent afisate. Cand un utilizator selecteaza un articol din lista, acest articol este automat copiat in CE. Utilizatorul poate tipari text direct in CE. Daca textul se potriveste cu un articol din lista, articolul este automat setat pe video invers si se executa scroll-ul . Un CB drop-down (stil CBS_DROPDOWN) difera de un CB simplu prin aceea ca lista este afisata numai la cererea utilizatorului si nu permite introducerea de text in CE asociat. Un CB drop-down list (stil CBS_DROPDOWNLIST) are in plus fata de CB drop-down bara de navigare verticala. Stilurile se dau in functia Create or CreateEx . Alte stiluri exista pentru cosmetizarea CB. Cind cream un CB trebuie sa punem stilul WS_VSCROLL daca dorim scroll vertical . Daca m_wndComboBox este un obiect CComboBox, instructiunea: m_wndComboBox.Create (WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_SORT, rect, this, IDC_COMBOBOX); ceaza un CB drop-down list care contine bara pentru scroll vertical. Dimensiunea controlului (dreptunghiul) trebuie sa fie destul de mare pentru a afisa tot textul. Stiluri combo box Stil CBS_DROPDOWN CBS_DROPDOWNLIST CBS_HASSTRINGS CBS_LOWERCASE Descriere Creaza un combo box drop-down. Creaza un com box drop down list. Vezi list box. Textul din combo box va fi lower case (vezi controlul de 61

editare). CBS_SIMPLE Creaza un combo box simplu. CBS_SORT Vezi LBS_SORT. CBS_UPPERCASE Vezi Controlul de editare. Adaugare articolelor se face cu CComboBox::AddString si CComboBox::InsertString. Numarul maxim de caractere pentru CE al CB este setat cu CComboBox::LimitText. Functiile GetWindowText si SetWindowText lucreaza pentru CE al CB. Functii specifice: GetLBText, care regaseste textul unui articol identificat printr-un index 0-based. GetLBTextLen, returneaza lungimea unui articol, in caractere; ShowDropDown, afiseaza sau ascunde un CB drop-down list; GetDroppedState, returneaza o valoare ce indica daca CB drop-down list este afisat. Notificari Combo Box Notificarea Message-Macro Map Sim ple DropDown Drop-Down List

CBN_DROPDOWN ON_CBN_DROPDOWN Trimis cand este afisat (CB drop-down list). CBN_CLOSEUP ON_CBN_CLOSEUP Trimis cand CB drop-down list este nchis. CBN_DBLCLK ON_CBN_DBLCLK CBN_SELCHANGE Trimis cand s-a schimbat selectia. CBN_SELENDOK Trimis cand s-a facut o selectie. Pentru alte notificari consultati MSDN. ON_CBN_SELCHANGE ON_CBN_SELENDOK

Nu toate notificarile se aplica la toate tipurile de CB. Notificarile CBN_DROPDOWN si CBN_CLOSEUP nu sunt trimise la un CB simplu (CBS_SIMPLE) pentru ca un asemenea CB este deschis tot timpul. CB cu stilurile CBS_DROPDOWN si CBS_DROPDOWNLIST-nu primesc notificarea CBN_DBLCLK pentru ca pe articolele din lista nu se poate face dublu clic. (LB asociat CB se inchide dupa primul clic). Notificarile CBN_EDITUPDATE si CBN_EDITCHANGE sunt echivalente cu EN_UPDATE si EN_CHANGE trime de CE, si CBN_SELCHANGE este la fel cu LBN_SELCHANGE pentru LB. Cind procesam notificarea CBN_SELCHANGE, CE asociat poate sa nu fie actualizat cu selectia din LB asociat. Va trebui sa folosim GetLBText pentru a regasi noul text selectat in loc de GetWindowText. Indexul articolului selectat il gasim cu CComboBox::GetCurSel. Exemplu de tratare a mesajului WM_DRAWITEM ntr-un list box i construirea unui control list box cu atribut de culoare pentru text (item din listbox) l gsiti pe pagina mentionat anterior.

62

GDI - Graphical Device Interface


Biblioteca GDI32.DLL Sumar Pentru a desena n Windows, avem nevoie s tim cu ce scriem (penia, grosime, culoare, stil), cu ce desenm fundalul (pensula, culoare, stil), ce foncturi folosim, de unde ncepem s desenm,etc. Toate aceste elemente i nc multe altele sunt descrise ntr-o structur ce formeaz aa numitul device context sau n romn, context de dispozitiv , prescurtat DC. nainte de a desena, trebuie s crem un asemenea DC, s-l utilizm n cadrul desenrii i apoi s-l distrugem. S vedem care sunt problemele legate de desenare i bineneles elementele constitutive ale unui context de dispozitiv. Vom ncerca s ne familiarizm cu modul de scriere n Windows. Trebuie reinut de la nceput c orice afiare (indiferent de dispozitiv) este o desenare. n MFC contextul de dispozitiv este descris n clasa CDC i clasele derivate din aceasta. Pentru o mai bun documentare vedei MSDN i cartea lui J. Prossie Programming with MFC, Second edition. GDI = permite manipularea elementelor grafice independente de dispozitiv, este un sistem de afisare static, ce permite numai animatii simple. Dipozitive grafice de iesire: 4. dispozitive rastru = reprezentare imagine prin matrice de puncte (placi video, imprimante matriciale, laser); 5. dispozitive vectoriale = deseneaza imaginile prin linii = plottere; Din punctul de vedere al programatorului, interfaa GDI este format din cteva sute de rutine si unele tipuri de date, macroinstructiuni si structuri de date. Tipuri de apeluri de functii Functii care obtin (sau creaza) si elibereaza (sau distrug) un context de dispozitiv; (in API: BeginPaint ... EndPaint, GetDc, ReleaseDC; in MFC (clase): CDC, CPaintDC, CClientDC, CMetaFileDC, CWindowDC) Functii care obtin informatii despre contextul de dispozitiv - structura TEXTMETRICS, GetTextMetrics; Functii care deseneaza ceva (TextOut, DrawText, desenarea liniilor, a zonelor colorate si a imaginilor bitmap...); Functii care stabiliesc sau obtin atribute ale contextului de dispozitiv - Un atribut al DC specifica modul de lucru al functiilor de desenare; SetTextColor, etc. Toate atributele DC au valori prestabilite care devin active la obtinerea DC. Pentru fiecare functie de tip Set exista si o functie de tip Get, folosita pentru obinerea valorilor curente ale atributelor DC. Functii care lucreaz cu obiecte GDI. Primitive GDI Linii si curbe: linii drepte, dreptunghiuri, elipse, arce. Liniile sint desenate folosind penita (HPEN, clasa CPen) curenta selectata in DC Suprafete pline: Suprafata poate fi umpluta folosind pensula GDI curenta (HBRUSH, clasa CBrush) Imagini bitmap: matrici dreptunghiulare de biti, care corespund pixelilor unui dispozitiv de afisare - pt. sisteme grafice de tip rastru. Imagini bitmap - dependente de dispozitiv si imagini bitmap independente de dispozitiv (DIB = Device Independent Bitmap) care pot fi stocate in fisiere. Text: afisarea textului este legata de fonturi (HFONT, clasa CFont). Alte aspecte ale GDI: Moduri de mapare si transformri: sistem de coordonate n pixeli, inci, mm. Metafisiere (metafiles): o colectie de comenzi GDI stocate intr-o forma binara; sint folosite pentru transferarea reprezentarilor unor elemente grafice vectoriale prin intermediul memoriei temporare (clipboard). 63

Cai (paths): colectie de linii drepte si curbe stocate intern de GDI; pot fi folosite pt. desenare, umplere sau decupare. Decupare (clipping): desenarea poate fi limitata la o anumita sectiune a zonei client, numita zona de decupare - definita in general de o cale sau de o regiune. Tiparire (printing):

Contextul de dispozitiv (DC): Modalitati de obtinere a variabilei handle a DC: Varainata 1 (putem desena numai in regiunea invalida a ferestrei): la tratarea mesajului WM_PAINT: PAINTSTRUCT ps; hdc = BeginPaint (hwnd, &ps); ... EndPaint(hwnd, &ps); structura PAINTSTRUCT contine o structura de tip RECT rcPaint; care defineste dreptunghiul ce cuprinde regiunea invalida a zonei client a ferestrei; se valideaza regiunea invalida. Varianta 2 (putem desena in toata zona client a ferestrei; nu se valideaza regiunea invalida ale zonei client): hdc = GetDC(hwnd); ... ReleaseDC(hwnd, hdc); Varianta 3: (DC cuprinde in plus bara de titlu a ferestrei, barele de derulare si chenarul) hdc = GetWindowDC(hwnd); ... ReleaseDC(hwnd, hdc); Pentru folosirea acestei functii trebuie interceptat mesajul WM_NCPAINT ( non client paint). Varianta 4: CreateDC hdc = CreateDC(pszDriver, pszDevice, pszOutput, pData); ... DeleteDC(hdc); Pentru a obtine o variabila handle a DC pentru spatiul de afisare: hdc = CreateDC(DISPLAY, NULL, NULL, NULL); Varianta 5: Obtinere informatii despre DC - CreateIC care are aceeasi parametri ca si CreateDC hdc = CreateIC(pszDriver, pszDevice, pszOutput, pData); ... DeleteDC(hdc); Varianta 6: context de dispozitiv in memorie - necesar la lucrul cu imagini bitmap: hdcmem = CreateCompatibleDC (hdc) ... DeleteDC(hdcMem); Crearea unui metafisier: 64

hdcMeta = CreateMetaFile(pszFileName); ... hmf = CloseMetaFile(hdcMeta); Atributele Contextului de Dispozitiv Cele mai uzuale atribute ale DC sunt date in urmatorul tabel: Attribute Text color Background color Background mode Mapping mode Drawing mode Default Black White OPAQUE MM_TEXT R2_COPYPEN Set with CDC::SetTextColor CDC::SetBkColor CDC::SetBkMode CDC::SetMapMode CDC::SetROP2 Get with CDC::GetTextColor CDC::GetBkColor CDC::GetBkMode CDC::GetMapMode

CDC::GetROP2 Current position (0,0) CDC::MoveTo CDC::GetCurrentPosition Current pen BLACK_PEN CDC::SelectObject CDC::SelectObject Current brush WHITE_BRUSH CDC::SelectObject CDC::SelectObject Current font SYSTEM_FONT CDC::SelectObject CDC::SelectObject GDI Moduri de desenare functia SetROP2 Mod de desenare Operatii executate R2_NOP dest = dest R2_NOT dest = NOT dest R2_BLACK dest = BLACK R2_WHITE dest = WHITE R2_COPYPEN dest = src R2_NOTCOPYPEN dest = NOT src R2_MERGEPENNOT dest = (NOT dest) OR src R2_MASKPENNOT dest = (NOT dest) AND src R2_MERGENOTPEN dest = (NOT src) OR dest R2_MASKNOTPEN dest = (NOT src) AND dest R2_MERGEPEN dest = dest OR src R2_NOTMERGEPEN dest = NOT (dest OR src) R2_MASKPEN dest = dest AND src R2_NOTMASKPEN dest = NOT (dest AND src) R2_XORPEN dest = src XOR dest R2_NOTXORPEN dest = NOT (src XOR dest) Moduri de mapare functia SetMapMode Mod de mapare Distanta ce corespunde la o unitate logica MM_TEXT Pixel MM_LOMETRIC 0,1 mm MM_HIMETRIC 0,01 mm MM_LOENGLISH 0.01 inci MM_HIENGLISH 0.001 inci MM_TWIPS 1/1440 inci MM_ISOTROPIC arbitrar (x = y) MM_ANISOTROPIC Arbitrar (x!=y) Vizorul si fereastra SetWindowExt seteaz extensia ferestrei marimea dorita a ferestrei in unitati logice. SetViewportExt seteaza extensia vizorului marimea in pixeli a ferestrei in care desenam.. 65 Axa x spre dreapta spre dreapta spre dreapta spre dreapta spre dreapta spre dreapta Selectabil Selectabil Axa y in jos in sus in sus in sus in sus in sus selectabil Selectabil

Marimea fereastrei este masurata in unitati logice. Marimea viewport-ului este masurata in unitati de dispozitiv, sau pixeli. In modul MM_ISOTROPIC ordinea de apel este SetWindowExt si apoi SetViewportExt. Exemple de cod CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ANISOTROPIC); dc.SetWindowExt (500, 500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, 500); Originea este in coltul din stanga sus. CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ANISOTROPIC); dc.SetWindowExt (500, -500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, -500); Modul MM_ISOTROPIC CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ISOTROPIC); dc.SetWindowExt (500, 500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, 500); Pentru vizor se folosesc coordonatele de dispozitiv (pixeli). Pentru toate modurile de mapare, Windows transforma coordonatele ferestrei (coordonate logice) in coordonate ale vizorului (coordonate de dispozitiv) folosind doua formule: xViewport = (xWindow - xWinOrg) * (xViewExt / xWinExt) + xViewOrg yViewport = (yWindow - yWinOrg) * (yViewExt / yWinExt) + yViewOrg unde (xWindow, yWindow) este punct in coordonate logice care trebuie translatat; (xViewport, yViewport) este punct in coordonate de dispozitiv; (xWinOrg, yWinOrg) = originea ferestrei in coordonate logice; (xViewOrg, yViewOrg) = originea vizorului in coordonate dispozitiv. Formulele de mai sus implica faptul ca punctul (xWinOrg, yWinOrg) este intotdeauna mapat la punctul (xViewOrg, yViewOrg). (xWinExt, yWinExt) = extensia ferestrei in coordonate logice; (xViewExt, yViewExt) = extensia vizorului in coordonate de dispozitiv; In majoritatea modurilor de mapare aceste extensii sint prestabilite si nu pot fi modificate. Raportul intre extensia vizorului si extensia ferestrei reprezinta un factor de scalare folosit pentru convertirea unitatilor logice in unitati de dispozitiv. Extensiile pot avea valori negative: aceasta inseamna ca nu este obligatoriu ca valorile pe axa x sa creasca spre dreapta si valorile pe axa y sa creasca in jos. 66

Functiile folosite pentru a realiza conversia intre puncte reprezentate in coordonate de dispozitiv in puncte reprezentate in coordonate logice si invers sunt: DPtoLP(hdc, pPoints, iNumber); LPtoDP(hdc, pPoints, iNumber); pPoints = matrice de structuri POINT iNumber = numarul de puncte care urmeaza a fi convertite. Daca dorim sa stim unde este punctul din centru in unitati MM_LOENGLISH, trebuie sa folosim DPtoLP : CRect rect; GetClientRect (&rect); CPoint point (rect.Width () / 2, rect.Height () / 2); CClientDC dc (this); dc.SetMapMode (MM_LOENGLISH); dc.DPtoLP (&point); DPtoLP va returna coordonatele punctului central in coordonate logice. Daca dorim sa stim coordonatele in pixeli al punctului de coordonate logice (100,100) in modul de mapare MM_LOENGLISH vom folosi LPtoDP: CPoint point (100, 100); CClientDC dc (this); dc.SetMapMode (MM_LOENGLISH); dc.LPtoDP (&point); Modul MM_TEXT Functiile SetViewportOrgEx si SetWindowOrgEx modifica originea vizorului si a ferestrei. Aceste functii au ca efect deplasarea axelor astfel incit punctul de coordonate (0,0) nu se mai refera la coltul din stanga sus al ecranului. In general se foloseste doar una din cele doua functii. Explicaii asupra lucrului cu aceste functii: daca schimbam originea vizorului la (xViewOrg, yViewOrg) atunci punctul logic de coordonate (0,0) va fi mapat la punctul de coordonate de dispozitiv (xViewOrg, yViewOrg). daca schimbm originea ferestrei la (xWinOrg, yWinOrg) atunci acest punct logic va fi mapat la punctul de coordonate de dispozitiv (0,0) care este intotdeauna coltul din stinga sus al zonei client. Exemplu Mutare origine Sa presupunem ca zona client are latimea cxClient si inaltimea cyClient. Daca dorim ca punctul de coordonate logice (0,0) sa se afle in centrul zonei client, atunci: SetViewportOrgEx(hdc, cxClient/2, cyClient/2, NULL); Valorile logice ale axei x sint cuprinse in intervalul [-cxClient/2, cxClient/2], iar cele ale axei y in intervalul [-cyClient/2, cyClient/2]. Afisarea de text incepind cu coltul din stanga sus, care are coordonatele de dispozitiv (0,0) inseamna folosirea urmatoarelor coordonate logice: TextOut(hdc, -cxClient/2, -cyClient/2, ...,...); Acelasi rezultat poate fi obtinut si cu functia SetWindowOrgEx in locul functiei SetViewportOrgEx: SetWindowOrgEx(hdc, -cxClient/2, -cyClient/2, NULL); 67

Setarea extent-ului O aplicatie poate modifica in mod direct extentul ferestrei sau al vizorului numai dac modul de mapare este MM_ISOTROPIC sau MM_ANISOTROPIC. Modficarea extentului ferestrei se face cu ajutorul functiei SetWindowExt, iar extentul vizorului se modifica cu functia SetViewportExt. Valorile se dau totdeauna in unitati absolute, nu in unitati logice si nu sunt afectate de modul curent de mapare. Setarea unui extent la valoarea zero nu este permisa. Din cauza ca perechea de extent-uri stabileste un factor de scalare ce va fi folosit in conversii, marimea extentului ar trebui sa fie cat mai mica posibila pentru a simplifica calculele, de exemplu folosirea de extent-uri de 400 si 300 este echivalent cu exetent-uri de 4 si 3. Pentru a schimba orientarea unei axe (fata de orientarea implicita data de Windows), factorul de scalare trebuie sa fie negativ. Urmatorul cod are ca efect schimbarea orientarii axei y, y pozitiv va fi in sus. SetMapMode(hDC, MM_ANISOTROPIC); SetViewportExt(hDC, 1, -1); SetWindowExt(hDC, 1, 1); Setarea originilor Functiile folosite sunt: SetWindowOrg, OffsetWindowOrg, SetViewportOrg si OffsetViewportOrg. Originile sunt independente de extent. Originile sunt specificate in unitati absolute ce nu sunt afectate de modul curent de mapare. Exemple Setam un mod de mapare in care la o unitate logica ii corespund trei unitati de dispozitiv: SetMapMode(hDC, MM_ANISOTROPIC); SetWindowOrg(hDC, 0, 0); SetWindowExt(hDC, 1, 1); SetViewportOrg(hDC, 0, 0); SetViewportExt(hDC, 3, 3); Urmatorul cod deseneaza un dreptunghi de 1 pe 2 mm. SetMapMode(hDC, MM_HIMETRIC); SetViewportOrg(hDC, 0, 100); // Ce ...? SetWindowOrg(hDC, 0, 0); Rectangle(hDC, 0, 0, 100, 200); Unitatile de dispozitiv sunt mapate la rezolutia dispozitivului fizic: SetMapMode(hDC, MM_ANISOTROPIC); SetWindowOrg(hDC, 0, 0); SetWindowExt(hDC, 600, 600); // logical window is 600 dpi SetViewportOrg(hDC, 0, 0); // Device viewport is dpi of actual output device. SetViewportExt(hDC, GetDeviceCaps(hDC, LOGPIXELSX), GetDeviceCaps(hDC, LOGPIXELSY)); 68

Obtinerea informatiilor despre un periferic Functia CDC::GetDeviceCaps Urmtorul cod obtine rezolutia ecranului, in pixeli: CClientDC dc (this); int cx = dc.GetDeviceCaps (HORZRES); int cy = dc.GetDeviceCaps (VERTRES); Functia GetDeviceCaps va returna totdeauna valori fizice corecte pentru imprimanta sau orice alt periferic hardcopy (de exemplu LOGPIXELSX si LOGPIXELSY). Pentru o imprimanta laser cu 600 dpi, LOGPIXELSX si LOGPIXELSY vor avea valoarea 600. Pentru lista complet a parametrilor vedei MSDN. Useful GetDeviceCaps Parameters Parameter Returns HORZRES Width of the display surface in pixels VERTRES Height of the display surface in pixels HORZSIZE Width of the display surface in millimeters VERTSIZE Height of the display surface in millimeters LOGPIXELSX Number of pixels per logical inch horizontally LOGPIXELSY Number of pixels per logical inch vertically NUMCOLORS For a display device, the number of static colors; for a printer or plotter, the number of colors supported BITSPIXEL Number of bits per pixel PLANES Number of bit planes RASTERCAPS Bit flags detailing certain characteristics of the device, such as whether it is palettized and whether it can display bitmapped images TECHNOLOGY Bit flags identifying the device typescreen, printer, plotter, and so on

Desenarea pe ecran
(Exemple de cod) Gsiti explicatiile pentru construirea aplicatiei pe pagina in documentul ce contine cursul complet.

69

Meniuri
Windows furnizeaz suport aplicaiilor care utilizeaz meniuri: afiarea barei de meniu, derularea unui meniu popup cnd acesta este selectat, notific aplicaia cnd o comand de meniu a fost selectat. Definire termeni: Bara de meniu care apare in partea cea mai de sus a ferestrei se numeste top-level menu, iar comenzile se numesc top-level menu items. Meniul care apare cind un articol de meniu este selectat se numeste drop down menu, iar articolele din acest meniu se numesc articole meniu (menu items). Articolele de meniu sunt identificate prin valori intregi, numite ID-ul art. de meniu sau ID-ul comenzii. Windows suporta meniurile popup care sunt asemanatoare cu cele drop down cu deosebirea ca pot fi afisate oriunde pe ecran. Meniurile de context (right click) sunt meniuri popup. Meniul sistem cuprinde comenzi pentru redimensionare, mutare, minimizare, maximizare, inchidere fereastra. Actiunile legate de meniuri le gasim in clasa CMenu din MFC. CMenu contine o data membru publica HMENU m_hMenu ce pastreaza un handle la meniu, si mai multe functii ce constituie in fapt apeluri ale functiilor din API (CMenu::TrackPopupMenu, CMenu::EnableMenu, etc.). CMenu contine doua functii virtuale DrawItem si MeasureItem care pot fi rescrise daca dorim sa cream articole de meniu stilizate ce contin bitmap-uri si alte elemente grafice. Modalitati de crearea unui meniu in MFC. 1. in mod programabil, CreateMenu, InsertMenu, etc. 2. initializind o serie de structuri de date ce descriu continutul unui meniu si apoi apelam CMenu::LoadMenuIndirect. 3. cream o resursa de meniu si incarcam meniul rezultat in aplicatie in timpul executiei. Crearea unui meniu Metoda cea mai usoara este de a adauga un template de meniu la fisierul de resurse al aplicatiei. Un fisier de resurse este un fisier text care are extensia rc, si care contine instructiuni ce definesc meniul. Acest fisiser de resurse este compilat si legat la aplicatie cu un utilitar numit rc.exe (in MS VC++). O resursa este un obiect binar ca de exemplu un meniu, o icoana, stringuri, bitmap-uri. Fiecare resursa este identificata printr-un ID intreg sau string (MyMenu sau IDR_MYMENU). ID-urile sunt definite cu #define. O data ce o resursa este compilata si legata la o aplicatie ea poate fi incarcata printr-un simplu apel de functie. Un fisier de resurse contine: ID resusrsa, numele articolului de meniu, ID-urile articolului de meniu, atribute ale meniului. Exemplu IDR_MAINFRAME MENU PRELOAD DISCARDABLE BEGIN POPUP &File BEGIN MENUITEM &New\tCtrl+N, ... MENUITEM SEPARATOR END POPUP &View BEGIN ... END END

ID_FILE_NEW

Indicatii: O elipsa in descrierea meniului inseamna ca sunt necesare informatii suplimentare dupa ce articolul este selectat, ! inseamna ca se executa imediat o comanda (Exit!).

70

Afxres.h defineste valorile ID-urile pentru comenzile uzuale. Valori valide pentru ID-uri sunt in intervalul 1-0xEFFF sau mai exact 0x8000 0xF000 confrm Nota Teh. 20 din MFC. Textul ce urmeaza caracterului TAB \t identifica un accelerator. Un accelerator este o tasta sau o combinatie de taste (Ctrl+C, Ctrl+V, etc.) care cand sunt apasate au acelasi efect ca selectarea articolului din meniu. Cind definim articolul de meniu, putem sa-i indicam starea initiala, de ex GRAYED, ceea ce-l face disable, CHECKED, etc. Incrcarea i afiarea unui meniu In timpul execuiei o resurs de meniu trebuie ncrcat i ataat la fereastra. Cand fereastra este afisata, meniul va fi afisat de asemenea. Metoda 1: apel la functia CFrameWnd::Create Create(NULL, _T(Nume aplicatie), NULL, MAKEINTRESOURCE(IDR_MAINFRAME)); WS_OVERLAPPEDWINDOW, rectDefault,

Parametrul numarul 6 indica resursa de meniu. MAKEINTRESOURCE transforma un intreg intr-o data de tip LPSTR. Metoda 2: apel la functia CFrameWnd::LoadFrame, care creaza o fereastra cadru si ataseaza un meniu la aceasta LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW, NULL, NULL); LoadFrame este asemenatoare cu Create, dar poate incarca si icoane si alte resurse ceea ce nu face Create (in spate este CreateWindow , CreateWindowEx). Metoda 3: construim un obiect de tip meniu, clasa CMenu, si-l incarcam cu CMenu::LoadMenu si apoi il atasam la fereastra CWnd::SetMenu: CMenu menu; menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu); menu.Detach(); CMenu::Detach() va detasa meniul din obiectul CMenu astfel incat meniul nu va fi distrus prematur cind obiectul menu va fi distrus. Regula generala Un meniu incarcat cu LoadMenu ar trebui sa fie distrus cu DestroyMenu inainte ca aplicatia sa se termine. Metoda 3 este folosita in programe care au mai mult de un meniu. Exemplu de aplicatie cu doua meniuri: Create(NULL, _T(aplicatie)); m_menuLong.LoadMenu(IDR_LONGMENU); m_menuShort.LoadMenu(IDR_SHORTMENU); SetMenu(m_bShortMenu ? &m_menuShort, &m_menuLong); Comutarea intre cele doua meniuri se face astfel (ca raspuns la o comanda a utilizatorului) de la meniul lung la cel scurt: m_bShortMenu = TRUE; SetMenu(&m_menuShort); DrawMenuBar(); 71

de la meniul scurt la meniul lung m_bShortMenu = FALSE; SetMenu(&m_menuLong); DrawMenuBar(); Functia CWnd::DrawMenuBar() redeseneaza bara meniu pentru a reflecta schimbarile facute in meniu. Daca m_menuLong si m_menuShort sunt variabile membru ale clasei fereastra cadru (derivata din CWnd), destructorii pentru meniuri se vor apela cand fereastra cadru va fi distrusa, deci in acest caz nu mai e nevoie de DestroyMenu. Raspunsul la comenzile din meniu Cand utilizatorul selecteaza un articol de meniu (bara de meniuri) fereastra primeste o serie de mesaje: WM_INITMENU ce notifica ferestrei ca un articol al meniului superior (top-level menu item) a fost selectat. Inainte ca meniul sa fie afisat, fereastra primeste mesajul WM_INITMENUPOPUP, locul unde ar putea fi actualizate (enable, disable, checked, etc.) articolele meniului, meniul inca nu este afisat. Cand parcurgem articolele unui meniu drop down (popup), fereastra primeste mesajul WM_MENUSELECT, mesaj ce poate fi tratat, in special in SDK, prin afisarea unui text in bara de stare (scurt help). Dar cel mai important mesaj este WM_COMMAND trimis cand utililizatorul selecteaza un articol de meniu. Cuvantul inferior al parametrului wParam pastreaza ID-ul comenzii (articolul din meniul popup) si in SDK se face practic switch pe LOWORD(wParam) pentru a identifica care comanda a fost selectata. In MFC se adauga in harta de mesaje a clasei respective macroul ON_COMMAND care trateaza mesajul WM_COMMAND. ON_COMMAND are doi parametri, primul indica ID_ul comenzii, iar al doilea indica handlerul comenzii (functia ce va fi apelata la selectia acestei comenzi). Exemplu ON_COMMAND(ID_FILE_SAVE, OnFileSave) Functiile ce trateaza comenzile de meniu nu au parametri si nu intorc nimic.

Intervale pentru comenzi


Sunt folosite pentru a trata un grup de articole meniu cu o singura functie. Functia va avea codul necesar pentru a identifica corect care comanda a fost selectata. Exemplu Tratam culorile penitei si construim un meniu cu comenzile: Rosu, Verde, Albastru. Pentru a manevra mai usor interfata, comenzile acestui meniu vor fi inserate in harta de mesaje a clasei derivate din CView. Presupunem IDurile sunt: IDM_ROSU, IDM_VERDE, IDM_ALBASTRU iar functiile corespunzatoare OnRosu, OnVerde, OnAlbastru, iar culoarea o pastram intr-un int, m_nCuloare, cu valorile respective 0,1,2. In harta de mesaje vom avea: ON_COMMAND(IDM_ROSU, OnRosu) ON_COMMAND(IDM_VERDE, OnVerde) ON_COMMAND(IDM_ALBASTRU, OnAlbastru) iar functiile vor fi: void CXView::OnRosu() { 72

m_nCuloare = 0;

void CXView::OnVerde() { m_nCuloare = 1; } void CXView::OnAlbastru() { m_nCuloare = 2; } Daca am avea mai multe culori? Cum procedam? Grupam aceste comenzi si vom extrage ID-ul comenzii cu CWnd:;GetCurrentMessage, iar harta de mesaje va fi: ON_COMMAND(IDM_ROSU, OnCuloare) ON_COMMAND(IDM_VERDE, OnCuloare) ON_COMMAND(IDM_ALBASTRU, OnCuloare) si codul din functia OnCuloare() void CXView::OnCuloare() { UNIT nID = (UINT) LOWORD(GetCurrentMessage()->wParam); m_nCuloare = nID IDM_ROSU; } Indecsii meniului incep cu 0 (zero). Dar harta de mesaje este inca prea mare. Vom folosi macroul ON_COMMAND_RANGE care trateaza o serie de comenzi care au ID-uri contigue (secventiale): ON_COMMAND_RANGE(IDM_ROSU, IDM_ALBASTRU, OnCuloare); In final am obtinut o singura intrare in harta de mesaje si o singura functie ce trateza diverse optiuni de meniu.

Actualizarea articolelor intr-un meniu


Distingem mai multe posibilitati, pe care le exemplificam in continuare. Metoda 1: actualizare in momentul cand articolul este selectat void CXView::OnCuloare() { CMenu* pMenu = GetMenu(); pMenu->CheckMenuItem(m_nCuloare + IDM_ROSU, MF_UNCHECKED); pMenu->CheckMenuItem(nID, MF_CHECKED); m_nCuloare = nID IDM_ROSU; } Metoda 2: mutam codul care actualizeaza meniul in tratarea mesajului WM_INITMENUPOPUP iar functia este OnInitMenuPopup. Aici actualizarea se face inainte ca meniul sa fie afisat. Aceasta functie are trei parametri: un pointer de tip CMenu ce pointeaza la submeniul care va fi afisat, un UINT valoare ce da indexul art din submeniu si variabila de tip BOOL care este diefrita de 0 daca mesajul este al meniului sistem si 0 altfel. 73

In harta de mesaje avem: ON_WM_INITMENUPOPUP() iar functia: (COLOR_MENU_INDEX este o variabila ce specifica pozitia meniului Color in bara de meniu a aplicatiei) void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { if (!bSysmenu && (nIndex == COLOR_MENU_INDEX)) { pPopMenu->CheckMenuItem(IDM_ROSU, MF_UNCHECKED); pPopMenu->CheckMenuItem(IDM_VERDE, MF_UNCHECKED); pPopMenu->CheckMenuItem(IDM_ALBASTRU, MF_UNCHECKED); pPopMenu->CheckMenuItem(m_nCuloare + IDM_ROSU, MF_CHECKED); } } Un alt mecanism este tot la mesajul WM_INITMENUPOPUP si consta in a completa harta de mesaje cu functii (handleri) pentru comenzi, handleri ce vor fi apelati inainte ca meniul sa fie vizibil si inaintea fiecarei selectii din meniu. Fiecarui handler de actualizare ii este pasat un pointer la un obiect CCmdUI a carei functii membru pot fi folosite pentru a modifica articolul de meniu. Si pentru ca aceasta clasa CCmdUI, nu este specifica unui tip particular al unui element al interfetei utilizatorului, acesti handleri pot fi utilizati si pentru actualizarea butoanelor din toolbar si alte obiecte ale interfetei UI. Exemplu in harta de mesaje: ON_COMMAND_UPDATE_UI(IDM_ROSU, OnUpdateRosu) ON_COMMAND_UPDATE_UI(IDM_VERDE, OnUpdateVerde) ON_COMMAND_UPDATE_UI(IDM_ALBASTRU, OnUpdateAlbastru) void CMainFrame::OnUpdateRosu(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_nCuloare); } , etc. Metode din CCmdUI: Enable, SetCheck, SetRadio (nu are echivalent in SDK), SetText Keyboard Acceleratori Adugare la aplicatie Acceleratori: combinatii de taste pentru a selecta o comanda. Un accelerator produce mesajul WM_COMMAND. Se creaza o tabela de acceleratori in fisierul de resurse, o resursa speciala care coreleaza ID-ul articolului de meniu cu tastele sau combinatiile de taste, si incarca resursa in program cu un apel de functie. Resursa tabela de acceleratori este definita de un bloc ACCELERATORS in fisierul de resurse. Format general: ResurseID ACCELERATORS BEGIN ... END 74

In MFC tabela de acceleratori are structura: IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE BEGIN N, ID_FILE_NEW, VIRTKEY, CONTROL ... VK_BACK, ID_EDIT_UNDO, VIRTKEY, ALT END VIRTKEY = spune compilatorului de resurse ca prima intrare este un cod de cheie virtuala, iar urmatorul este CONTROL, ALT sau SHIFT Incarcarea acceleratorilor se face cu functia: LoadAccelTable(MAKEINTRESOURCE(IDR_MAINFRAME)); De asemenea se poate face si cu LoadFrame. Daca doua resurse au acelasi nume, LoadFrame le incarca pe amindoua la un singur apel de functie. Observatie Pentru ca acceleratorii sa lucreze, bucla de mesaje trebuie sa includa un apel la functia API ::TranslateAccelerator

Crearea meniului programatic (at run time)


Exemplu CMenu menuMain; menuMain.CreateMenu(); Cmenu menuPopup; menuPopup.CreatePopupMenu(); menuPopup.AppendMenu(MF_STRING, IDM_ROSU, &Rosu); menuMain.AppendMenu(MF_POPUP, (UINT) menuPopup.Detach(), &Culori); menuPopup.CreatePopupMenu(); menuPopup.AppendMenu(MF_STRING, IDM_EXIT, &Exit); ... menuMain.AppendMenu(MF_POPUP, (UINT)menuPopup.Detach(), &Sesiune); SetMenu(&menuMain); menuMain.Detach(); Modificarea programatica Functiile necesare pentru modificare sunt: AppendMenu, InsertMenu, ModifyMenu, DeleteMenu, RemoveMenu Inainte de a modifica un meniu, trebuie sa obtinem un pointer la acel meniu, CWnd::GetMenu. In top-level menu, articolele sunt referite prin indici ce incep cu 0 (zero) cel mai din stinga. Exemplu CMenu* pMenu = GetMenu(); pMenu-DeleteMenu(1, MF_BYPOSITION); sau 75

pMenu->DeleteMenu(IDM_CULORI, MF_BYCOMMAND); sau item-uri din meniuri CMenu* pMenu = GetMenu()-GetSubMenu(1); pMenu->DeleteMenu(IDM_VERDE, MF_BYCOMMAND); sau echivalent: CMenu* pMenu = GetMenu(); pMenu->DeleteMenu(IDM_VERDE, MF_BYCOMMAND); // merge bine Cititi clasa CMenu. Meniul system Obtinerea unui pointer la meniul sistem se face ca in exemplul de mai jos: CMenu* pSysMenu = GetSystemMenu(FALSE); FALSE = inseamna ca dorim un pointer la o copie a meniului sistem pe care vrem sa-l modificam. TRUE = reseteaza meniul sistem la starea sa implicita. Exemplu de adaugare a unei comenzi la meniul sistem: pSysMenu->AppendMenu(MF_SEPARATOR); pSysmenu->AppendMenu(MF_STRING, IDM_COMANDA_NOUA, _T(&Comanda adaugata)); in harta de mesaje: ON_WM_SYSCOMMAND() si in implementare avem: void CMainFrame::OnSysCommand(UINT nID, LPARAM lPram) { if ((nID & 0xFFF0 ) == IDM_COMMANDA_NOUA) { // ceva } CFrameWnd::OnSysCommand(nID, lParam); }

Meniuri contextuale. Mesajul WM_CONTEXTMENU


Se activeaza de obicei la clic dreapta mouse. Un meniu contextual nu este nimic altceva decit un submeniu care nu e atasat la un top-level menu. Functia CMenu::TrackPopupMenu afiseaza un asemenea meniu. Prototipul functiei este: BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPRECT lpRect = NULL) Descriere parametri x, y = locatia pe ecran (coordonate ecran) unde va apare meniul; 76

nFlags = informatii despre alianiamentul relativ la x (TPM_LEFTALIGN, TPM_CENTERALIGN, TPM_RIGHTALIGN) si care buton este utilizat in continuare pentru a face o selectie (TPM_LEFTBUTTON, TPM_RIGHTBUTTON). pWnd = identifica fereastra care va primi mesajul dupa selectia unei comenzi din meniu; lpRect = dimensiunea unui dreptunghi (coord. ecran) in care utilizatorul poate face clic fara a anula meniul afisat. Ex.

Exemplu CMenu menu; menu.LoadMenu(IDR_CONTEXTMENU); CMenu* pContextMenu = menu.GetSubMenu(0); pContextMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, point.x, point.y, AfxGetMainWnd()); Daca tratam mesajul WM_CONTEXTMENU in harta de mesaje avem macroul: ON_WM_CONTEXTMENU si functia afx_msg OnContextMenu(CWnd* pWnd, CPoint point); unde, pWnd identifica fereastra in care s-a facut clic si point coordonatele punctului unde s-a facut clic.

77

Obiecte nucleu
Ce sunt obiectele nucleu ? Ca dezvoltatori de sofware, noi crem, deschidem i manipulm obiecte nucleu n mod obinuit. Sistemul creeaz i manipuleaz cteva tipuri de obiecte nucleu, cum ar fi obiecte token, obiecte eveniment, obiecte fiier, obiecte fiiere mapate n memorie, obiecte de completare I/O, obiecte job, obiecte mailslot, obiecte mutex, obiecte pipe-uri, obiecte proces, obiecte semafor, obiecte fir de execuie i obiecte waitable timer.Aceste obiecte sunt create apelnd diferite funcii. De exemplu, funcia CreateFileMapping face ca sistemul s creeze un obiect fiier mapat n memorie. Fiecare obiect nucleu este pur i simplu un bloc de memorie alocat de nucleul sistemului de operare i accesibil doar acestuia. Acest bloc de memorie este o structur de date ai crei membri menin informaii despre obiect. Unii membri (descriptorii de securitate, contorul de utilizare, i aa mai departe) sunt la fel pentru toate obiectele nucleu, dar majoritatea sunt specifice unor obiecte nucleu particulare. De exemplu un obiect nucleu proces are un ID de proces, o prioritate de baz, un cod de ieire. Contorul de resurse Obiectele nucleu sunt proprietatea nucleului sistemului de operare, i nu a unui proces. n alte cuvinte, dac procesul nostru apeleaz o funcie care creeaz un obiect nucleu i apoi procesul se termin, obiectul nucleu nu este neaparat distrus. n majoritatea cazurilor, obiectul va fi distrus; dar dac un alt proces utilizeaz acel obiect nucleu creat de proces, nucleul tie s nu distrug obiectul pn cnd cellalt proces nu l mai folosete. Un lucru demn de reinut aici este c un obiect nucleu poate depi durata de via a procesului care l-a creat. Nucleul tie cte procese folosesc un anumit obiect nucleu deoarece fiecare obiect nucleu are un contor de utilizare. Acesta este una din datele membre comune tuturor obiectelor nucleu. Atunci cnd un obiect este creat, contorul su de utilizare este setat la 1. Apoi cnd un alt proces primete acces la un obiect nucleu existent, contorul de utilizare este incrementat. Atunci cnd un proces i ncheie execuia, nucleul sistemului decrementeaz automat contorul de utilizare pentru toate obiectele nucleu pe care le-a deschis procesul. Dac contorul de utilizare devine 0, nucleul distruge n mod automat obiectul. Acest lucru ne asigur c nici un obiect nucleu nu va rmne n sistem dac nu mai exist procese care refereniaz acel obiect. Securitate Obiectele nucleu pot fi protejate cu un descriptor de securitate. Acesta descrie cine creeaz obiectul, cine poate primi acces sau poate folosi obiectul i cine nu are acces la obiect. Descriptorii de securitate sunt de obicei folosii atunci cnd scriem aplicaii server; putem ignora aceast facilitate dac scriem aplicaii pentru partea de client. Windows 98 nu este conceput ca un sistem de operare pe partea de server. Din aceast cauz, Microsoft nu a implementat partea de securitate n Windows 98. Aproape toate funciile care creeaz obiecte nucleu au un pointer la o structur SECURITY_ATTIBUTES ca un argument, ca n exemplul de mai jos : HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName); Majoritatea aplicaiilor vor transmite FALSE pentru acest argument astfel nct obiectul este creat cu setrile de securitate implicite. Acestea nseamn c oricare membru al grupului administrator i creatorul obiectului vor avea acces nelimitat la obiect; toi ceilali nu au acces. Totui, putem aloca o structur SECURITY_ATTRIBUTES, o iniializm i trimitem adresa acesteia pentru acest parametru. O structur SECURITY_ATTRIBUTES arat n modul urmtor : typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; 78

BOOL bInheritHandle; } SECURITY_ATTRIBUTES; Chiar dac aceast structur este numit SECURITY_ATTRIBUTES, ea de fapt include doar un membru care are o legtur cu securitatea: lpSecurityDescriptor. Dac dorim s restrngem accesul la obiectele nucleu pe care le crem, trebuie s crem un descriptor de securitate i apoi s iniializm structura SECURITY_ATTIBUTES n modul urmtor : SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); // Folosit pentru redarea versiunii. sa.lpSecurityDescriptor = pSD; // Adresa unei SD neiniializate. sa.bInheritHandle = FALSE; // Discutat mai tarziu. HANDLE hFileMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 1024, "MyFileMapping"); Atunci cnd dorim s primim acces la un obiect nucleu existent (mai degrab dect s crem unul nou), trebuie s precizm operaiile pe care dorim s le efectum asupra obiectului. De exemplu, dac dorim s accesm un obiect fiier mapat n memorie existent pentru a citi date din el, putem apela OpenFileMapping : HANDLE hFileMapping = OpenFileMapping( FILE_MAP_READ, FALSE, "MyFileMapping"); Prin trimiterea valorii FILE_MAP_READ ca primul parametru al funciei OpenFileMapping, am indicat c intenionm s citim din acest fiier mapat n memorie dup ce primim acces la el. Funcia OpenFileMapping efectueaz o verificare a securitii mai nti, nainte de a ntoarce o valoare de identificator valid. Dac utilizatorul are voie s acceseze obiectul nucleu fiier mapat n memorie existent, OpenFileMapping returneaz un identificator valid. Totui, dac utilizatorul nu are acces, OpenFileMapping returneaz NULL, iar un apel al funciei GetLastError va returna valoarea 5 (ERROR_ACCES_DENIED). Totui, majoritatea aplicaiilor nu folosesc securitatea. Deoarece Windows 98 nu are aceti descriptori de securitate, pot aprea probleme la portarea programelor pe sistemul de operare Windows 2000. Crearea unui obiect nucleu n continuare vom discuta funcii care creeaz obiecte nucleu : HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD dwStackSize, LPTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD dwCreationFlags, PDWORD pdwThreadId); HANDLE CreateFile( PCTSTR pszFileName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_ATTRIBUTES psa, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, 79

PCTSTR pszName); HANDLE CreateSemaphore( PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); Toate funciile care creeaz obiecte nucleu returneaz identificatori relativi la proces care pot fi folosii de toate firele care ruleaz n acel proces. Ori de cte ori apelm o funcie care accept identificatorul unui obiect nucleu ca argument, i trimitem valoarea returnat de una din funciile Create*. Dac apelm o funcie pentru a crea un obiect nucleu i apelul eueaz, valoarea identificatorului returnat este de obicei 0 (NULL). Sistemul ar trebui s aib resurse foarte reduse de memorie sau s aib o problem de securitate pentru a se ntmpla acest lucru. Din pcate, puine funcii returneaz o valoare a identificatorului egal cu 1 (INVALID_HANDLE_VALUE) atunci cnd apelul lor eueaz. De exemplu, dac funcia CreateFile eueaz s deschid fiierul precizat, ea returneaz INVALID_HANDLE_VALUE n loc de NULL. Trebuie s fim foarte ateni atunci cnd verificm valoarea returnat de o funcie care creeaz un obiect nucleu. i anume, putem compara valoarea cu INVALID_HANDLE_VALUE doar atunci cnd crem CreateFile. Urmtorul cod este incorect : HANDLE hMutex = CreateMutex(); if (hMutex == INVALID_HANDLE_VALUE) { // Nu vom executa niciodat acest cod deoarece // CreateMutex returneaz NULL dac eueaz. } De asemenea, i codul urmtor este incorect : HANDLE hFile = CreateFile(); if (hFile == NULL) { // Nu vom executa niciodat acest cod deoarece // CreateFile returneaz INVALID_HANDLE_VALUE (-1) dac eueaz. }

nchiderea unui obiect nucleu


Indiferent de modalitatea de creare a obiectului nucleu, vom indica sistemului c am terminat de utilizat obiectul apelnd CloseHandle : BOOL CloseHandle(HANDLE hobj); Aceast funcie verific mai nti tabelul de identificatori ai procesului pentru a se asigura c indexul transmis identific un obiect la care procesul are de fapt drept de acces. Dac indexul este valid, sistemul obine adresa structurii de date a obiectului nucleu i decrementeaz contorul de utilizare din structur; dac contorul este 0, nucleul sistemului de operare distruge obiectul nucleu din memorie. Dac contorul de utilizare a oricrui dintre aceste obiecte devine 0, nucleul sistemului de operare distruge obiectul. Astfel, aplicaia noastr poate avea scurgeri de memorie n timpul execuiei sale, dar n momentul terminrii execuiei sistemul garanteaz c totul este curat n mod corespunztor. Acest lucru este efectuat pentru toate obiectele, nu doar pentru obiectele nucleu. Partajarea obiectelor nucleu dincolo de graniele procesului n mod frecvent, fire care ruleaz n procese diferite au nevoie s partajeze obiecte nucleu. Exist mai multe modaliti : obiectele fiiere mapate n memorie ne permit s partajm blocuri de date ntre dou procese care ruleaz pe aceeai main. mailslot-urile si pipe-urile cu nume permit aplicaiilor s trimit blocuri de date ntre procese care ruleaz pe maini diferite conectate la reea. 80

mutexurile, semafoarele i evenimentele permit firelor de execuie din procese diferite s i sincronizeze execuia, ca n cazul n care o aplicaie are nevoie s anune o alt aplicaie atunci cnd i-a terminat de executat sarcina. Creatorul obiectului poate preveni accesul unui utilizator neautorizat la acel obiect prin simpla interzicere a accesului la acel obiect. Motenirea identificatorilor obiectelor Motenirea poate fi folosit doar atunci cnd procesele sunt n relaia printe-copil. n acest caz, unul sau mai muli identificatori sunt disponibili procesului printe, iar acesta decide s creeze un proces copil, dndu-i acestuia acces la identificatorii obiectelor procesului printe. Pentru ca acest fel de motenire s funcioneze, procesul printe trebuie s efectueze civa pai. n primul rnd, atunci cnd procesul printe creeaz un proces copil, el trebuie s indice sistemului c vrea ca identificatorul obiectului s fie motenibil. Trebuie s avem n vedere c, chiar dac identificatorii obiectelor nucleu sunt motenibile, obiectele n sine nu sunt. Pentru a crea un identificator motenibil, procesul printe trebuie s aloce i s iniializeze o structur SECURITY_ATTRIBUTES i s transmit adresa acestei structuri funciei Create. Urmtorul cod creeaz un mutex i returneaz un identificator motenibil la acesta : SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // Make the returned handle inheritable. HANDLE hMutex = CreateMutex(&sa, FALSE, NULL); ... Aceast poriune de cod iniializeaz o structur SECURITY_ATTRIBUTES indicnd faptul c obiectul trebuie creat folosind descriptorii implicii de securitate (ignorai n Windows 98) i c identificatorul returnat trebuie s fie motenibil. Chiar dac Windows 98 nu are suport integral pentru securitate, el totui suport motenirea; de aceea, Windows 98 folosete n mod corect valoarea membrului bInheritHandle. Urmtorul pas este ca procesul printe s creeze procesul copil. Acest lucru este realizat cu ajutorul funciei CreateProcess : BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES pszThread, BOOL bInheritHandles, DWORD dwCreationFlags, PVOID pvEnvironment, PCTSTR pszCurrentDirectory, LPSTARTUPINFO pStartupInfo, PPROCESS_INFORMATION pProcessInformation); De obicei, atunci cnd crem un nou proces, trimitem valoarea FALSE pentru parametrul bInheritHandle. Aceast valoare spune sistemului c nu dorim ca procesul copil s moteneasc identificatorii motenibili care sunt n tabelul de identificatori ai procesului printe. Dac totui trimitem TRUE pentru acest parametru, copilul va moteni identificatorii motenibili ai procesului printe. Atunci cnd trimitem valoarea TRUE, sistemul creeaz noul proces copil dar nu i permite execuia imediat. Trebuie s reinem c motenirea identificatorilor obiectelor nucleu are loc doar la momentul crerii procesului fiu. Dac procesul printe creeaz noi obiecte nucleu, un proces copil care deja ruleaz nu va moteni aceste noi obiecte. Motenirea identificatorilor obiectelor are o caracteristic ciudat : atunci cnd o folosim, procesul copil nu are nici o idee c a motenit vreun identificator. Motenirea este util doar atunci cnd procesul copil spune 81

c ateapt acces la un obiect nucleu atunci cnd este creat de alt proces. De obicei, aplicaiile printe i copil sunt scrise de aceeai companie; totui, o companie diferit poate scrie aplicaia copil dac acea companie documenteaz ce ateapt procesul copil. Obiecte cu nume A doua metod de a partaja obiecte nucleu dincolo de graniele proceselor este de a denumi obiectele. Multe dei nu toate obiecte nucleu pot fi denumite. De exemplu, toate funciile urmtoare creeaz obiecte nucleu cu nume : HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL bInitialOwner, PCTSTR pszName); HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL bManualReset, BOOL bInitialState, PCTSTR pszName); HANDLE CreateSemaphore( PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName); Toate aceste funcii au un ultim parametru comun, pszName. Atunci cnd transmitem NULL pentru acest parametru, indicm sistemului c dorim s crem un obiect nucleu fr nume. Atunci cnd crem un astfel de obiect, l putem partaja dincolo de graniele procesului folosind motenirea sau DuplicateHandle. Pentru a partaja un obiect nucleu prin nume, trebuie s i atribuim acestuia un nume. Dac nu trimitem NULL pentru parametrul pszName, ar trebui s trimitem adresa unui ir terminat cu zero. Acest nume poate fi de lungime maxim egal cu MAX_PATH (definit ca 260). Acum c tim cum s numim un obiect nucleu, s vedem cum putem partaja obiectele n acest mod. S spunem c procesul A i ncepe execuia i apeleaz urmtoarea funcie : HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, "myMutex"); Aceast funcie creeaz un nou mutex i i atribuie numele myMutex. S observm c n identificatorul procesului A, hMutexProcessA nu este un identificator motenibil i nu are nevoie s fie atunci cnd doar denumim obiectele. Dup o perioad de timp, un proces creeaz procesul B. Acesta nu trebuie s fie copilul procesului A; el poate fi creat din Explorer sau din alt aplicaie. Faptul c procesul B nu trebuie s fie copilul procesului A este un avantaj al utilizrii obiectelor cu nume n locul motenirii. Atunci cnd procesul B i ncepe execuia, el execut urmtorul cod : HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "myMutex"); Atunci cnd apelul CreateMutex al procesului B este efectuat, sistemul verific mai nti dac nu exist deja un obiect nucleu cu acelai nume. Deoarece exist un obiect nucleu cu acelai nume, nucleu sistemului de operare verific tipul obiectului. Deoarece ncercm s crem un mutex i obiectul cu numele myMutex este de 82

asemenea un mutex, sistemul face o verificare a securitii pentru a vedea dac apelantul are acces deplin la obiect, iar n caz afirmativ sistemul gsete o intrare liber n tabelul de identificatori ai procesului B i iniializeaz aceast intrare pentru a pointa la obiectul nucleu existent. Dac tipurile obiectelor difer sau apelantului i este respins accesul, CreateMutex eueaz (returneaz NULL). Atunci cnd apelul CreateMutex al procesului B este reuit, nu este creat de fapt un mutex. Procesului B i este atribuit un identificator relativ la proces care identific obiectul mutex existent. Bineneles, deoarece o nou intrare din tabelul de identificatori ai procesului B refereniaz acest obiect, contorul de utilizare a mutexului este incrementat; obiectul nu va distrus pn cnd ambele procese A i B i-au nchis identificatorii la acest obiect. Este foarte posibil ca valorile identificatorilor din cele dou procese s difere. Atunci cnd obiectul B apeleaz CreateMutex, el transmite informaia de atribute de securitate i un al doilea parametru funciei. Aceti parametri sunt ignorai dac un obiect cu numele specificat exist ! O aplicaie poate s i dea seama dac a creat de fapt un nou obiect nucleu sau dac a deschis unul deja existent apelnd funcia GetLastError imediat dup apelul funciei Create* : HANDLE hMutex = CreateMutex(&sa, FALSE, "JeffObj"); if (GetLastError() == ERROR_ALREADY_EXISTS) { // Am deschis un obiect nucleu existent. // sa.lpSecurityDescriptor i cel dea- doilea parametru sunt ignorai } else { // Am creat un obiect nou. // sa.lpSecurityDescriptor i cel de-al doilea parametru sunt folosii pentru a crea obiectul. } Exist o metod alternativ pentru partajarea obiectelor nucleu prin nume. n loc s apelm funcia Create*, un proces poate apela una din funciile Open* urmtoare : HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenFileMapping( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); Aceste funcii au acelai prototip. Ultimul parametru, pszName, indic numele obiectului nucleu. Nu putem trimite NULL pentru acest parametru; trebuie s trimitem adresa unui ir ncheiat cu zero. Aceste funcii pur i simplu caut n singurul spaiu de nume al obiectelor nucleu pentru a gsi o potrivire. Dac nu exist nici un obiect nucleu cu numele precizat, funcia returneaz NULL i GetLastError returneaz 2 (ERROR_FILE_NOT_FOUND). Totui, dac exist un obiect nucleu cu acel nume i dac este de acelai tip, sistemul verific s vad dac accesul cerut (prin intermediul parametrului dwDesiredAcces) este permis; dac da, tabelul de identificatori ai procesului este actualizat i contorul de utilizare al obiectului este incrementat. Identificatorul returnat va fi motenibil dac trimitem valoarea TRUE pentru parametrul bInheritHandle. Principala deosebire dintre funcia Create* i funcia Open* este c dac obiectul nu exist deja, funcia Create* l va crea, n timp ce apelul funciei Open* va eua. 83

Obiectele cu nume sunt de obicei folosite pentru a preveni rularea instanelor multiple a unei aplicaii care ruleaz. Pentru a realiza acest lucru, putem s apelm funcia Create* n funcia noastr main sau WinMain pentru a crea obiecte cu nume (nu conteaz ce tip de obiecte crem). Atunci cnd funcia Create* returneaz, apelm GetLastError. Dac aceasta returneaz ERROR_ALREADY_EXISTS, atunci o alt instan a aplicaiei noastre ruleaz si noua instan i poate nceta execuia. int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow) { HANDLE h = CreateMutex(NULL, FALSE, "{FA531CC1-0497-11d3-A180-00105A276C3E}"); if (GetLastError() == ERROR_ALREADY_EXISTS) { // Mai exist o instan a aplicaiei care ruleaz. return(0); } // Aceasta este prima instan a aplicaiei. ..... // nainte de iei, se inchide obiectul. CloseHandle(h); return(0); }

Spaiile de nume Terminal Server


Trebuie notat c Terminal Server schimb puin scenariul anterior. O main Terminal Server va avea spaii de nume multiple pentru obiectele nucleu. Exist doar un spaiu de nume global, care este folosit de obiectele nucleu care sunt proiectate s fie accesibile din toate sesiunile client. Acest spaiu de nume este n general folosit de ctre servicii. n plus, fiecare sesiune client are propriul spaiu de nume. Acest lucru face ca dou sau mai multe sesiuni care ruleaz aceeai aplicaie s nu interfereze una cu alta o sesiune nu poate accesa obiectele celeilalte sesiuni chiar dac obiectele au acelai nume. Pe o main fr Terminal Server, serviciile i aplicaiile partajeaz acelai spaiu de nume. Obiectele nucleu cu nume ale unui serviciu ntotdeauna se gsesc n spaiul global de nume. Implicit, n Terminal Server, un obiect nucleu cu nume al unei aplicaii este n spaiul de nume al sesiunii. Totui, este posibil s form ca un obiect nucleu cu nume s mearg n spaiul de nume global prefixnd numele cu Global, ca n exemplul urmtor : HANDLE h = CreateEvent(NULL, FALSE, FALSE, "Global\\MyName"); Putem de asemenea s precizm c vrem ca obiectul nucleu s mearg n spaiul de nume al sesiunii prefixnd numele cu Local\, ca mai jos : HANDLE h = CreateEvent(NULL, FALSE, FALSE, "Local\\MyName"); Microsoft consider cuvintele Global i Local drept cuvinte rezervate pe care nu trebuie s le folosim n numele obiectelor exceptnd cazul n care dorim s form un anumit spaiu de nume. Microsoft consider de asemenea cuvntul Session drept un cuvnt rezervat, dei deocamdat nu are nici neles deosebit. Toate aceste trei cuvinte rezervate sunt case-sensitive. Ele sunt ignorate dac maina gazd nu ruleaz Terminal Server.

84

Procese
Un proces este n mod normal definit ca o instan a unui program n rulare. n Win32, un proces este posesorul unui spaiu de adrese de 4 GB. Spre deosebire de corespondentele lor din MS-DOS i Windows pe 16 bii, procesele Win32 sunt inerte : adic, un proces Win32 nu face nimic, el doar are un spaiu de 4 GB de spaiu de adrese care conine codul i data pentru fiierul unei aplicaii .EXE. Orice DLL necesar fiierului .EXE va avea codul i datele sale ncrcate n spaiul de adrese al procesului. n completarea spaiului de adrese, un proces este posesorul unor diferite resurse, cum ar fiiere, alocri dinamice ale memoriei i fire de execuie. Diferitele resurse create n timpul vieii unui proces sunt distruse atunci cnd procesul se termin garantat. Dup cum am spus, procesele sunt inerte. Pentru ca un proces s realizeze ceva, el trebuie s fie posesorul unui fir de execuie; acest fir de execuie este responsabil cu executarea codului coninut n spaiul de adrese al procesului. De fapt, un proces poate conine mai multe fire de execuie, toate rulnd un anumit cod n mod simultan. Pentru a face acest lucru, fiecare fir are propriul su set de regitri ai procesorului i stiva sa proprie. Fiecare proces are cel puin un fir care execut cod coninut n spaiul de adrese al procesului. Dac nu ar fi fost nici un fir de execuie, atunci nu ar fi fost nici un motiv ca procesul s mai existe, iar sistemul ar distruge n mod automat procesul i spaiul su de adrese. Pentru ca toate procesele s ruleze, sistemul de operare programeaz o parte din timpul procesorului pentru fiecare fir de execuie. Sistemul de operare d iluzia c toate firele ruleaz concurent, oferind buci de timp (numite cuante) firelor. Atunci cnd este creat un proces Win32, primul su fir de execuie, numit firul principal de execuie, este creat n mod automat de ctre sistem. Firul principal poate apoi crea alte fire iar acestea pot crea la rndul lor alte fire. Windows NT este capabil s utilizeze maini care conin mai multe procesoare. Astfel, pe un calculator cu dou procesoare pot rula n acelai timp dou fire de execuie diferite. Nucleul Windows NT face tot managementul i programarea firelor n acest tip de sistem. Noi ca programatori nu trebuie s facem nimic special pentru a ne putea bucura de avantajele oferite de un calculator multiprocesor. Windows 95 poate folosi doar un singur procesor. Chiar dac maina pe care ruleaz Windows 95 are mai mult de un procesor, Windows 95 poate programa doar un singur fir la un moment dat; celelalte procese stau i ateapt. Atributele unui proces Identificatorul de instan al procesului Fiecarui fiier EXE sau DLL ncrcat n spaiul de adrese al unui proces i este atribuit un identificator de instan unic. Instana fisierului EXE este transmis ca fiind ca primul parametru al funciei WinMain, hinstExe. Valoarea identificatorului este n mod normal necesar pentru apeluri care ncarc resurse. De exemplu, pentru a ncrca o resurs de tip icoan dintr-un fiier EXE, vom apela : HICON LoadIcon (HINSTANCE hinst, LPCTSTR lpszIcon); Primul parametru al funciei LoadIcon indic ce fiier (EXE sau DLL) conine resursa pe care dorim s o ncrcm. Multe aplicaii salveaz parametrul hinstExe al funciei WinMain ntr- variabil global pentru a fi uor accesibil ntregului cod din fiierul EXE. Valoarea real a parametrului hinstExe a funciei WinMain este adresa de baz a memoriei care indic unde a ncrcat sistemul imaginea fiierului EXE n spaiul de adrese al procesului. De exemplu, dac sistemul deschide fiierul EXE i i ncarc coninutul la adresa 0x00400000, parametrul hinstExe a funciei WinMain va avea valoarea 0x00400000. Adresa de baz la care o aplicaie este ncrcat este determinat de ctre linker. Linker-e diferite pot folosi adrese de baz diferite. Linker-ul Visual C++ folosete o adres de baz implicit egal cu 0x00400000 deoarece aceasta este cea mai mic adres la care se poate ncrca imaginea unui fiier executabil n Windows 95. Unele linker-e mai vechi folosesc o adres de baz implicit egal cu 0x00010000 deoarece aceasta este cea mai mic adres la care imaginea unui fiier executabil poate fi ncrcat n Windows NT. Putem schimba adresa de baz pe care o ncarc aplicaia noastr folosind switch-ul de adres al linker-ului pentru linker-ul Microsoft. Funcia GetModuleHandle HMODULE GetModuleHandle (LPCTSTR lpszModule); 85

returneaz adresa de baz care indic unde este ncrcat un fiier EXE sau DLL n spaiul de adrese al procesului. Atunci cnd apelm aceast funcie, i transmitem un ir terminat cu zero care precizeaz numele fiierului EXE sau DLL ncrcat n spaiul de adrese al procesului apelant. Dac sistemul gsete numele acelui fiier, GetModuleHandle returneaz adresa de baz la care este ncrcat fiierul. Sistemul returneaz NULL dac nu poate gsi fiierul specificat. Putem de asemenea apela GetModuleHandle, transmindu-i valoarea NULL parametrului lpszModule. Atunci cnd procedm astfel, GetModuleHandle returneaz adresa de baz a fiierului EXE. Acest lucru este fcut de codul C run-time de pornire atunci cnd apeleaz funcia noastr WinMain. Trebuie s inem minte dou caracteristici importante ale funciei GetModuleHandle. n primul rnd, GetModuleHandle examineaz doar spaiul de adrese al procesului apelant. Dac acest proces nu folosete nici o funcie GDI, apelarea funciei GetModuleHandle i transmiterea ctre acesta valoarea GDI32 va cauza returnarea valorii NULL, dei este posibil c fiierul GDI32.DLL este ncrcat n spaiul de adrese al procesului. n al doilea rnd, apelarea GetModuleHandle transmindu-i valoarea NULL returneaz adresa de baz a fiierului EXE n spaiul de adrese al procesului. Aa c chiar dac apelm GetModuleHandle(NULL) din codul coninut n interiorul unui fiier DLL, valoarea returnat este adresa de baz a fiierului EXE i nu cea a fiierului DLL. Funcia GetModuleHandle funcioneaz diferit n Windows pe 16 bii. n Win32, fiecare proces are propriul spaiu de adrese, asta nsemnnd c fiecare proces crede c este singurul proces care ruleaz n sistem. Un proces nu poate vedea uor un alt proces. Din acest motiv, nu este fcut nici o deosebire ntre valorile hinstExe i hmodExe ale unui proces, ele sunt i aceeai valoare. Pentru motive de compatibilitate, cei doi termeni continu s existe n documentaia Win32. Linia de comand a unui proces Atunci cnd este creat un nou proces, i este transmis o linie de comand. Aceasta nu este aproape niciodat goal; cel mai puin, numele fiierului executabil folosit pentru a crea noul proces este primul ir din linia de comand. Este important de reinut c parametrul lpszCmdLine pointeaz ntotdeauna ctre un ir de caractere ANSI. Microsoft a ales ANSI pentru a ajuta la portarea aplicaiilor din Windows pe 16 bii pe Win32, deoarece aplicaiile Windows pe 16 bii ateapt un ir ANSI. Putem de asemenea obine un pointer la linia complet de comand a procesului nostru apelnd funcia GetCommandLine : LPTSTR GetCommandLine (VOID); Aceast funcie returneaz un pointer la un buffer care conine linia de comand complet, incluznd calea complet la fiierul executabil. Probabil cel mai silit (compelling) motiv de a folosi GetCommandLine n loc de parametrul lpszCmdLine este c att versiunea ANSI, ct i cea UNICODE a funciei GetCommandLine exist n Win32 pe cnd parametrul lpszCmdLine ntotdeauna indic ctre un buffer care conine un ir de caractere ANSI. Variabilele de mediu ale unui proces Fiecare proces are un bloc de mediu asociat lui. Un astfel de bloc este un bloc de memorie alocat n interiorul spaiului de adrese al unui proces. Fiecare bloc conine un set de iruri cu urmtorul aspect : VarName1 = VarValue1\0 VarName2 = VarValue2\0 VarName3 = VarValue3\0 ..... VarNameX = VarValueX\0 \0 Prima parte a fiecrui ir este numele unei variabile de mediu. Acest nume este urmat de semnul egal, care este urmat de valoarea pe care vrem s i-o atribuim variabilei. Toate irurile din blocul de mediu trebuie s fie sortate alfabetic dup numele variabilelor de mediu. Deoarece semnul egal este folosit pentru a separa numele de valoare, simbolul egal nu poate s apar n nume. De asemenea, spaiile sunt semnificative. De exemplu, dac declarm aceste dou variabile : XZY= Win32 (Dup egal este un spaiu gol.) ABC=Win32 86

i apoi le comparm valorile, sistemul va declara c cele dou variabile sunt diferite. Acest lucru deoarece orice spaiu gol care apare imediat nainte sau dup egal este luat n considerare. De exemplu, dac ar trebui s adugm dou iruri la blocul de mediu, XYZ =Home XYZ=Work atunci varibila de mediu XYZ va conine Home iar cealalt variabil va conine Work. n final, un caracter 0 trebuie plasat la sfritul tuturor variabilelor de mediu pentru a marca sfritul blocului de mediu. Pentru a crea un set iniial de varibile de mediu pentru Windows 95, trebuie s modificm fiierul sistem AUTOEXEC.BAT prin plasarea unor linii SET. Fiecare linie treebuie s fie de forma : SET VarName=VarValue Atunci cnd repornim sistemul, coninutul fiierului este parsat i variabilele de mediu pe care le-am setat vor fi disponibile fiecrui proces pe care l pornim n sesiunea Windows. Atunci cnd un utilizator se logheaz n Windows NT, sistemul creeaz nucleul procesolui i i asociaz un set de iruri de mediu. Sistemul obine setul iniial de iruri de mediu examinnd dou intrri din registrul Windows. Prima cheie, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\Environment conine lista cu toate variabilele de mediu din sistem. Cea de-a doua, HKEY_CURRENT_USER\Environment, conine lista tuturor variabilelor de mediu care se aplic doar pentru utilizatorul logat n acel moment. Un utilizator poate aduga, terge sau schimba oricare din caeste intrri fcnd dublu clic pe opiunea System din Control Panel i apoi selectnd tabul Environment. Doar un utilizator cu drepturi de administrator pe acel sistem poate modifica variabilele din lista System Variables. Aplicaia noastr poate folosi diferite funcii din registrul Windows pentru a modifica aceste intrri. Totusi, pentru ca schimbrile s aib loc pentru toate aplicaiile, utilizatorul trebuie s se delogheze i apoi s se logheze din nou. Unele aplicaii, cum ar fi Explorer, Task Manager i Control Panel pot s i actualizeze blocul de mediu cu intrri noi de registru atunci cnd fereastra lor principal primete un mesaj WM_WININICHANGE. De exemplu, dac actualizm intrrile din registrul Windows i vrem ca anumite s-i actualizeze blocurile lor de mediu, putem apela : SendMessage (HWND_BROADCAST, WM_WININICHANGE, 0L, (LPARAM)"Environment"); n mod normal, un proces copil motenete un set de variabile de mediu care identice cu cele ale printelui. Totui, procesul printe poate controla ce varibile de mediu motenete copilul. Prin motenire nelegem c procesul copil primete copia proprie a bloculuim de meiu al printelui, nu c procesul copil i procesul printe mpart acelai bloc de mediu. Acest lucru nseamn c un proces copil poate aduga, terge sau modifica o variabil n blocul propriu de mediu iar schimbarea s nu fie reflectat n blocul de mediu al printelui. Dac dorim s folosim variabilele de mediu, Win32 ofer cteva funcii pe care le poate folosi aplicaia noastr. Funcia GetEnvironmentVariable ne permite s detectm prezena i valoarea unei variabile de mediu: DWORD GetEnvironmentVariable (LPCTSTR lpszName, LPTSTR lpszValue, DWORD cchValue); Atunci cnd apelm GetEnvironmentVariable, lpszName pointeaz la numele de variabil dorit, lpszValue pointeaz la bufferul care reine valoarea variabilei i cchValue indic mrimea acestui buffer n caractere. Funcia returneaz fie numrul de caractere copiate n buffer, fie 0 dac numele variabilei nu a fost gsit n mediu. Funcia SetEnvironmentVariable ne d posibilitatea s adugm o variabil, s tergem o variabil sau s modificm valoarea unei variabile : BOOL SetEnvironmentVariable (LPCTSTR lpszName, LPCTSTR lpszValue);

87

Aceast funcie seteaz variabila identificat de parametrul lpszName la valoarea identificat de parametrul lpszValue. Dac variabila cu numele specificat nu exist, variabila este adugat i dac valoarea variabilei lpszValue este NULL, atunci valoarea este tears din blocul de mediu. Modulul de eroare al unui proces Cu fiecare proces este asociat un set de indicatori care precizeaz sistemului cum trebuie s rspund procesul la erorile serioase. Erorile serioase reprezint erori de disc, excepii netratate, eecuri de gsire de fiiere i aranjare defectuoas a datelor. Un proces poate spune sistemului cum s trateze fiecare din aceste erori apelnd funcia SetErrorMode : UINT SetErrorMode (UINT fuErrorMode); Parametrul fuErrorMode este o combinaie ntre oricare dintre indicatorii din tabelul urmtor (bitwise ORed together): Indicator Descriere SEM_FAILCRITICALERRORS Sistemul nu afieaz csua de mesaj a identificatorului de eroare critic i returneaz eroarea la procesul apelant. SEM_NOGPFAULTERRORBOX Sistemul nu afieaz csua de mesaj cu generalprotection-fault. Acest indicator trebuie setat doar pentru depanarea aplicaiilor care trateaz ele nsele erori de tipul general-protection-fault GP cu un identificator de excepii. SEM_NOOPENFILEERRORBOX Sistemul nu afieaz o csu de mesaj atunci cnd nu gsete un fiier. SEM_NOALIGNMENTFAULTEXCEPT Sistemul repar automat erorile de aliniere a memoriei i le face invizibile pentru aplicaie. Acest indicator nu are nici un efect pe procesoarele x86 sau Alpha. n mod implicit, un proces copil motenete indicatorii de moduri de eroare ai printelui su. n alte cuvinte, dac un proces are n mod curent indicatorul SEM_NOGPFAULTERRORBOX setat i apoi creeaz un proces copil, acesta din urm va avea la rndul su acest indicator setat. Totui, procesul copil nu este anunat de acest lucru i este posibil s se nu se fi scris n indicatorul de erori GP. Dac apare o eroare GP n unul din firele procesului copil, aplicaia corespunztoare acestui proces s-ar putea s se termine fr a anuna utilizatorul. Un proces printe poate preveni ca un proces copil s moteneasc modul de erori al printelui preciznd indicatorul CREATE_DEFAULT_EROR_MODE atunci cnd apeleaz CreateProcess. Unitatea de disc curent i directorul curent ale unui proces Directorul curent al unitii de disc curente reprezint locaia unde diferite funcii Win32 caut fiiere i directoare atunci cnd nu sunt cile complete nu sunt furnizate. Sistemul pstreaz intern evidena unitii de disc i a directorului curent al unui proces. Deoarece aceast informaie este meninut pe un ntreg proces, un fir de execuie care schimb aceste valori le schimb pentru toate firele din proces. Un fir poate obine i poate seta unitatea de disc curent proprie i directorul curent propriu apelnd urmtoarele dou funcii : DWORD GetCurrentDirectory (DWORD cchCurDir, LPTSTR lpszCurDir); BOOL SetCurrentDirectory (LPCTSTR lpszCurDir); Procesul printe poate obine directoarele curente proprii prin apelul funciei GetFullPathName : DWORD GetFullPathName ( LPCTSTR lpszFile, DWORD cchPath, LPTSTR lpszPath, LPTSTR *ppszFilePart); 88

De exemplu, pentru a afla directorul curent pentru unitatea de disc C, am putea face urmtorul apel: TCHAR szCurDir [MAX_PATH]; DWORD GetFullPathName (__TEXT(C:), MAX_PATH, szCurDir, NULL); Trebuie s inem minte c variabilele de mediu ale unui proces trebuie ntotdeauna s fie pstrate n ordine alfabetic. Din cauza acestei necesiti, variabilele de mediu de litera de unitate de disc vor trebui s fie plasate la nceputul blocului de mediu Funcia CreateProcess Un proces este creat atunci cnd aplicaia noastr apeleaz funcia CreateProcess: BOOL CreateProcess( LPCTSTR lpszApplicationName, LPCTSTR lpszCommandLine, LPSECURITY_ATTRIBUTES lpsaProcess, LPSECURITY_ATTRIBUTES lpsaThread, BOOL fInherithandles, DWORD fdwCreate, LPVOID lpvEnvironment, LPTSTR lpszCurDir, LPSTARTUPINFO lpsiStartupInfo, LPPROCESS_INFORMATION lppiProcInfo);

Pentru descrierea parametrilor consultai MSDN. Oprirea execuiei unui proces Un proces se poate termina n trei feluri : 1. Un fir de execuie din proces apeleaz funcia ExitProcess. (Cel mai frecvent) 2. Un fir din alt proces apeleaz funcia TerminateProcess (Ar trebui evitat) 3. Toate firele din proces se opresc singure. (Foarte rar) Funcia ExitProcess Un proces se termin cnd unul din firele din proces apeleaz ExitProcess : VOID ExitProcess (UINT fuExitCode); Aceast funcie oprete execuia procesului i seteaz codul de ieire al procesului la fuExitCode. ExitProcess nu returneaz o valoare deoarece procesul s-a terminat. Dac includem orice cod dup apelul funciei ExitProcess, acest cod nu se va executa niciodat. Aceasta este metoda cea mai folosit pentru terminarea unui proces deoarece ExitProcess este apelat atunci cnd funcia WinMain returneaz n codul C run-time de pornire. Acest cod apeleaz ExitProcess, transmind valoarea returnat din WinMain. Orice alt fir de execuie care ruleaz n cadrul procesului i va termina la rndul lui execuia. Documentaia Win32 stipuleaz faptul c un proces nu se termin pn cnd toate firele i termin execuia. Din punctul de vedere al sistemului de operare, acest lucru este adevrat. Totui, codul C run-time impune o abordare diferit a aplicaiei; codul C run-time se asigur c procesul se termin atunci cnd firul principal de execuie returneaz din funcia WinMain, chiar i n cazul n care celelalte fire sunt n execuie, apelnd ExitProcess. Totui, dac apelezi ExitThread n funcia WinMain n loc s apeleze ExitProcess sau pur i simplu returnm, firul principal de execuie al aplicaiei i va nceta execuia, dar procesul nu se va termina dac mcar unul din celelalte fire din proces nc mai ruleaz. Funcia TerminateProcess Un apel al funciei TerminateProcess poate i el opri execuia unui proces : BOOL TerminateProcess (HANDLE hProcess,UINT fuExitCode); 89

Aceast funcie este diferit de ExitProcess n mod foarte important : orice fir poate apela TerminateProcess pentru a termina execuia unui alt proces sau a propriului proces. Parametrul hProcess reprezint identificatorul procesului care trebuie s fie terminat. Atunci cnd procesul se termin, propriul su cod de ieire devine valoarea pe care am transmis-o ca fiind parametrul fuExitCode. Folosirea funciei TerminateProcess este descurajat; este de preferat s o folosim doar dac nu putem s oprim execuia unui proces folosind alte metode. n mod normal, la terminarea execuiei unui proces, sistemul anun orice DLL ataat procesului faptul c procesul i ncheie execuia. Dac apelm TerminateProcess, totui, sistemul nu anun nici un DLL ataat procesului, lucru care face ca procesul s nu se termine corect. De exemplu, putem scrie un DLL pentru a goli datele ntr-un fiier pe disc n momentul n care procesul se detaeaz de DLL. Aceast detaare are loc atunci cnd o aplicaie descarc DLL-ul apelnd FreeLibrary. Deoarece DLL-ul nu este notificat de aceast detaare, el nu poate s i desfoare activitatea. Sistemul anun DLL-ul atunci cnd un proces se termin normal sau cnd apelm ExitProcess. Dei este posibil ca DLL-ul s nu poat s i termine aciunea, sistemul garanteaz faptul c toat memoria alocat este eliberat, toate fiierele deschise vor fi nchise, toate obiectele nucleu vor avea contorul de utilizare decrementat i toate celelalte obiecte utilizator sau GDI vor fi eliberate indiferent de modul de terminare al procesului. Ce se ntmpl atunci cnd firele din proces i ncheie execuia Dac toate firele din proces i ncheie execuia (n urma fie a unor apeluri ExitThread, fie TerminateThread), sistemul de operare presupune c nu mai este nici un motiv pentru a mai pstra spaiul de adrese al procesului. Aceasta este o presupunere corect deoarece nu mai exist nici un fir n execuie n spaiul de adrese al procesului. Atunci cnd sistemul detecteaz c firele nu mai ruleaz, el oprete procesul. Atunci cnd se ntmpl acest lucru, codul de ieire al procesului este setat la codul de ieire al ultimului fir de execuie care s-a terminat. Ce se ntmpl la terminarea unui proces La terminarea unui proces au loc urmtoarele aciuni : 1. Orice alte fire din proces sunt oprite. 2. Toate obiectele utilizator sau GDI alocate de ctre proces sunt eliberate iar obiectele nucleu sunt nchise. 3. Starea procesului obiect nucleu devine semnalat. Alte fire de execuie din sistem pot s se suspende pn la terminarea procesului. 4. Codul de ieire al procesului se schimb de la STIL_ACTIVE la codul transmis de ctre ExitProcess sau TerminateProcess. 5. Contorul de utilizare al procesului obiect de nucleu este decrementat cu 1. La terminarea unui proces, procesul obiect nucleu asociat lui nu este eliberat pn cnd toate referinele existente la acel obiect nu sunt nchise. De asemenea, oprirea unui proces nu face ca procesele sale copil s se opreasc i ele. Atunci cnd procesul se termin, codul procesului i orice alte resurse alocate de ctre proces sunt scoase din memorie. Totui, memoria privat alocat de sistem procesului obiect nucleu nu este eliberat pn cnd contorul de utilizare nu atinge valoarea 0.Acest lucru se poate ntmpla numai dac toate celelalte procese care au creat sau au deschis identificatori ctre procesul care este acum terminat anun sistemul c nu mai au nevoie s referenieze procesul. Aceste procese anun sistemul apelnd CloseHandle. Dup ce un proces nu mai ruleaz, procesul printe nu poate face mare lucru cu identificatorul procesului. Totui, el poate apela GetExitCodeProcess pentru a verifica dac procesul identificat prin hProcess s-a terminat i dac da, s i verifice codul de ieire. BOOL GetExitCodeProcess (HANDLE hProcess, LPWORD lpwdExitCode); Codul de ieire este returnat n DWORD-ul lpwdExitCode. Dac procesul nu s-a terminat atunci cnd apelm GetExitCode, funcia seteaz acest parametru la valoarea STILL_ACTIVE (definit ca 0x103). Dac funcia a fost apelat cu succes, este returnat TRUE.

90

Fire de execuie
n acest capitol vom discuta conceptul de fir de execuie i vom descrie modul n care sistemul folosete fire de execuie pentru a executa codul aplicaiei noastre. La fel ca i procesele, firele au propriti asociate lor i vom discuta despre unele dintre funciile disponibile pentru interogarea i schimbarea acestor proprieti. De asemenea vom examina funciile care ne permit s crem noi fire in sistem. n final vom vorbi despre terminarea frelor de execuie. Cnd crem un fir de execuie Un fir de execuie descrie o cale de execuie n interiorul unui proces. De fiecare dat cnd un proces este iniializat, sistemul creeaz un fir de execuie principal. Acest fir pornete codul C run-time de pornire, care la rndul lui apeleaz funcia noastr WinMain, apoi continu s se execute pn cnd funcia WinMain returneaz i codul C run-time de pornire apeleaz ExitProcess. Pentru multe aplicaii, firul principal de execuie este singurul fir de execuie de care are nevoie aplicaia. Totui, procesele pot crea fire suplimentare pentru a le ajuta s i ndeplineasc sarcinile. Ideea din spatele crerii firelor de execuie este de a utiliza ct mai mult timp de procesor. De exemplu, un program care lucreaz cu foi de calcul are nevoie s efectueze recalculri pe msur ce utilizatorul introduce date n celule. Deoarece recalculrile unei foi de calcul complexe pot avea de nevoie de cteva secunde pentru a fi efectuate, o aplicaie bine gndit nu ar trebui s recalculeze foaia de calcul dup fiecare schimbare pe care o efectueaz utilizatorul. n schimb, recalcularea ar trebui fcut ntr-un fir de execuie cu prioritate mai redus dect firul principal de execuie. n acest mod, dac utilizatorul introduce date, firul principal ruleaz, lucru care nseamn c sistemul nu va programa nici un timp de calcul firului care efectueaz recalcularea. Atunci cnd utilizatorul se oprete din scris, firul principal este suspendat, ateptnd o intrare iar firului care face recalcularea i este dat timp de lucru. De ndat ce utilizatorul ncepe s scrie din nou, firul principal de execuie, avnd prioritate mai mare, trece in faa firului cu recalcularea. Crearea firelor de execuie adiionale face ca aplicaia s devin nelegtoare cu utilizatorul. De asemenea este destul de uor de implementat. Cnd nu trebuie s creem un fir de execuie Firele sunt incredibil de folositoare i i au locul lor, dar atunci cnd folosim fire de execuie putem crea noi poteniale probleme n timp ce ncercm s rezolvm pe cele vechi. De exemplu, s spunem c dezvoltm a aplicaie care proceseaz cuvinte si vrem s permitem ca funcia de tiprire s ruleze ca propriul fir de execuie. Acest lucru pare o idee bun deoarece utilizatorul poate imediat s se ntoarc la editarea documentului n timp ce acesta este tiprit. Dar totui acest lucru nseamn c datele din document pot fi schimbate n timp ce documentul este tiprit. Acest lucru creeaz un nou tip de problem pe care trebuie s o rezolvm. Totui poate ar fi mai bine s nu lsm tiprirea n propriul fir de execuie; dar aceast soluie este puin mai drastic. Ce ar fi dac am permite utilizatorului s editeze un alt document dar s blocm documentul care trebuie tiprit astfel nct s nu poat fi modificat pn cnd procesul de tiprire nu s-a terminat ? Cea de-a treia soluie este : copiem fiierul de tiprit ntrun fiier temporar i lsm utilizatorul s l modifice pe cel original. Atunci cnd fiierul temporar care conine documentul s-a terminat de tiprit, putem terge fiierul temporar. Dup cum se observ, firele de execuie rezolv unele probleme cu riscul crerii altora noi. O alt utilizare greit a firelor de execuie poate aprea n dezvoltarea interfeei utilizator a unei aplicaii. n majoritatea aplicaiilor, toate componentele interfeei utilizator ar trebui s mpart acelai fir de execuie. Dac crem o caset de dialog, de exemplu, nu ar avea nici un sens ca un listbox s fie creat de un fir de execuie i un buton de alt fir. Firele de execuie n MFC MFC ncapsuleaz fire de execuie n clasa CWinThread. De asemenea ncapsuleaz evenimente, mutex-uri si alte obiecte de sincronizare Win32 in clase C++ uor de folosit. Din punctul de vedere al Windows-ului toate firele de execuie sunt la fel. Totui, MFC-ul distinge dou tipuri de fire de execuie : fire de execuie cu interfa cu utilizatorul i fire de execuie de lucru. Diferena dintre cele dou este ca primele pot crea ferestre si pot procesa mesaje trimise ctre aceste ferestre. Cel de-al doilea tip de fire de execuie efectueaz operaii n fundal care nu primesc o intrare direct de la utilizator i de aceea nu au nevoie de ferestre si de cozi de mesaje. Firele de execuie de lucru sunt ideale pentru efectuarea unor operaii izolate care pot fi desprite de restul aplicaiei si pot fi efectuate n fundal. Un exemplu clasic de fir de execuie de lucru este cel folosit de un control de animaie pentru a rula clipuri cu extensia .AVI. Acel fir de execuie face mai mult dect s deseneze un 91

cadru, se pune in starea de sleep pentru o fraciune de secund iar apoi se trezete i repet procesul. n acest mod el adaug doar puin la ncrctura de lucru a procesorului deoarece i petrece majoritatea vieii suspendat ntre cadre i totui asigur un serviciu de valoare. Acesta este un exemplu foarte bun de proiectare cu multiple fire de execuie deoarece firul de execuie din fundal primete de fcut o anumit operaie i apoi i este permis s repete acea operaie pn cnd firul principal de execuie anun c operaia se poate termina. Funcia de fir de execuie n SDK Toate firele de execuie i ncep execuia la o funcie care trebuie s o precizm n prealabil. Funcia trebuie s aib urmtorul prototip : DWORD WINAPI YourThreadFunc (LPVOID lpvThreadParm); La fel ca i WinMain, funcia nu este apelat chiar de sistemul de operare. n schimb, acesta apeleaz o funcie intern, care nu face parte din funciile C run-time, coninut n KERNEl32. Putem s o numim StartOfThread; numele real nu este important. Aceast funcie arat astfel : void StartOfThread ( LPTHREAD_START_ROUTINE lpStartAddr, LPVOID lpvThreadParm) { try { DWORD dwThreadExitCode = lpStartAddr (lpvThreadParm); ExitThread (dwThreadExitCode); } __except (UnhandledExceptionFilter (GetExceptionInformation())) { ExitProcess (GetExceptionCode()); } } Funcia StartOfThread pune n micare urmtoarele aciuni : 1. Este setat un frame structurat de tratare a erorilor n jurul funciei noastre a firului de execuie astfel nct orice excepie care apare n timpul execuiei firului va avea o tratare implicit din partea sistemului. 2. sistemul apeleaz funcia noastr a firului de execuie, transmindu-i parametrul pe 32 bii lpvThreadParm pe care l-am transmis funciei CreateThread. 3. Atunci cnd funcia firului returneaz, funcia StartOfThread apeleaz ExitThread, transmindu-i valoarea de retur a funciei firului. Contorul de utilizare al obiectului nucleu este decrementat iar firul i ntrerupe execuia. 4. Dac firul determin apariia unei excepii care nu este tratat, atunci frame-ul SEH setat de funcia StartOfThread va trata excepia. n mod normal, acest lucru nseamn c o csu de dialog este prezentat utilizatorului iar atunci cnd acesta nchide aceast csu, funcia StartOfThread apeleaz ExitProcess pentru a termina execuia ntregului proces, nu doar firul n cauz. Firul principal de execuie al unui proces ncepe executnd funcia StartOfThread a sistemului. Aceasta apeleaz la rndul ei codul C run-time de startup, care apeleaz funcia noastr WinMain. Codul C run-time de startup, totui, nu se ntoarce n funcia StartOfThread deoarece codul de startup apeleaz n mod explicit ExitProcess. Funcia firului de execuie n MFC O funcie de fir de execuie este o funcie cu apel invers, aa c ea trebuie s fie o funcie static membr a unei clase sau o funcie global declarat n afara unei clase. Prototipul ei este : UINT ThreadFunc (LPVOID pParam); pParam este o valoare pe 32 de bii a crei valoare este egal cu parametrul pParam transmis funciei AfxBeginThread. Foarte des, pParam este adresa unei structuri de date definit de aplicaie care conine informaia transmis firului de execuie de lucru de ctre firul care l-a creat. De asemenea poate fi o valoare scalar, un handle sau chiar un pointer ctre un obiect. Este posibil s folosim aceeai funcie de fir de execuie pentru dou sau mai multe fire de execuie dar trebuie s fim ateni la problemele de reintrare cauzate de variabilele globale i statice. 92

Atta timp ct variabilele (i obiectele) pe care le folosete un fir de execuie sunt create n stiv, problemele de reintrare nu mai apar deoarece fiecare fir de execuie are stiva sa proprie. Crearea unul fir de execuie de lucru n MFC Cea mai bun modalitate de a lansa un fir de execuie ntr-o aplicaie MFC este apelul AfxBeginThread. MFC definete 2 versiuni diferite de AfxBeginThread : una care pornete un fir de execuie cu interfa cu utilizatorul i alta care pornete un fir de execuie de lucru. Codul surs pentru ambele este gsit n Thrdcore.cpp. Nu trebuie s folosim funcia Win32::CreateThread pentru a crea un fir de execuie ntr-un program MFC dect doar dac firul nu folosete MFC. AfxBeginThread nu este nici pe departe un wrapper pentru funcia CreateThread ; n plus fa de lansarea unui nou fir de execuie, ea iniializeaz informaia intern de stare folosit de cadrul de lucru, efectueaz verificri la diferite momente din timpul procesului de creare i se asigur c funciile din biblioteca run-time C sunt accesate ntr-o modalitate sigura din punctul de vedere al firelor de execuie. AfxBeginThread faciliteaz crearea unui fir de execuie de lucru. Atunci cnd este apelat, ea creeaz un nou obiect de tip CWinThread, lanseaz un fir de execuie, l ataeaz obiectului CWinThread i returneaz un pointer CWinThread. Declaraia CWinThread* pThread = AfxBeginThread (ThreadFunc, &threadInfo); pornete un fir de execuie de lucru i i transmite structura de date predefinit de aplicaie (&ThreadInfo) care conine intrarea ctre firul de execuie. ThreadFunc este funcia de fir de execuie funcia care este executat atunci cnd firul de execuie ncepe s se execute. O funcie de fir de execuie foarte simpl care se nvrte ntr-o bucl care mnnc din procesor i apoi se termin arat n felul urmtor : UINT ThreadFunc (LPVOID pParam) { UINT nlterations = (UINT) pParam; for(UINT i=0; i<nlterations; i++); return 0; } n acest exemplu, valoarea transmis n pParam nu este un pointer, ci un UINT normal. Funciile de fir de execuie sunt descrise mai n detaliu n urmtoarea seciune. Funcia AfxBeginThread accept patru parametri opionali care specific prioritatea firului, mrimea stivei, indicatorii de creere i atributele de securitate. Prototipul complet al funciei este: CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES 1pSecurityAttrs = NULL) nPriority definete prioritatea firului de execuie. Fire de execuie cu prioritate mare sunt programate pentru procesor n faa celor cu prioritate mai sczut, dar n practic, chiar i fire de execuie cu prioritate foarte sczut de obicei au tot timpul de procesor de care au nevoie. nPriority nu reprezint un nivel absolut de prioritate. El precizeaz un nivel de prioritate relativ fa de nivelul de prioritate al procesului caruia i aparine firul. Implicit prioritatea este THREAD_PRIORITY_NORMAL, care atribuie firului de execuie aceeai prioritate ca i a procesului care l deine. Nivelul de prioritate se poate schimba n orice moment cu comanda CWinThread::SetThreadPriority. Parametrul nStackSize transmis ctre AfxBeginThread precizeaz mrimea maxim a stivei a firului. In mediul Win32, fiecare fir de execuie primete stiva sa proprie. Valoarea 0 implicit a variabilei nStackSize permite stivei s ating mrimea maxim de 1MB. Aceasta nu nseamn ca fiecare fir de execuie are nevoie de minim 1 MB de memorie; nseamn ca fiecrui fir de execuie i se aloc 1 MB din spaiul de adrese din spaiul de 4GB n care se execut aplicaiile Windows pe 32 de bii. Memoria nu este alocat spaiului de adrese al stivei pn cnd nu este necesar, aa c majoritatea stivelor nu folosesc niciodat mai mult de civa KB din memoria fizic. Preciznd o limit pentru mrimea stivei permite sistemului de operare s prind funciile care se apeleaz 93

recursiv la infinit i care eventual consum stiva. Limita standard de 1 MB este suficient pentru aproape toate aplicaiile. dwCreateFlags poate fi doar una din dou valori. Valoarea implicit 0 precizeaz sistemului s execute firul imediat. Dac este specificat n schimb CREATE_SUSPENDED, firul pornete n starea suspendat i nu i ncepe execuia pn cnd alt fir de execuie (de obicei firul care l-a creat ) apeleaz CWinThread::ResumeThread asupra firului suspendat, ca mai jos : CWinThread* pThread = AfxBeginThread (ThreadFunc, SthreadInfo, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pThread->ResumeThread(); //Pornete firul de execuie. Uneori este util s crem un fir i s i amnm execuia pn mai trziu. Indicatorul CREATE_SUSPENDED este mecanismul prin care putem declara o execuie cu ntrziere. Ultimul parametru din lista de argumente a funciei AfxBeginThread, IpSecurityAttrs, este un pointer ctre o structur SECURITY_ATTRIBUTES care conine atributele de securitate ale firului de execuie nou creat i care de asemenea precizeaz sistemului dac procesele copil ar trebui s moteneasc handler-ul firului de execuie. Valoarea implicit NULL atribuie noului fir de execuie aceleai proprieti ca i a firului care l-a creat. Crearea unui fir de execuie cu interfa cu utilizatorul n MFC Crearea unui astfel de fir de execuie este diferit de procesul de creare a unui fir de execuie de lucru. Un fir de lucru este definit de funcia sa iar comportarea unui fir de execuie cu interfa este dat de o clas care se poate crea dinamic i care este derivat din CWinThread, asemntor unei clase de aplicaie derivat din CWinApp. Clasa de mai jos creeaz o fereastr de cadru de tip top-level care se nchide singur cnd este apsat butonul stnga al mouse-ului. nchiderea ferestrei oprete i firul de execuie deoarece funcia CWnd::OnNcDestroy trimite un mesaj WM_QUIT ctre coada de mesaje a firului de execuie. Trimiterea acestui mesaj WM_QUIT ctre un fir de execuie principal oprete firul de execuie i termin aplicaia. // Clasa CUIThread class CUIThread : public CWinThread DECLARE_DYNCREATE (CUIThread) public: virtual BOOL Initlnstance (); IMPLEMENT_DYNCREATE (CUIThread, CWinThread) BOOL CUIThread::Initlnstance () { m_pMainWnd = new CMainWindow m_pMainWnd->ShowWindow (SW_SHOW); m_pMainWnd->UpdateWindow (); return TRUE; } // Clasa CMainWindow class CMainWindow : public CFrameWnd { public: CMainWindow (); protected: afx_msg void OnLButtonDown (UINT, CPoint); // Se declar harta de mesaje DECLARE_MESSAGE_MAP () }; BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd) ON_WM_LBUTTONDOWN () 94

END_MESSAGE_MAP () CMainWindow::CMainWindow () { Create (NULL, _T ("UI Thread Window")); } void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point) { PostMessage (WM_CLOSE, 0, 0); } Trebuie remarcat parametrul SW_SHOW transmis ctre ShowWindow n locul parametrului normal m_nCmdShow. Acesta este un membru al CWinApp, aa c atunci cnd crem o fereastr top-level de la un fir de execuie cu interfa este de datoria noastr s precizm starea iniial a ferestrei. Putem lansa un CUIThread apelnd o form a funciei AfxBeginThread care accept un pointer CRuntimeClass ctre clasa firului de execuie : CWinThread* pThread = AfxBeginThread(RUNTIME_CLASS (CUIThread)); Versiunea funciei AfxBeginThread pentru fire de execuie cu interfa accept aceiai patru parametri opionali ca i versiunea pentru firele de lucru, dar nu accept o valoare pParam. Odat pornit, un fir de execuie cu interfa ruleaz asincron respectnd firul care l-a creat. Suspendarea i repornirea firelor de execuie Un fir de execuie n MFC care ruleaz poate fi suspendat prin apelul CWinThread::SuspendThread i poate si repornit cu CWinThread::ResumeThread. Un fir de execuie poate apela funcia SuspendThread pe el nsui sau un alt fir poate face acest apel pentru el. Totui, un fir suspendat nu poate apela ResumeThread pentru a se reporni; altcineva trebuie s fac acest apel. Un fir de execuie suspendat nu consum aproape deloc din timpul procesorului i impune n sistem o suprasarcin practic nul. Pentru fiecare fir de execuie Windows-ul pstreaz un contor de suspendare care este incrementat de SuspendThread i decrementat de ResumeThread. Un fir de execuie este programat pentru timpul procesorului doar atunci cnd contorul su de suspendare este 0. Dac funcia SuspendThread este apelat de dou ori succesiv, ResumeThread trebuie apelat de asemenea de dou ori. Un fir de execuie creat fr atributul CREATE_SUSPENDED are iniial contorul de suspendare egal cu 0. Un fir creat cu atributul CREATE_SUSPENDED se pornete avnd iniial contorul de suspendare iniializat cu valoarea 1. Att SuspendThread ct i ResumeThread returneaz contorul de suspendare anterior, aa c pentru a fi siguri c un fir de execuie va fi repornit indiferent de cat este de mare valoarea contorului de suspendare trebuie s apelm ResumeThread n mod repetat pn cnd returneaz valoarea 1. ResumeThread returneaz 0 dac firul nu este apelat sau dac nu este suspendat. n viaa real, o aplicaie trebuie s fie atent atunci cnd apeleaz SuspendThread deoarece nu avem nici o idee despre ce poate acesta s fac atunci cnd ncercm s i reluam execuia. Dac firul ncearc s aloce memorie dintr-un heap, de exemplu, firul va trebui s se blocheze pe heap. Pe msur ce alte fire ncearc s acceseze heap-ul, execuia lor va fi oprit pn cnd primul fir i reia execuia. SuspendThread este neprimejdios doar atunci cnd tim ce este exact firul int (sau ce face) i lum masuri extreme pentru a evita problemele sau blocajele cauzate de suspendarea firului. n SDK, un fir de execuie poate fi suspendat prin apelul funciei SuspendThread i apoi i putem relua execuia cu ajutorul funciei ResumeThread. Ambele funcii sunt asemntoare cu funciile similare din MFC. Punerea unui fir de execuie n starea de sleep Un fir de execuie poate s fie suspendat pentru o anumit perioad de timp prin apelul funciei API ::Sleep. Un fir suspendat nu consum din timpul procesorului. Declaraia ::Sleep (10000) suspend firul de execuie curent pentru 10 secunde. O utilizare pentru ::Sleep este pentru a implementa fire de execuie ale cror aciuni sunt nnscut bazate pe timp, ca de exemplu firul de execuie din fundalul unui control de animaie sau un fir care muta acele unui ceas. ::Sleep poate fi folosit i pentru a elibera resturile unor alte perioade de timp ale firului de execuie. 95

Declaraia ::Sleep(0) suspend firul curent i permite planificatorului s ruleze alte fire de aceeai prioritate sau de prioritate mai ridicat. Dac nici unul din astfel de fire nu ateapt execuia, apelul funciei returneaz imediat i firul curent i continua execuia. n Windows NT 4.0 sau mai sus se poate comuta pe alt fir de execuie prin apelul funciei SwitchToThread. Vom folosi ::Sleep(0) dac codul pe care l scriem trebuie s mearg pe toate platformele Win32. Dac scriem o aplicaie care folosete mai multe fire de execuie pentru a desena pe ecran, cteva declaraii ::Sleep(0) plasate strategic pot face adevrate minuni pentru calitatea ieirii. S presupunem c realizm o animaie cu micarea a patru obiecte i atribuim fiecrui obiect firul su propriu de execuie. Fiecare fir este responsabil cu micarea unui obiect pe ecran. Dac doar rulm fiecare fir ntr-o bucl i i dm posibilitatea s acapareze tot timpul de procesor pe care l poate lua, este foarte posibil ca micarea obiectelor s fie neregulat. Dar dac lsm fiecare fir s i mute obiectul civa pixeli i apoi apelm ::Sleep(0), animaia va fi mult mai lin. Valoarea pe care o transmitem funciei ::Sleep nu garanteaz c firul va fi repornit exact la momentul terminrii intervalului de timp. Transmiterea ctre ::Sleep a valorii 10000 ne garanteaz c firul va fi repornit dup cel puin 10 secunde. Firul va putea s rmn suspendat 10 secunde, sau chiar 20, acest lucru fiind n funcie de sistemul de operare. n practic, firul de obicei va fi repornit cu o fraciune de secund dup terminarea intervalului de timp, dar nu exist nici o garanie. La momentul actual nu exist n nici o versiune de Windows o metod de a suspenda un fir de execuie pentru o perioad precis de timp. Schimbarea ctre un alt fir de execuie Sistemul ofer o funcie numit SwitchToThread care permite rularea unui alt fir de execuie dac exist : BOOL SwitchToThread (); Atunci cnd apelm aceast funcie, sistemul verific dac exist un fir care este privat de timp de procesor. Dac nu este gsit nici un astfel de fir, funcia SwitchToThread returneaz imediat. Dac exist un astfel de fir, funcia planific firul (care este posibil s aib o prioritate mai sczut dect firul care a apelat funcia SwitchToThread). Acestui fir i este permis s ruleze pentru o cuant de timp iar dup aceea planificatorul funcioneaz normal. Aceast funcie permite unui fir de execuie care dorete accesul la o resurs s foreze un fir cu o prioritate mai mic care deine acea resurs s elibereze resursa. Dac nici un fir nu poate fi executat dup apelul SwitchToThread, funcia returneaz FALSE; n caz contrar, ea returneaz o valoare nenul. Apelul SwitchToThread este similar cu apelul funciei Sleep cu parametrul avnd valoarea 0. Diferena este c SwitchToThread permite execuia firelor de execuie cu prioritate mai mic. Sleep replanific firul apelant chiar dac exist fire cu prioritate mai redus care sunt private de timp de procesor. Windows 98 nu are o implementare folositoare pentru aceast funcie. Terminarea unui fir de execuie n SDK Asemenea unui proces, un fir poate fi oprit n trei feluri : 1. Firul se oprete singur apelnd funcia ExitThread. (cea mai ntlnit metod) 2. Un fir din acelai proces sau din alt proces apeleaz TerminateThread. (de evitat) 3. Procesul care conine firul se oprete. Funcia ExitThread Un fir se oprete atunci cnd apeleaz ExitThread : VOID ExitThread (UINT fuExitCode); Aceast funcie oprete firul i seteaz codul de ieire al firului la fuExitCode. Funcia ExitThread nu returneaz o valoare deoarece firul nu s-a oprit. Aceast metod este cea mai ntlnit deoarece ExitThread este apelat atunci cnd funcia de fir returneaz ctre funcia intern a sistemului StartOfThread. Aceasta apeleaz la rndul ei ExitThread, transmindu-i valoarea returnat din funcia noastr de fir. Funcia TerminateThread Un apel TerminateThread termin de asemenea firul : 96

BOOL TerminateThread (HANDLE hThread, DWORD dwExitCode); Funcia seteaz firul identificat de parametrul hThread i seteaz codul de ieire la dwExitCode. Funcia TerminateThread exist astfel nct s putem s oprim un fir atunci cnd nu mai rspunde. Este recomandat s o folosim doar ca ultim mijloc. Atunci cnd un fir moare prin apelul ExitThread, stiva firului este distrus. Totui, dac firul este oprit prin TerminateThread, sistemul nu distruge stiva pn cnd procesul care este proprietarul firului se termin, deoarece alte fire de execuie s-ar putea s foloseasc pointeri care refereniaz date coninute pe stiva firului care sa oprit. Dac aceste alte fire ncearc s acceseze stiva, apare o violare de acces. Atunci cnd firul se termin, sistemul anun orice DLL-uri ataate procesului proprietar al firului c firul se oprete. Dac apelm ns TerminateThread, sistemul nu anun nici un DLL ataat procesului, adic procesul nu va fi nchis corect. De exemplu, un DLL poate fi scris pentru a goli un buffer ntr-un fiier pe disc atunci cnd firul se detaeaz de DLL. Deoarece DLL-ul nu este anunat de sistem de detaare atunci cnd folosim TerminateThread, DLL-ul nu poate efectua operaia sa normal de curire. Oprirea unui fir de execuie n MFC Odat ce un fir de execuie este pornit, el poate fi oprit n dou moduri. Un fir de execuie de lucru se oprete atunci cnd funcia de fir execut o declaraie return sau cnd orice alt funcie oriunde n fir apeleaz AfxEndThread. Un fir de execuie cu interfa este oprit atunci cnd este trimis un mesaj WM_QUIT ctre coada de mesaje sau cnd firul nsui apeleaz AfxEndThread. Un fir de execuie poate aduga un mesaj WM_QUIT la el nsui cu funcia API ::PostQuitMessage. AfxEndThread, ::PostQuitMessage i return accept toate un cod de ieire pe 32 de bii care poate fi extras cu ::GetExitCodeThread dup oprirea firului de execuie. Urmtoarea declaraie copie codul de ieire al firului indicat de pThread n dwExitCode: DWORD dwExitCode; ::GetExitCodeThread (pThread->m_hThread, &dwExitCode);

Dac funcia ::GetExitCodeThread este apelat pentru un fir care este n execuie , atunci atribuie variabilei dwExitCodeThread valoarea STILL_ACTIVE(0x103). n acest exemplu, identificatorul firului transmis ctre funcia ::GetExitCodeThread este extras din data membru m_hThread a obiectului CWinThread care ncapsuleaz firul de execuie. Ori de cte ori avem un CWinThread i vrem s apelm o funcie API care are nevoie de un handle de fir, putem obine acest handle din m_hThread. Ce se ntmpl atunci cnd procesul se oprete Funciile ExitProcess sau TerminateProcess discutate anterior termin de asemenea execuia firelor. Diferena este c aceste funcii opresc toate firele coninute n procesul care se termin. Ce se ntmpl la oprirea unui fir de execuie Urmtoarele aciuni au loc la oprirea unui fir de execuie : 1. Toi identificatorii obiectelor utilizator aparinnd firului sunt eliberai. n Win32, majoritatea obiectelor sunt proprietatea procesului care conine firul care a creat obiectele. Totui, dou obiecte utilizator pot fi proprietatea unui fir de execuie : ferestre i legturi. Atunci cnd firul care a creat aceste obiecte i termin execuia, sistemul distruge n mod automat obiectele. Celelalte obiecte sunt distruse doar atunci cnd procesul proprietar se termin. 2. Starea obiectului nucleu fir de execuie devine semnalat. 3. Codul de ieire al firului se schimb de la STILL_ACTIVE la codul de terminare al firului. 4. Dac firul este ultimul fir activ din proces, procesul se oprete. 5. Contorul de utilizare al obiectului nucleu fir este decrementat cu 1. Atunci cnd firul se termin, obiectul nucleu fir asociat lui nu devine automat liber pn cnd toate referinele rmase la obiect nu sunt nchise. De ndat de un fir nu mai ruleaz, orice alt fir din sistem nu mai are ce face cu identificatorul primului gir. Totui, aceste alte fire pot apela GetExitCodeThread pentru a verifica dac firul identificat prin hThread s-a terminat i, n caz afirmativ, s i determine codul su de ieire. BOOL GetExitCodeThread (HANDLE hThread, LPDWORD lpdwExitCode); 97

Valoarea de ieire este returnat n DWORD-ul la care pointeaz lpdwExitCode. Dac firul nu este terminat atunci cnd este apelat GetExitCodeThread, funcia scrie n DWORD identificatorul STILL_ACTIVE ( definit ca 0x103). Dac funcia reuete este returnat TRUE. Fire de execuie i sincronizarea firelor de execuie n mediul Microsoft Win32, fiecare aplicaie rulat reprezint un proces i fiecare proces conine unul sau mai multe fire de execuie. Un fir de execuie este o cale de execuie prin codul unui program i n plus un set de resurse (stive, registre de stare i aa mai departe) atribuite de ctre sistemul de operare. Multiplicarea firelor de execuie nu este la ndemna oricui. Aplicaiile cu mai multe fire de execuie sunt dificil de scris i de depanat din cauz c paralelismul firelor de execuie concurente adaug un nivel suplimentar de complexitate codului programului. Folosite corect ins, firele de execuie multiple pot s mbunteasc dramatic timpul de rspuns al unei aplicaii. Un procesor de cuvinte care face verificarea de sintax ntr-un fir de execuie dedicat, de exemplu, poate continua s proceseze mesajele n firul principal de execuie i i d posibilitatea utilizatorului s continue s lucreze n timp ce verificarea de sintax i continua rularea. Ce face mai dificil s scriem un procesor de text cu mai multe fire de execuie este faptul c firul de execuie care efectueaz verificarea de sintax va trebui in mod inevitabil s i sincronizeze aciunile cu alte fire de execuie din cadrul aplicaiei. Majoritatea programatorilor au fost condiionai s gndeasc n termeni sincroni la codul lor : funcia A apeleaz funcia B, funcia B efectueaz o operaie i returneaz n A i aa mai departe. Dar firele de execuie sunt asincrone prin natura lor. ntr-o aplicaie cu mai multe fire de execuie, trebuie s ne gndim la ce se ntmpl dac, de exemplu, dou fire de execuie apeleaz funcia B n acelai moment sau un fir de execuie citete o variabil n timp ce cellalt o scrie. Dac funcia A lanseaz funcia B ntr-un fir de execuie separat, trebuie s anticipm de asemenea problemele care ar putea s apar dac funcia A continu s ruleze in timpul execuiei funciei B. De exemplu, este uzual s transmitem adresa unei variabile creat n stiv n funcia A ctre funcia B pentru procesare. Dar funcia B este ntr-un alt fir de execuie, variabila s-ar putea s nu mai existe atunci cnd funcia B ajunge s o proceseze. Chiar i cel mai inofensiv cod la prima vedere poate fi corupt n mod fatal atunci cnd implic dou fire de execuie diferite. Fire de execuie, procese i prioriti Planificatorul este acea component a sistemului de operare care decide care fire de execuie ruleaz, cnd i pentru ct timp. Planificarea firelor de execuie este o sarcin complex al crei obiectiv este de a mpri timpul procesorului ntre firele de execuie ct mai eficient cu putin pentru a crea iluzia c toate ruleaz n acelai timp. Pe mainile cu mai multe procesoare, Windows NT i Windows 2000 ruleaz n realitate dou sau mai multe fire de execuie n acelai timp distribuind firele ntre procesoare folosind o schem numit procesare multipl simetric, sau SMP. Windows 95 i Windows 98 nu sunt sisteme de operare SMP, astfel nct ele distribuie toate firele aceluiai procesor chiar i pe calculatoarele cu mai multe procesoare. Planificatorul folosete o varietate de tehnici pentru a mbunti performana procesrii multiple i pentru a ncerca s asigure faptul c fiecare fir de execuie primete suficient timp de procesor. n cele din urm, totui, decizia care fir s se execute mai nti este dat de prioritatea firelor. La orice moment dat, fiecare fir are atribuit un nivel de prioritate de la 0 la 31, cu un numr mai mare indicnd o prioritate mai mare. Dac un fir cu prioritatea 11 ateapt execuia i toate celelalte fire care ateapt execuia au prioriti mai mici dect 11, firul cu prioritatea 11 ruleaz primul. Dac dou fire au aceeai prioritate, planificatorul l execut pe cel care a fost executat mai de mult timp. Atunci cnd perioada de timp, sau cuantum, acordat firului expir, cellalt fir cu prioritate egal cu primul este executat dac toate celelalte fire active au prioriti mai mici. Ca o regul general, planificatorul ntotdeauna acord urmtoarea perioad de timp firului n ateptare cu cea mai mare perioad. Acest lucru nseamn c firele cu prioritate sczut nu sunt executate niciodat ? Nici gnd. n primul rnd, s ne aducem aminte c Windows-ul este un sistem de operare bazat pe masaje. Daca un fir de execuie apeleaz ::GetMessage i coada sa de mesaje este goal, firul se blocheaz pn cnd devine disponibil un mesaj. Acest lucru d ansa firelor cu prioritate redus de a fi executate. Majoritatea firelor cu interfa i petrec mare parte din timpul lor blocate pe coada de mesaje, aa c att timp ct un fir de lucru cu prioritate ridicat nu monopolizeaz procesorul, chiar i firele cu prioritate redus practic beneficiaz de tot timpul de procesor de care au nevoie. (Un fir de execuie de lucru nu se blocheaz pe coada de mesaje deoarece nu proceseaz mesaje.) Planificatorul face mai multe trucuri cu nivelele de prioritate pentru a mbunti disponibilitatea sistemului de rspuns i pentru a reduce pericolul ca un fir oarecare s nu primeasc timp de procesor. Dac un fir cu prioritatea 7 st mai mult timp fr s primeasc timp de procesor, planificatorul poate mari temporar prioritatea firului la 8, 9 sau chiar mai mare pentru a-i da ansa de a se executa. Windows NT 3.x crete prioritile firelor care 98

se execut n prim-plan pentru a mbunti timpul de rspuns al aplicaiei n care lucreaz utilizatorul, iar Windows NT 4.0 Workstation crete perioada de timp acordat acestor fire. Windows-ul folosete o tehnic numit motenirea prioritii pentru a preveni blocarea pentru prea mult timp a firelor cu prioritate ridicat pe obiecte de sincronizare deinute de fire cu prioritate sczut. De exemplu, dac un fir de execuie cu prioritatea 11 ncearc s revendice un mutex deinut de un fir cu prioritatea 4, planificatorul poate s mreasc prioritatea celui de-al doilea fir pentru ca mutex-ul s se elibereze mai devreme. De fapt cum sunt atribuite prioritile prima dat ? Atunci cnd apelm AfxBeginThread sau CWinThread::SetThreadPriority noi specificm prioritatea relativ a firului. Sistemul de operare combin nivelul de prioritate relativ cu clasa de prioritate a procesului tat al firului pentru a calcula un nivel de prioritate de baz pentru fir. Nivelul real de prioritate al firului, un numr ntre 0 i 31, poate varia continuu deoarece prioritatea poate crete i scdea. Nu putem controla creterea (i nici nu am vrea s facem acest lucru chiar dac am putea s l facem), dar putem s controlm nivelul de prioritate de baz setnd clasa de prioritate a procesului i nivelul de prioritate relativ al firului.

Prioritatea proceselor i a firelor de execuie


Clasele de prioritate ale proceselor Majoritatea proceselor i ncep existena cu clasa de prioritate NORMAL_PRIORITY_CLASS. O dat pornite ns, un proces s i schimbe prioritatea apelnd ::SetPriorityClass, care accept ca argumente un identificator de proces (care poate fi obinut cu apelul ::GetCurrentProcess) i unul din specificatorii din tabelul urmtor : Clasele de prioritate ale proceselor Clasa de prioritate Descriere IDLE_PRIORITY_CLASS Procesul ruleaz doar cnd sistemul este liber, de exemplu cnd nici un alt fie de execuie nu ateapt s fie executat. NORMAL_PRIORITY_CLASS Clasa de prioritate implicit. Procesul nu are nevoi speciale de planificare. HIGH_PRIORITY_CLASS Procesul primete o prioritate mai mare ca un proces IDLE_PRIORITY_CLASS sau NORMAL_PRIORITY_CLASS. REALTIME_PRIORITY_CLASS Procesul are cea mai mare prioritate posibil, mai ridicat chiar dect HIGH_PRIORITY_CLASS. Majoritatea aplicaiilor nu au nevoie s i schimbe clasa de prioritate. Procesele cu prioritile HIGH_PRIORITY_CLASS sau REALTIME_PRIORITY_CLASS pot afecta timpul de rspuns al sistemului i pot chiar ntrzia activiti sistem vitale, cum ar golirea zonei de cache a harddiskului. O folosire corect a clasei HIGH_PRIORITY_CLASS este pentru aplicaiile sistem care rmn ascunse majoritatea timpului dar produc o fereastr atunci cnd are loc un eveniment de intrare. Aceste aplicaii impun o sarcin foarte mic asupra sistemului att timp ct sunt blocate ateptnd o intrare, dar o dat ce apare o intrare ele primesc o prioritate mai mare dect restul aplicaiilor. Clasa REALTIME_PRIORITY_CLASS este furnizat n primul rnd pentru programele care primesc date n mod real i care trebuie s aib parte de partea leului din timpul procesorului pentru a funciona corect. Clasa IDLE_PRIORITY_CLASS este ideal pentru protecia ecranului, monitoare de sistem sau alte aplicaii cu prioritate redus care sunt proiectate s opereze neobstructiv n fundal. Algoritmul de planificare are un efect important asupra tipurilor de aplicaii pe care le pot rula utilizatorii. nc de la nceput, dezvoltatorii de la Microsoft i-au dat seama c vor trebui s modifice algoritmul de planificare n timp pe msur ce scopul calculatoarelor se va schimba. Dar dezvoltatorii de software au nevoie s scrie programe acum i Microsoft garanteaz c programele vor rula i n versiuni ulterioare ale sistemului. Cum poate Microsoft s schimbe modulde funcionare al sistemului i totui s pstreze softul n stare de funcionare ? Iat cteva rpunsuri : Microsoft nu ofer documentaia complet a planificatorului; Microsoft nu permite aplicaiilor s beneficieze de toate avantajele planificatorului; Microsoft ne spune c algoritmul de planificare poate fi schimbat astfel inct noi programm defensiv. 99

API-ul Windows ofer un strat abstract pentru planificatorul sistemului, astfel nct nu avem acces direct la planificator. n schimb, noi apelm funcii Windows care interpreteaz parametrii notri n funcie de versiunea sistemului de operare pe care rulm. Atunci cnd proiectm o aplicaie, trebuie s ne gndim la alte aplicaii pe care utilizatorul le va rula mpreun cu aplicaia noastr. Apoi va trebui s alegem o clas de prioritate bazat pe viteza de rspuns pe care dorim s o aib firele de execuie din aceast aplicaie. O dat ce alegem o clas de prioritate, nu trebuie s ne mai gndim cum interacioneaz aplicaia noastr cu alte aplicaii si trebuie s ne concentrm asupra firelor din aplicaie. Windows suport apte prioriti relative pentru fire de execuie : idle, lowest, below normal, normal, above normal, highest i time-critical. Aceste prioriti sunt relative la clasa de prioritate a procesului. Din nou, majoritatea firelor au prioritatea normal. Descriere Firul ruleaz la 31 pentru clasa de prioritate realtime i la 15 pentru toate celelalte clase de prioritate. Highest Firul ruleaz cu dou nivele peste normal. Above normal Firul ruleaz cu un nivel peste normal. Normal Firul ruleaz normal pentru clasa de prioritate a procesului. Below normal Firul ruleaz cu un nivel sub normal. Lowest Firul ruleaz cu dou nivele sub normal. Idle Firul ruleaz cu prioritatea 16 pentru clasa de prioritate real-time i cu 1 pentru celelalte clase de prioritate. n concluzie, procesul face parte dintr-o clas de prioritate i n cadrul acestuia atribuim prioriti relative firelor de execuie. Prioritatea absolut difer de la un sistem de operare la altul. La Windows 2000 ea se calculeaz n modul urmtor: Prioritatea reltiv a firului Idle Time-critical Highest Above normal Normal Below normal Lowest Idle 15 6 5 4 3 2 1 Clasa de prioritate a procesului Below Normal Above Normal Normal 15 15 15 8 10 12 7 9 11 6 8 10 5 7 9 4 6 8 1 1 1 Prioritatea relativ a firelor de execuie Time-critical

High 15 15 14 13 12 11 1

RealTime 31 26 25 24 23 22 16

Dup cum se observ nu exist nivelul de prioritate 0, care este rezervat. De asemenea, nivelurile 17, 18, 19, 20, 21, 27, 28, 29 sau 30 pot fi obinute doar dac scriem un driver care ruleaz n mod nucleu. O aplicaie utilizator nu poate obine aceste prioriti. De asemenea trebuie observat c un fir dintr-o clasa de prioritate real-time nu poate avea o prioritate mai mic de 16. De asemenea, un fir dintr-o clas non-real-time nu poate avea o prioritate mai mare de 15. Procesele nu sunt niciodat planificate, doar firele pot fi planificate. Clasa de prioritate a proceselor este o abstraciune introdus de Microsoft pentru a ne ndeprta de funcionarea intern a planificatorului. Un fir de execuie cu prioritate ridicat ar trebui s execute mai puine instruciuni, avnd acces la procesor aproape imediat, iar cele cu prioritate rmn planificabile pentru o perioada mai mare de timpi execut mai multe instruciuni. Dac se respect aceste reguli, ntregul sistem de operare va rspunde mult mai repede la aciunile utilizatorilor.

100

Programarea prioritilor Cum atribuim unui proces o clas de prioritate n SDK? Atunci cnd apelm CreateProcess, putem transmite clasa de prioritate dorit n parametrul fdwCreate. O dat ce procesul copil ruleaz, el poate s i schimbe propria prioritate apelnd SetPriorityClass : Bool SetPriorityClass (HANDLE hProcess, DWORD fdwPriority); Aceast funcie schimb clasa de prioritate identificat de hProcess la valoarea specificat de parametrul fdwPriority. Acest parametru poate fi unul din identificatorii din tabelul de mai sus. Deoarece aceast funcie ia identificatorul unui proces, putem schimba clasa de prioritate a oricrui proces care ruleaz in sistem atta timp ct avem un identificator al lui i avem accesul corespunztor. n mod normal, un proces va ncerca s i modifice propria sa clas de prioritate. Bool SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS); O funcie complementar folosit pentru a extrage clasa de prioritate a unui proces este : DWORD GetPriorityClass (HANDLE hProcess); Funcia returneaz unul din identificatorii din tabelul de mai sus. Task Managerul din Windows 2000 permite utilizatorilor s schimbe prioritatea unui proces. La crearea unui fir de execuie, prioritatea sa relativ este ntotdeauna setat implicit la normal. Pentru a seta prioritatea unui fir, trebuie s apelm funcia SetThreadPriority : BOOL SetThreadPriority (HANDLE Thread, int nPriority); Bineneles, parametrul hThread identific firul singular a crui prioritate vrem s o schimbm, iar parametrul nPriority este unul din cei 7 identificatori din tabelul de mai jos. Prioritatea relativ a firului de execuie Constanta simbolic Time-critical THREAD_PRIORIY_TIME_CRITICAL Highest THREAD_PRIORIY_TIME_HIGHEST Above normal THREAD_PRIORIY_TIME_ABOVE_NORMAL Normal THREAD_PRIORIY_TIME_NORMAL Below normal THREAD_PRIORIY_TIME_BELOW_NORMAL Lowest THREAD_PRIORIY_TIME_LOWEST Idle THREAD_PRIORIY_TIME_IDLE Funcia complementar care extrage prioritatea relativ a firului este: int GetThreadPriority (HANDLE hThread); Aceast funcie returneaz unul din identificatorii din tabelul de mai sus. n MFC putem trimite una din valorile de mai sus funciilor AfxBeginThread i CWinThread::SetThreadPriority. n SDK, CreateThread creeaz ntotdeauna un nou fir cu prioritatea relativ normal. Pentru a face ca firul s aib prioritatea idle, putem transmite indicatorul CREATE_SUSPENDED funciei CreateThread. Acest lucru face ca firul s nu execute cod deloc. Putem apela apoi SetThreadPriority pentru a schimba prioritatea firului la prioritatea idle. Apoi apelm ResumeThread astfel nct firul poate fi planificabil. Nu tim cnd va primi mai mult timp de procesor, dar planificatorul ia in considerare faptul c acest fir are prioritatea idle. n cele din urm, nchidem identificatorul ctre noul fir astfel nct obiectul nucleu poate fi distrus de ndat ce firul i termin execuia. Windows nu ofer o funcie care returneaz nivelul de prioritate a unui fir de execuie. Aceast omisiune este deliberat. Microsoft i rezerv dreptul de a schimba algoritmul de planificare n orice moment. Este recomandat s nu dezvoltm o aplicaie care are nevoie de cunotine specifice ale alogoritmului planificatorului. Dac rmnem cu clasele de prioritate ale proceselor i cu nivelele relative de prioritate ale firelor de execuie, aplicaia noastr va rula bine att n sistemul actual, ct i n versiunile urmtoare.

101

Sincronizarea firelor de execuie


Accesul atomic : familia de funcii de sincronizare API-ul Win32 include o familie de funcii numite ::InterlockedIncrement, ::InterlockedDecrement, ::InterlockExchange, ::InterlockCompareExchange i ::InterlockExchangeAdd pe care le putem folosi pentru a opera n siguran asupra unor valori pe 32 de bii fr s folosim n mod explicit obiecte de sincronizare. De exemplu, dac nVar este un UINT, DWORD sau alte tipuri de date pe 32 de bii, putem s o incrementm cu declaraia ::InterlockedIncrement (& nVar); i sistemul se va asigura c nici un alt acces la nVar folosind funcii Interlocked nu se va suprapune cu acesta. nVar va trebui s fie aliniat pe o limit de 32 de bii, deoarece altfel funciile Interlocked s-ar putea s dea gre pe sistemele Windows NT cu mai multe procesoare. De asemenea, ::InterlockCompareExchange i ::InterlockExchangeAdd sunt suportate doar n Windows NT 4.0 sau mai recent i n Windows 98. Seciuni critice n SDK O seciune critic este o mic poriune de cod care cere acces exclusiv la o resurs comun nainte de execuia codului. Aceasta este modalitatea de a manipula atomic o resurs. Prin atomic nelegem faptul c codul tie c nici un alt fir de execuie nu va accesa resursa. Bineneles, sistemul poate porni alt fir de execuie i poate planifica altele. Totui, el nu va planifica fire care doresc s acceseze resursa pn cnd firul nostru nu prsete seciunea critic. Care sunt punctele de reper n folosirea seciunilor critice? Atunci cnd avem o resurs care este accesat de multiple fire de execuie, trebuie s crem o structur CRITICAL_SECTION. Dac avem resurse care sunt ntotdeauna folosite mpreun, putem crea o singur structur CRITICAL_SECTION care s le pzeasc pe toate. Dac avem resurse multiple care nu sunt utilizate tot timpul mpreun, trebuie s crem o structur CRITICAL_SECTION pentru fiecare resurs.

n cazul n care avem o seciune critic, trebuie s apelm EnterCriticalSection creia i transmitem adresa structurii CRITICAL_SECTION care identific resursa. Structura CRITICAL_SECTION identific ce resurs vrea firul s acceseze iar EnterCriticalSection verific dac acea resurs este disponibil. Dac EnterCriticalSection vede c nici un alt fir nu acceseaz resursa, atunci permite accesul firului apelant. Dac funcia vede c exist un fir de execuie care acceseaz resursa, firul apelant trebuie s atepte pn la eliberarea resursei. Atunci cnd un fir execut cod care nu acceseaz resursa, trebuie s apeleze LeaveCriticalSection. Aceasta este modalitatea prin care firul spune sistemului c nu mai vrea s acceseze resursa. Dac uitm s apelm LeaveCriticalSection, sistemul va crede c resursa este ocupat i nu va permite accesul altor fire de execuie. Este foarte important s mpachetm codul care vrea s acceseze resursa n interiorul funciilor EnterCriticalSection i LeaveCriticalSection. Dac uitm s facem acest lucru chiar i doar ntr-un singur loc resursa va fi predispus la corupere. Atunci cnd nu putem rezolva problema de sincronizare cu ajutorul funciilor de sincronizare, trebuie s ncercm s folosim seciunile critice. Avantajul seciunilor critice este c sunt uor de folosit i folosesc funciile de sincronizare intern, astfel nct se execut foarte rapid. Dezavantajul major este c nu le putem folosi pentru sincronizarea firelor n procese diferite. n mod normal, structura CRITICAL_SECTION este alocat ca o variabil global pentru a oferi tuturor firelor din proces o modalitate simpl de a referi structura; prin numele variabilei. Totui, structura CRITICAL_SECTION poate fi alocat ca o variabil local sau poate fi alocat dinamic n heap . Sunt doar dou cerine. 1. Prima este c toate firele care vor s acceseze resursa trebuie s tie adresa structurii CRITICAL_SECTION care protejeaz resursa. Putem transmite aceast adres firelor prin orice mecanism dorim. 102

2.

A doua cerin este c membrii din structura CRITICAL_SECTION trebuie s fie iniializai nainte ca alte fire s ncerce s acceseze resursa. Structura este iniializat n modul urmtor :

VOID InitializeCriticalSection (PCRITICAL_SECTION pcs); Aceast funcie iniializeaz membrii structurii CRITICAL_SECTION. Deoarece aceast funcie doar seteaz nite variabile membru, nu poate eua i de aceea valoarea de retur este VOID. Aceast funcie trebuie apelat nainte ca orice fir de execuie s apeleze EnterCriticalSection. Platforma SDK menioneaz clar c rezultatele nu sunt definite dac un fir ncearc s intre ntr-o structur CRITICAL_SECTION neiniializat. Atunci cnd suntem siguri ca firele de execuie nu vor mai ncerca s acceseze resursa comun, trebuie s stergem (curm) structura CRITICAL_SECTION prin urmtorul apel : VOID DeleteCriticalSection (PCRITICAL_SECTION pcs); DeleteCriticalSection examineaz variabilele membru din interiorul structurii. Aceste variabile indic ce fir de execuie, dac exist unul, acceseaz n momentul respectiv resursa comun. EnterCriticalSection efectueaz urmtoarele teste : dac nici un fir nu acceseaz resursa, EnterCriticalSection actualizeaz variabilele membru pentru a indica faptul c firul apelant a primit dreptul de a accesa resursa i apoi returneaz imediat, permind firului s i continue execuia. dac variabilele membru indic faptul c firul apelant a primit deja dreptul de a accesa resursa, EnterCriticalSection actualizeaz variabilele pentru a indica de cte ori a primit accesul la resurs firul apelant i apoi funcia returneaz imediat, permind firului s i continue execuia. Aceast situaie este rar ntlnit i apare doar atunci cnd firul apeleaz EnterCriticalSection de dou ori la rnd fr a apela LeaveCriticalSection. dac variabilele membru indic faptul c un fir (altul dect cel apelant) a primit dreptul de a accesa resursa, EnterCriticalSection pune firul apelant n starea de ateptare. Acest lucru este benefic deoarece astfel firul nu mai consum timp de procesor. Sistemul reine ce fire de execuie doresc s acceseze resursa, actualizeaz automat variabilele membru ale structurii CRITICAL_SECTION i permite firului s devin planificabil de ndat ce firul care acceseaz resursa apeleaz LeaveCriticalSection. EnterCriticalSection nu are o structur intern prea complex; ea efectueaz doar cteva teste simple. Ce face funcia aceasta att de valoroas este c poate efectua aceste teste atomic. Dac dou fire apeleaz EnterCriticalSection n acelai timp pe un sistem multiprocesor, funcia se comport corect : un fir primete permisiunea s acceseze resursa iar cellalt este plasat n starea de ateptare. Dac EnterCriticalSection pune un fir n starea de ateptare, firul s-ar putea s nu mai fie planificat pentru o perioad mai mare de timp. De fapt, ntr-o aplicaie scris prost, firul ar putea s nu mai fie planificat nici o dat pentru timp de procesor. Dac acest lucru are loc, spunem c firul este nfometat de timp de procesor. n realitate, firele care ateapt o seciune critic nu nfometeaz niciodat. Apelurile EnterCriticalSection n cele din urm expir, cauznd apariia unei excepii. Putem ataa un debugger aplicaiei noastre pentru a determina ce nu a funcionat corect. Durata de timp care trebuie s expire este dat de valoarea coninut n regitri la adresa : HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager Putem folosi urmtoarea funcie n locul funciei EnterCriticalSection : BOOL TryEnterCriticalSection (PCRITICAL_SECTION pcs); Aceast funcie nu permite firului apelant s intre n starea de ateptare. n schimb, valoarea returnat ne spune dac firul apelant a fost n stare s primeasc acces la resurs. Dac TryEnterCriticalSection vede c resursa este accesat de un alt fir, ea returneaz FALSE. n toate celelalte cazuri ea returneaz TRUE. Cu aceast funcie, un fir poate verifica rapid dac poate accesa o resurs comun i n caz contrar continu s fac altceva. Dac funcia returneaz TRUE, variabilele membru ale structurii CRITICAL_SECTION au fost actualizate pentru a reflecta faptul c firul acceseaz o resurs. De aceea, fiecare apel al funciei TryEnterCriticalSection care returneaz TRUE trebuie s fie potrivit cu un apel al funciei LeaveCriticalSection. Windows 98 nu are o implementare util pentru funcia TryEnterCriticalSection. Ea returneaz ntotdeauna FALSE. La sfritul codului care acceseaz resursa, trebuie s apelm aceast funcie : 103

VOID LeaveCriticalSection (PCRITICAL_SECTION pcs); LeaveCriticalSection examineaz variabilele membre din interiorul structurii. Funcia decrementeaz cu 1 un contor care indic de cte ori a primit accesul la resurs firul apelant. Dac acest contor este mai mare dect 0, LeaveCriticalSection nu face nimic altceva i returneaz. Dac contorul devine 0, ea verific dac mai sunt alte fire n ateptare apelnd EnterCriticalSection. Dac mcar un fir este in ateptare, actualizeaz variabilele membre i face unul din firele n ateptare planificabil. Dac nici un fir nu este n ateptare, LeaveCriticalSection actualizeat variabilele membre pentru a indica faptul c firul nu mai acceseaz resursa. Ca i funcia EnterCriticalSection, funcia LeaveCriticalSection efectueaz toate aceste teste i actualizeaz automat. Totui, LeaveCriticalSection nu plaseaz niciodat un fir n starea de ateptare; ea returneaz imediat. Atunci cnd un fir ncearc s intre ntr-o seciune critic care este deinut de un alt fir, firul apelant este plasat imediat ntr-o stare de ateptare. Acest lucru nseamn c firul trebuie s tranziteze din modul utilizator n modul nucleu (aproape 1000 de cicluri de procesor). Aceast tranziie este foarte costisitoare. Pe un sistem multiprocesor, firul care este proprietarul curent al resursei se poate executa pe un procesor diferit i poate reda controlul asupra resursei rapid. De fapt, firul care este proprietarul resursei o poate elibera nainte ca cellalt fir s i termine tranziia n modul nucleu. Dac acest lucru are loc, se pierde mult timp de procesor. Pentru a mbunti performana seciunilor critice, Microsoft a incorporat n ele spinlocks. Astfel, atunci cnd apelm EnterCriticalSection, ea intr ntr-o bucl folosind un spinlock pentru a ncerca s preia controlul asupra resursei pentru un anumit numar de ori. Doar dac o incercare eueaz firul face tranziia n modul nucleu pentru a intra in starea de ateptare. Pentru a utiliza un spinlock cu o seciune critic, trebuie s iniializm seciunea critic prin apelul : BOOL InitializeCriticalSectionAndSpinCount( PCRITICAL_SECTION pcs, DWORD dwSpinCount); La fel ca i funcia InitializeCriticalSection, primul parametru al funciei InitializeCriticalSectionAndSpinCount este adresa structurii seciune critic. Dar n cel de-al doilea parametru, dwSpinCount, transmitem numarul de cate ori dorim s se execute spinlock, ct timp ncearc s obin resursa nainte de a trimite firul n starea de ateptare. Aceast valoare poate fi orice numr de la 0 la 0x00FFFFFF. Dac apelm aceast funcie cnd rulm pe un sistem cu un singur procesor, parametrul dwSpinCount este ignorat i counterul este setat ntotdeauna la 0. Acest lucru este bun deoarece setarea unui contor de revenire pe un sistem cu un singur procesor este inutil : firul care este proprietarul resursei nu o poate elibera dac cellalt fir este n bucl. Putem modifica acest contor folosind urmtoarea funcie : DWORD SetCriticalSectionSpinCount( PCRITICAL_SECTION pcs, DWORD dwSpinCount); Folosirea acestor spinlocks cu seciuni critice este util deoarece nu avem nimic de pierdut. Partea cea mai grea este determinarea valorii pentru parametrul dwSpinCount. Pentru cea mai bun performan, trebuie s ne jucm cu numerele pn cnd suntem satisfcui de performaele rezultatelor. Ca exemplu, seciunea critic care pzete accesul la heap-ul procesului nostru are contorul egal cu 4000. Probabilitatea ca funcia InitializeCriticalSection s eueze este foarte redus. Microsoft nu a prevzut acest lucru atunci cnd a proiectat-o. Funcia poate eua deoarece ea aloc un bloc de memorie pentru ca sistemul s aib nite informaie intern pentru depanare. Dac aceast alocare eueaz, este generat o eroare STATUS_NO_MEMORY. Putem capta mai uor aceast problem folosind InitializeCriticalSectionAndSpinCount. Aceast funcie aloc la rndul ei memorie dar returneaz FALSE dac nu reuete. O alt problem poate s apar. Intern, seciunile critice folosesc un obiect nucleu eveniment dac dou sau mai multe fire se lupt pentru seciunea critic n acelai timp. Deoarece acest caz este rar ntlnit, sistemul nu creeaz obiectul nucleu pn 104

cnd nu este necesar prima dat. Acest lucru salveaz multe resurse de sistem deoarece majoritatea seciunilor critice nu au niciodat controverse. ntr-o situaie cnd avem puin memorie, o seciune critic ar putea avea un conflict i sistemul poate s nu fie capabil s creeze obiectul nucleu eveniment cerut. Funcia EnterCriticalSection va cauz apariia excepiei EXCEPTION_INVALID_HANDLE. Majoritatea programatorilor ignor aceast eroare posibil i nu au o tratare special n codul lor deoarece aceast eroare este foarte rar ntlnit. Totui, dac dorim s fim pregtii pentr aa ceva, avem dou opiuni. Putem folosi tratarea structurat a erorilor i putem prinde eroarea. Atunci cnd apare o eroare, putem fie s nu accesm resursa protejat de serciunea critic, fie s ateptm s se elibereze memoria i apoi s apelm EnterCriticalSection din nou. Cealalt opiune este s crem seciunea critic cu InitializeCriticalSectionAndSpinCount i s ne asigurm c am setat bitul superior din parametrul dwSpinCount. Atunci cnd aceast funcie vede c acest parametru este setat, creeaz obiectul nucleu eveniment i l asocieaz cu seciunea critic la momentul iniializarii. Dac evenimentul nu poate fi creat, funcia returneaz FALSE i putem trata acest lucru mult mai elegant n cod. Exist mai multe tehnici i informaii folositoare atunci cnd lucrm cu seciuni critice. Prima ar fi folosirea unei singure seciui critice pentru fiecare resurs. Dac avem cteva structuri de date care nu legtur ntre ele, ar trebui s crem o variabil CRTICAL_SECTION pentru fiecare structur de date. Acest lucru este mai bun dect s avem o singur structur CRITICAL_SECTION care pzete accesul la toate resursele. int g_nNums[100]; TCHAR g_cChars[100]; CRITICAL_SECTION g_cs; // O resurs comun. // O alt resurs comun. // Pzete ambele resurse.

DWORD WINAPI ThreadFunc(PVOID pvParam) { EnterCriticalSection(&g_cs); for (int x = 0; x < 100; x++) { g_nNums[x] = 0; g_cChars[x] = TEXT('X'); } LeaveCriticalSection(&g_cs); return(0); } Acest cod folosete o singur seciune critic pentru a proteja tablourile g_nNums i g_cChars atunci cnd acestea sunt iniializate. Dar aceste tablouri nu au nici o legtur unul cu altul. Dac funcia ThreadFunc este implementat ca n continuare, cele dou tablouri sunt iniializate separat : DWORD WINAPI ThreadFunc(PVOID pvParam) { EnterCriticalSection(&g_cs); for (int x = 0; x < 100; x++) g_nNums[x] = 0; for (x = 0; x < 100; x++) g_cChars[x] = TEXT('X'); LeaveCriticalSection(&g_cs); return(0); } Teoretic, dup ce tabloul g_nNums a fost iniializat, un fir diferit de cel ce a initializat structura g_nNums, care dorete s acceseze doar tabloul g_nNums i nu la g_cChars poate ncepe execuia n timp ce ThreadFunc continu s iniializeze tabloul g_cChars. Dar din pcate, acest lucru nu este posibil deoarece o singur seciune critic protejeaz ambele structuri de date. Pentru a repara acest lucru, putem crea dou seciuni critice: 105

int g_nNum[100]; // O resurs comun CRITICAL_SECTION g_csNums; // Pzete g_nNums TCHAR g_cChars[100]; // O alt resurs comun. CRITICAL_SECTION g_csChars; // Pzete g_cChars DWORD WINAPI ThreadFunc(PVOID pvParam) { EnterCriticalSection(&g_csNums); for (int x = 0; x < 100; x++) g_nNums[x] = 0; LeaveCriticalSection(&g_csNums); EnterCriticalSection(&g_csChars); for (x = 0; x < 100; x++) g_cChars[x] = TEXT('X'); LeaveCriticalSection(&g_ csChars); return(0); } Cu aceast implementare, un alt fir poate ncepe s foloseasc tabloul g_nNums de ndat ce funcia ThreadFunc a terminat iniializarea lui. Putem de asemenea s avem un fir care iniializeaz tabloul g_nNums i un fir separat iniializeaz tabloul g_cChars. O alt posibilitate este accesarea resurselor multiple simultan. Uneori avem nevoie s accesm dou resurse simultan. Dac aceasta a fost o cerin a funciei ThreadFunc, ea va trebui s fie implementat ca n exemplul urmtor : DWORD WINAPI ThreadFunc(PVOID pvParam) { EnterCriticalSection(&g_csNums); EnterCriticalSection(&g_csChars); // Aceast bucl are nevoie de acces simultan la ambele resurse. for (int x = 0; x < 100; x++) g_nNums[x] = g_cChars[x]; LeaveCriticalSection(&g_csChars); LeaveCriticalSection(&g_csNums); return(0); } S presupunem c alt fir din proces are nevoie la rndul lui s acceseze tablourile : DWORD WINAPI OtherThreadFunc(PVOID pvParam) { EnterCriticalSection(&g_csChars); EnterCriticalSection(&g_csNums); for (int x = 0; x < 100; x++) g_nNums[x] = g_cChars[x]; LeaveCriticalSection(&g_csNums); LeaveCriticalSection(&g_csChars); return(0); } Tot ce am fcut n aceste funcii a fost s schimbm ordinea apelurilor funciilor EnterCriticalSection i LeaveCriticalSection. Dar deoarece aceste dou funcii sunt scrise ca n exemplul de mai sus, poate aprea un blocaj. S presupunem c funcia ThreadFunc i ncepe execuia i devine proprietatea seciunii critice a tabloului g_csChars. Apoi firul care execut OtherThreadFunc primete timp de procesor i devine proprietar al seciunii 106

critice a tabloului g_csChars. n acest fel, avem o situaie de blocaj. Atunci cnd una din funciile ThreadFunc i OtherThreadFunc ncearc s i continue execuia, nici o funcie nu poate deveni proprietara celeilalte seciuni critice de care are nevoie. Pentru a rezolva aceast problem, trebuie s cerem ntotdeauna accesul la resurs n exact aceeai ordine. Trebuie s notm c ordinea nu are importan atunci cnd apelm LeaveCriticalSection deoarece aceast funcie nu are niciodat ca efect intrarea unui fir n starea de ateptare. Alt sugestie este s nu inem ocupate seciunile critice pentru o perioad prea mare de timp. Atunci cnd o seciune critic este ocupat pentru o perioad mai mare de timp, alte fire s-ar putea s intre n starea de ateptare, care afecteaz performana aplicaiei. n continuare avem o tehnic cu care putem s reducem timpul pierdut n timpul seciunii critice. Urmtorul cod previne alte fire s schimbe valoarea din g_s nainte ca mesajul WM_SOMEMSG s fie trimis ferestrei si procesat. SOMESTRUCT g_s; CRITICAL_SECTION g_cs; DWORD WINAPI SomeThread(PVOID pvParam) { EnterCriticalSection(&g_cs); // Trimite un mesaj ctre fereastr. SendMessage(hwndSomeWnd, WM_SOMEMSG, &g_s, 0); LeaveCriticalSection(&g_cs); return(0); } Este imposibil s spunem de ct timp are nevoie funcia de fereastr pentru procesarea mesajului WM_SOMEMSG ar putea fi cteva milisecunde sau civa ani. n acest timp, nici un alt fir nu poate accesa structura g_s. Este mai bine s scriem cod ca mai jos : SOMESTRUCT g_s; CRITICAL_SECTION g_cs; DWORD WINAPI SomeThread(PVOID pvParam) { EnterCriticalSection(&g_cs); SOMESTRUCT sTemp = g_s; LeaveCriticalSection(&g_cs); // Trimite un mesaj ctre fereastr. SendMessage(hwndSomeWnd, WM_SOMEMSG, &sTemp, 0); return(0); } Acest cod salveaz valoarea n sTemp, o variabil temporar. Probabil putem ghici ct timp are nevoie procesorul pentru a executa aceast linie doar cteva cicluri de procesor. Imediat dup ce variabila temporar este salvat, LeaveCriticalSection este apelat deoarece structura global nu mai are nevoie s fie protejat. A doua implementare este mult mai buna dect prima deoarece alte fire sunt oprite s foloseasc structura g_s pentru doar cteva cicluri de procesor n loc de o perioad nedefinit de timp. Bineneles, aceast tehnic presupune c instantaneul structurii este destul de bun pentru ca funcia ferestrei s o citeasc. De asemenea presupune c funcia de fereastr nu are nevoie s schimbe membrii structurii. Seciuni critice n MFC MFC-ul implementeaz seciunile critice cu ajutorul clasei CCriticalSection. CCriticalSection::Lock blocheaz o seciune critic i CCriticalSection::Unlock o deblocheaz. S presupunem c clasa unui document include o dat membru de tip list nlnuit creat din clasa MFC CList i c dou fire separate folosesc aceast list. Una scrie n list i alta citete din ea. Pentru a preveni cele dou fire de a accesa lista n acelai moment, putem s o protejm cu o seciune critic. Urmtorul exemplu folosete un obiect global CCriticalSection pentru a demonstra cele spuse anterior. (S-au folosit obiecte de sincronizare globale n exemple pentru a ne asigura c obiectele sunt vizibile n mod egal fiecrui fir in proces, dar obiectele de sincronizare nu trebuie s aib domeniul global.) 107

// Date globale CCriticalSection g_cs; // Firul A g_cs.Lock(); //Scrie n lista nlnuit g_cs.Unlock(); //Firul B g_cs.Lock(); //Citete din lista nlnuit g_cs.Unlock(); Acum este imposibil ca firele A i B s acceseze lista nlnuit n acelai timp deoarece lista este pzit de aceeai seciune critic. O form alternativ a funciei CCriticalSection::Lock accept o valoare de expirare i unele documentaii MFC precizeaz c dac i transmitem o valoare, ea va returna dac perioada expir nainte ca seciunea critic s devin liber. Documentaia este greit. Putem specifica un timp de expirare dac vrem, dar Lock nu va returna pn cnd nu se va elibera seciunea critic. Este evident de ce o list nlnuit ar trebui protejat de apelurile unor fire concurente , dar cum rmne cu variabilele simple ? De exemplu, s presupunem c firul A incrementeaz o variabil cu declaraia : nVar++; i firul B face altceva cu acea variabil. Ar trebui ca nVar s fie protejat cu o seciune critic ? n general, da. Ceea ce pare o operaie indivizibil ntr-un program C++ poate fi compilat ntr-o secven de cteva instruciuni main. i un fir poate primi accesul naintea altuia ntre orice dou secven main. Ca o regul, este o idee bun de a proteja orice dat de accesul simultan pentru scriere sau pentru accesul simultan pentru scriere i citire. O seciune critic este instrumentul perfect pentru acest lucru. Sincronizarea firelor de execuie folosind obiecte nucleu Evenimente n SDK Dintre toate obiectele nucleu, evenimentele sunt cele mai primitive. Ele conin un contor de utilizare, o valoare de tip Boolean care indic dac evenimentul este cu resetare manual sau automat, i o alt valoare de tip Boolean care ne spune c obiectul este n starea semnalat sau nesemnalat. Evenimentul in starea semnalat are semnificatia ca evenimentul poate fi folosit in continuare de alte fire, sau mai corect, firele ce asteapta pe acel eveniment devin planificabile. Exist dou tipuri de obiecte eveniment : cu resetare manual sau cu resetare automat. Atunci cnd un eveniment cu resetare manual este semnalat, toate firele care ateapt obiectul devin planificabile. Atunci cnd un eveniment cu resetare automat este semnalat, doar unul din firele care ateapt evenimentul devine planificabil. Evenimentele reprezint cea mai folosit metod atunci cnd un fir face o operaie de iniializare i apoi semnaleaz unui alt fir s efectueze restul sarcinii. Evenimentul este iniializat ca nesemnalat, iar dup ce firul i termin sarcina iniial, seteaz evenimentul la semnalat. n acest moment al execuiei, un alt fir, care atepta evenimentul, observ c evenimentul este semnalat i devine planificabil. Al doilea fir tie c primul fir i-a efectuat sarcina. Funcia CreateEvent creeaz un obiect nucleu eveniment : HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa, BOOL fManualReset, BOOL fInitialState, PCTSTR pszName); Parametrul fManualReset este o valoare de tip Boolean care spune sistemului dac s creeze un eveniment cu resetare manual (TRUE)sau un eveniment cu resetare automat (FALSE). Parametrul fInitialState indic dac evenimentul se creaza in starea semnalat (TRUE) sau nesemnalat (FALSE). 108

Dup ce sistemul creeaz obiectul, CreateEvent returneaz identificatorul obiectului relativ la proces. Firele din alte procese pot avea acces la obiect apelnd CreateEvent folosind aceeai valoare trimis n parametrul pszName; folosind motenirea; folsind funcia DuplicateHandle; apelnd OpenEvent, preciznd un nume n parametrul pszName care se potrivete cu numele precizat n apelul CreateEvent: HANDLE OpenEvent( DWORD fdwAccess, BOOL fInherit, PCTSTR pszName); Ca ntotdeauna, trebuie s apelm funcia CloseHandle atunci cnd nu mai avem nevoie de obiectul nucleu. O dat ce un obiect nucleu este creat, putem controla direct starea sa. Atunci cnd apelm SetEvent, schimbm starea evenimentului la semnalat : BOOL SetEvent(HANDLE hEvent); Atunci cnd apelm ResetEvent, setm starea evenimentului la nesemnalat: BOOL ResetEvent(HANDLE hEvent); Microsoft a definit un efect secundar pentru ateptarea reuit pentru un obiect cu resetare automat : el este automat resetat la starea nesemnalat atunci cnd un fir efectueaz o ateptare reuit a obiectului. De obicei nu este necesar s apelm ResetEvent pentru un eveniment cu resetare automat deoarece sistemul reseteaz automat evenimentul. Microsoft nu a definit un efect secundar pentru o ateptare reuit pentru evenimentele cu resetare manual. Avem n continuare un exemplu pentru sincronizarea firelor de execuie folosind obiectele nucleu eveniment: // Crem un identificator global la un eveniment // nesemnalat i cu resetare manual HANDLE g_hEvent; int WINAPI WinMain(...) { // Crem evenimentul nesemnalat cu resetare manual g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Crem 3 noi fire. HANDLE hThread[3]; DWORD dwThreadID; hThread[0] = _beginthreadex(NULL, 0, WordCount, NULL, 0, &dwThreadID); hThread[1] = _beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID); hThread[2] = _beginthreadex(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID); OpenFileAndReadContentsIntoMemory(...); // Permitem tuturor firelor s acceseze resursa SetEvent(g_hEvent); ... } DWORD WINAPI WordCount(PVOID pvParam) { // Ateptm pn cnd datele din fiier ajung n memorie. WaitForSingleObject(g_hEvent, INFINITE); // Accesm blocul de memorie. ... return(0); 109

} DWORD WINAPI SpellCheck (PVOID pvParam) { // Ateptm pn cnd datele din fiier ajung n memorie. WaitForSingleObject(g_hEvent, INFINITE); // Accesm blocul de memorie. ... return(0); } DWORD WINAPI GrammarCheck (PVOID pvParam) { // Ateptm pn cnd datele din fiier ajung n memorie. WaitForSingleObject(g_hEvent, INFINITE); // Accesm blocul de memorie. ... return(0); } La pornirea procesului, el creeaz un obiect cu resetare manual n starea nesemnalat i salveaz identificatorul ntr-o variabil global. Acest lucru uureaz accesarea aceluiai obiect eveniment de ctre alte fire din acest proces. Crem trei fire de execuie. Aceste fire ateapt pn cnd coninutul unui fiier este citit n memorie, iar apoi fiecare fir acceseaz datele : un fir efectueaz o numrare a cuvintelor, un altul ruleaz verificatorul de sintax, iar ultimul lanseaz verificatorul gramatical. Codul pentru aceste fire ncepe n mod identic : fiecare fir apeleaz WaitForSingleObject, care suspend firul pn cnd coninutul fiierului este citit n memorie de ctre firul principal. De ndat ce firul primar are datele, el apeleaz SetEvent, care semnalizeaz evenimentul. n acest moment al execuiei, sistemul face toate cele trei fire planificabile toate primesc timp de procesor i acceseaz blocul de memorie. Trebuie s remarcm c toate cele trei fire vor accesa memoria n mod read-only. Acesta este singurul motiv pentru care toate cele trei fire pot rula simultan. De asemenea, trebuie s observm c dac sistemul are mai multe procesoare, toate aceste fire se pot executa simultan, executnd un volum are de lucru ntr-o perioad scurt de timp. Dac folosim un eveniment cu resetare automat n loc de unul cu resetare manual, aplicaia se comport diferit. Sistemul permite doar unui fir secundar s devin planificabil dup ce firul principal apeleaz SetEvent. Din nou, nu avem nici garanie care fir va fi fcut planificabil de ctre sistem. Celelalte dou fire vor rmne n ateptare. Firul care devine planificabil are acces exclusiv la blocul de memorie. Putem rescrie codul de mai sus astfel nct fiecare fir s apeleze SetEvent nainte de a returna: DWORD WINAPI WordCount(PVOID pvParam) { // Wait until the file's data is in memory. WaitForSingleObject(g_hEvent, INFINITE); // Access the memory block. ... SetEvent(g_hEvent); return(0); } DWORD WINAPI SpellCheck (PVOID pvParam) { // Wait until the file's data is in memory. WaitForSingleObject(g_hEvent, INFINITE); // Access the memory block. ... SetEvent(g_hEvent); return(0); 110

} DWORD WINAPI GrammarCheck (PVOID pvParam) { // Wait until the file's data is in memory. WaitForSingleObject(g_hEvent, INFINITE); // Access the memory block. ... SetEvent(g_hEvent); return(0); } Atunci cnd un fir i ncheie accesul exclusiv asupra datelor, el apeleaz SetEvent, care permite sistemului s fac unul din cele dou fire care ateapt planificabil. Din nou, nu tim care din ele va fi fcut planificabil, dar acest fir va avea propriul acces exclusiv la blocul de memorie. Atunci cnd acest fir i termin sarcina, apeleaz la rndul lui SetEvent, fcnd ca al treilea fir s primeasc i el la rndul lui acces exclusiv asupra memoriei. Putem folosi nc o funcie pentru evenimente : BOOL PulseEvent(HANDLE hEvent); PulseEvent face un eveniment semnalat i apoi imediat nesemnalat. Dac apelm PulseEvent pentru un eveniment cu resetare manual, toate firele care ateptau pe acel eveniment devin planificabile. Dac apelm PulseEvent pe un eveniment cu resetare automat, doar unul din fire care ateapt devine planificabil. Dac nu exist nici un fir n ateptare, funcia nu are nici un efect. PulseEvent nu este foarte folositoare. Dac o aplicm in practic, nu putem ti care fire, dac exist, vor vedea acest efect i vor deveni planificabile. Evenimente n MFC Clasa MFC CEvent ncapsuleaz obiectele eveniment Win32. Un eveniment este puin mai mult dect un indicator n nucleul sistemului de operare. La orice moment dat, el poate fi n una din urmtoarele dou stri : setat sau nesetat. Un eveniment setat spunem c este n starea semnalat, iar un eveniment nesetat spunem ca este nesemnalat. CEvent::SetEvent seteaz un eveniment, iar CEvent::ResetEvent l reseteaz. O funcie nrudit, CEvent::PulseEvent seteaz un eveniment i l elibereaz ntr-o singur operaie. Evenimentele sunt uneori descrise ca declanatoare de fire. Un fir de execuie apeleaz CEvent::Lock() pentru a se bloca pe un eveniment i a atepta ca acesta s devin disponibil. Un alt fir seteaz evenimentul i prin urmare elibereaz firul n ateptare. Setarea evenimentului este asemntoare acionrii unui declanator : deblocheaz firul n ateptare i i d posibilitatea s i rezume execuia. Un eveniment poate avea blocate pe el unul sau mai multe fire de execuie, iar dac codul nostru este scris corect, toate firele n ateptare vor fi eliberate atunci cnd evenimentul devine setat. Windows suport dou tipuri diferite de evenimente : evenimente care se reseteaz automat i evenimente care se reseteaz manual. Diferena dintre ele este destul de simpl, dar implicaiile sunt importante. Un eveniment cu resetare automat este resetat automat n starea nesemnalat atunci cnd un fir blocat pe el este eliberat. Un eveniment care se reseteaz manual nu se reseteaz automat, trebuie resetat prin cod. Regulile pentru alegerea unuia dintre aceste dou tipuri de evenimente i pentru folosirea lor dup ce am fcut alegerea sunt urmtoarele : Dac doar un fir este declanat de eveniment, folosim un eveniment care se autoreseteaz i eliberm firul n ateptare cu SetEvent. Nu este nevoie s apelm ResetEvent deoarece evenimentul este resetat automat n momentul cnd firul este eliberat. Dac dou sau mau multe fire vor fi declanate de eveniment, folosim un eveniment cu setare manual i eliberm toate firele n ateptare cu PulseEvent. Din nou, nu este nevoie s apelm ResetEvent deoarece PulseEvent reseteaz evenimentul dup eliberarea firelor. Este vital s folosim un eveniment cu resetare manual pentru a declana fire multiple de execuie. De ce? Deoarece un eveniment cu resetare automat va fi resetat n momentul n care unul din fire este eliberat i din aceast cauz va declana doar un fir. Este n aceeai msur important s folosim PulseEvent pentru a activa declanatorul unui eveniment cu resetare manual. Dac folosim SetEvent i ReleaseEvent, nu avem nici o garanie 111

c toate firele n ateptare vor fi eliberate. PulseEvent nu doar seteaz i reseteaz evenimentul, ci de asemenea se asigur c toate firele care ateapt acel eveniment sunt eliberate nainte de resetarea evenimentului. Un eveniment este creat prin construirea unui obiect CEvent. CEvent::CEvent accept patru parametri, toi opionali. Prototipul funciei este : CEvent ( BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)

Primul parametru, bInitiallyOwn, precizeaz c obiectul este iniial semnalat (TRUE) sau nesemnalat (FALSE). Opiunea implicit este bun n majoritatea cazurilor. bManualReset precizeaz dac obiectul este un eveniment cu resetare manual (TRUE) sau automat (FALSE). Cel de-al treilea parametru, lpszName, atribuie un nume obiectului de tip eveniment. Ca i mutexurile, evenimentele pot fi folosite pentru a coordona fire de execuie rulnd n procese diferite, iar pentru ca un eveniment s treac de graniele proceselor, trebuie s i atribuim un alt nume. Dac firul care utilizeaz evenimentul aparine aceluiai proces, lpszName trebuie s fie NULL. Ultimul parametru, lpsaAttribute, este un pointer ctre o structur SECURITY_ATTRIBUTES care descrie atributele de securitate ale obiectului. NULL accept atributele implicite de securitate, care sunt suficiente pentru majoritatea aplicaiilor. Cum folosim evenimentele pentru a sincroniza firele? n continuare este un exemplu care implic un fir (firul A) care umple o zon de date tampon cu date i un alt fir (firul B) care face nite operaii cu acele date. S presupunem c firul B trebuie s atepte un semnal de la firul A care s spun c zona de date tampon este iniializat i pregtit. Un eveniment cu resetare automat este instrumentul perfect pentru aceast operaie: // Global data // Eveniment cu resetare automat, iniial nesemanalat CEvent g_event; // Firul A InitBuffer (&buffer); // Iniializm zona de date tampon // Eliberm firul B g_event.SetEvent(); // Firul B g_event.Lock(); // Ateptm semnalul Firul B apeleaz pentru a se bloca pe obiectul eveniment. Firul A apeleaz SetEvent atunci cnd este pregtit s elibereze firul B. Unicul parametru transmis funciei Lock specific ct timp este dispus s atepte apelantul, n milisecunde. Valoarea implicit este INFINITE, care nseamn s nsemne att ct este necesar. O valoare diferit de zero nseamn c funcia Lock a returnat deoarece un obiect a devenit semnalat; o nseamn c perioada de ateptare a expirat sau a intervenit o eroare. MFC-ul nu face nimic deosebit aici. El pur i simplu reconvertete obiectele nucleu de sincronizare a obiectelor i funciile API care opereaz asupra lor ntr-o form mai mult orientat obiect. Evenimentele care se reseteaz automat sunt eficiente pentru declanarea firelor singulare, dar ce se ntmpl dac un fir C rulnd n paralel cu firul B face ceva total diferit cu data din buffer? Atunci avem nevoie de un eveniment care se reseteaz automat pentru a elibera firele B i C deoarece un eveniment cu resetare automat ar elibera fie pe unul, fie pe altul, dar nu pe amndou. Codul pentru a declana dou sau mai multe fire cu ajutorul unui eveniment cu resetare manual este urmtorul: // Date globale //Nesemnalat, cu resetare manual CEvent g_event (FALSE, TRUE); 112

// Firul A // Iniializarea bufferului InitBuffer (& buffer); // Eliberm firele B i C g_event.PulseEvent (); // Firul B g_event.Lock (); //Firul C g_event.Lock (); // Ateptm semnalul // Ateptm semnalul

Trebuie s observm c firul A utilizeaz PulseEvent pentru a aciona declanatorul, conform cu cea de-a doua regul descris mai sus. n concluzie, folosim evenimente care se reseteaz automat i CEvent::SetEvent pentru a elibera firele singulare blocate pe un eveniment i folosim evenimente cu resetare manual i CEvent::PulseEvent pentru a elibera fire multiple. Dac respectm aceste reguli, atunci evenimentele ne vor servi ntr-un mod capabil i de ncredere. Uneori evenimentele nu sunt folosite ca declanatoare, ci ca mecanisme primitive de semnalizare. De exemplu, poate firul B vrea s tie dac firul A a terminat o operaie, dar nu vrea s se blocheze dac rspunsul este negativ. Firul B poate verifica starea unui eveniment fr a se bloca trimind ctre ::WaitForSingleObject identificatorul de fir i o valoare de expirare de timp egal cu 0. Identificatorul firului poate fi extras din membrul de date m_hObject al clasei CEvent : if (::WaitForSingleObject ( g_event.m_hObject, 0) == WAIT_OBJECT_0) { // Evenimentul este semnalat } else { // Evenimentul nu este semnalat } Atunci cnd folosim un eveniment n acest mod trebuie s fim ateni deoarece dac firul B verific evenimentul n mod repetat pn cnd devine setat, trebuie s ne asigurm ca evenimentul este cu resetare manual i nu unul cu resetare automat. n caz contrar, chiar actul verificrii evenimentului l va reseta. Mutexuri n SDK Obiectele nucleu mutex asigur accesul mutual exclusiv asupra al unui fir asupra unei resurse. De fapt, astfel si-a primit mutexul numele. Un mutex conine un contor de utilizare, un ID de fir i un contor de revenire. Mutexurile se comport identic cu sectiunile critice, dar mutexurile sunt obiecte nucleu, pe cnd seciunile critice sunt obiecte utilizator. Acest lucru nseamn c mutexurile sunt mai lente dect seciunile critice. Dar de asemenea nseamn ca fire din diferite procese pot accesa un singur mutex i c un fir poate preciza o valoare de timeout cnd ateapt o resurs. ID-ul de fir identific care fir din sistem este proprietarul mutexului n acel moment, iar contorul de rentoarcere indic numrul de ori de care firul a fost proprietarul mutexului. Mutexurile au mai multe utilizri i sunt printre cele mai folosite obiecte nucleu. n mod tipic, ele sunt folosite pentru a pzi un block de memorie care este accesat de mai multe fire de execuie. Dac acele fire ar accesa acel bloc de memorie simultan, data din acel bloc ar fi corupt. Mutexurile asigur c fiecare fir care acceseaz blocul de memorie are acces exclusiv aasupra blocului astfel inct integritatea datelor este meninut. Regulile pentru mutexuri sunt urmtoarele : dac ID-ul de fir este 0 (un ID invalid de fir), mutexul nu este deinut de nici un fir i este semnalat; dac ID-ul de fir este diferit de 0, un fir este propritarul mutexului i mutexul este semnalat; 113

spre deosebire de toate celelalte obiecte nucleu, mutexurile au cod special n sistemul de operare care le permite s ncalce regulile normale. Pentru a folosi un mutex, un proces trebuie mai nti s creeze mutexul apelnd CreateMutex : HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL fInitialOwner, PCTSTR pszName);

Un alt proces poate obine un identificator relativ la el nsusi, la un mutex existent apelnd OpenMutex : HANDLE OpenMutex(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName); Parametrul fInitialOwner controleaz starea iniial a mutexului. Dac trimitem FALSE (in cele mai multe cazuri), att ID-ul de fir ct i contorul de revenire sunt setate la 0. Acest lucru nseamn c mutexul nu este n posesia nimnui i de aceea este semnalat. Dac trimitem TRUE pentru fInitialOwner, ID-ul de fir al obiectului este setat la ID-ul firului i contorul de revenire este setat la 1. Deoarece ID-ul firului este diferit de 0, mutexul este iniial nesemnalat. Un fir poate primi acces la o resurs comun apelnd o funcie wait, creia i transmite identificatorul unui mutex care pzete resursa. Intern, funcia wait verific ID-ul firului pentru a vedea dac este egal cu 0 (mutexul este semnalat). Dac ID-ul firului este 0, parametrul de ID de fir este setat la ID-ul firului apelant, contorul de revenire este iniializat cu 1, iar firul apelant rmne planificabil. Dac funcia wait detecteaz c ID-ul de fir nu este 0 (mutexul este nesemnalat), firul apelant intr n starea de ateptare. Sistemul ine minte acest lucru i atunci cnd ID-ul de fir al mutexului redevine 0, sistemul seteaz ID-ul de fir la ID-ul firului n ateptare, seteaz contorul de revenire la 1 i permite firului n ateptare s fie planificabil din nou. Ca ntotdeauna, aceste verificri i schimbri asupra obiectului nucleu mutex sunt efectuate atomic. Pentru mutexuri, exist o excepie special de la regulile normale ale unui obiect semnalat sau nesemnalat. S presupunem c un fir ncearc s atepte un obiect mutex nesemnalat. n acest caz, firul este de obicei plasat n starea de ateptare. Totui, sistemul verific s vad dac firul care ncearc s preia controlul mutexului are acelai ID de fir ca n interiorul obiectului nucleu. Dac cele 2 ID-uri coincid, sistemul permite firului s rmn planificabil chiar dac mutexul era nesemnalat. Acest comportament nu l vom ntlni la nici un alt tip de obiecte nucleu. De fiecare dat cnd un fir ateapt cu succes un mutex, contorul de revenire al firului este incrementat. Singura modalitate ca s avem contorul de revenire mai mare de 1 este ca firul s atepte acelai mutex de mai multe ori, profitnd de aceast excepie de la regul. O dat ce un fir a ateptat cu succes un mutex, firul tie c are acces exclusiv la resursa protejat. Orice alt fir care ncearc s preia accesul la resurs (ateptnd la un mutex oarecare) este plasat n starea de ateptare. Atunci cnd firul care este proprietarul n acel moment al resursei nu mai are nevoie de aceasta, trebuie s elibereze mutexul apelnd ReleaseMutex : BOOL ReleaseMutex(HANDLE hMutex); Aceast funcie decrementeaz contorul de revenire al obiectului cu 1. Dac firul i termin cu succes ateptarea unui mutex de mai multe ori, acel fir trebuie s apeleze ReleaseMutex de acelai numr de ori nainte ca, contorul de recursie s devin 0. Atunci cnd contorul de recursie devine 0, ID-ul firului este la rndul lui setat la 0 i obiectul devine semnalat. Atunci cnd obiectul devine semnalat, sistemul verific dac orice alt fir de execuie ateapt mutexul. Dac da, sistemul alege n mod corect unul din firele care ateapt i l las s preia controlul mutexului. Acest lucru nseamn, bineneles, c ID-ul firului este setat la ID-ul firului selectat i contorul de recursie este setat la 1. Dac nici un alt fir nu ateapt acel mutex, mutexul st n starea semnalat astfel nct urmtorul fir care ateapt mutexul l primete imediat.

114

Probleme legate de abandonare Obiectele mutex sunt diferite de celelalte obiecte nucleu deoarece ele au o noiune de proprietate a firului de execuie. Nici unul din celelalte obiecte nucleu pe care le-am discutat nu reine ce fir de execuie l-a ateptat cu succes; doar mutexurile rein acest lucru. Acest concept pentru mutexuri reprezint motivul pentru care mutexurile au o excepie special de la regul care permite unui fir de execuie s preia controlul unui mutex chiar i atunci cnd acesta nu este semnalat. Aceast excepie nu se aplic doar unui fir de execuie care ncearc s preia controlul unui mutex, ci de asemenea i firelor care ncearc s elibereze un mutex. Atunci cnd un fir apeleaz ReleaseMutex, funcia verific s vad dac ID-ul firului apelant coincide cu ID-ul de fir n obiectul mutex. Dac cele dou ID-uri coincid, contorul de utilizare este decrementat ca mai jos. Dac ele nu coincid, ReleaseMutex nu face nimic i returneaz FALSE (indicnd eecul) apelantului. Efectuarea unui apel al funciei GetLastError n acest moment va returna ERROR_NOT_OWNER (ncercarea de a elibera un mutex care nu este deinut de apelant). n concluzie, dac un fir care este proprietarul unui mutex i ncheie execuia (folosind ExitThread, TerminateThread, ExitProcess sau TerminateProcess) nainte de a elibera mutexul, ce se ntmpl cu mutexul i cu celelalte fire care ateapt acel mutex ? Rspunsul este c sistemul consider c mutexul este abandonat firul care l controla nu l mai poate elibera deoarece el i-a ncetat execuia. Deoarece sistemul pstreaz evidena tuturor mutexurilor i a obiectelor nucleu, el tie exact cnd mutexurile devin abandonate. ntr-un astfel de caz, sistemul reseteaz automat ID-ul de fir al mutexului la 0 i contorul de recursie la 0. Apoi, sistemul verific s vad dac exist fire ce ateapt mutexul. n caz afirmativ, sistemul alege n mod corect un fir n ateptare, seteaz ID-ul de fir la ID-ul firului apelant i seteaz contorul de recursie la 1; firul selectat devine planificabil. Aceeai situaie aveam i mai devreme cu excepia faptului c funcia wait nu returneaz valoarea normal WAIT_OBJECT_0 firului. n schimb, ea returneaz valoarea WAIT_ABANDONED. Aceast valoare special de retur (care se aplic doar mutexurilor) indic faptul c mutexul pe care l atepta firul era n posesia unui alt fir care i-a ncetat execuia nainte de a termina de folosit resursa comun. Aceast situaie este delicat deoarece firul planificat nu are nici o idee despre integritatea resursei comune. n acest caz este de datoria noastr ca dezvoltatori sa decidem ce trebuie fcut. n viaa real, majoritatea aplicaiilor nu verific n mod explicit pentru valoarea de retur WAIT_ABANDONED deoarece foarte rar un fir i ncheie brusc execuia. Mutexuri n MFC Cuvntul mutex vine de la cuvintele mutual i exclusiv. La fel ca seciunile critice, mutexurile sunt folosite pentru a cpta acces exclusiv la o resurs mprit ntre dou sau mai multe fire de execuie. Spre deosebire de seciunile critice, mutexurile pot fi folosite pentru a sincroniza firele rulnd n cadrul aceluiai proces sau n cadrul unor procese diferite. Seciunile critice sunt n general preferate n locul mutexurilor pentru sincronizarea firelor n cadrul unui proces deoarece seciunile critice sunt mai rapide, dar dac vrem s sincronizm fire rulnd n dou sau mai multe procese, mutexurile reprezint soluia cea mai bun. S presupunem c dou aplicaii folosesc un bloc de memorie comun pentru a interschimba date. n cadrul acelei memorii comune exist o list nlnuit care trebuie protejat mpotriva acceselor unor fire concurente. Seciunea critic A nu va funciona deoarece nu poate ajunge n afara granielor procesului, dar un mutex va reui s fac acest lucru. Iat ce trebuie s facem nainte de a scrie sau de a citi n lista nlnuit : //Date globale CMutex g_mutex (FALSE, _T (MyMutex)); ... g_mutex.Lock(); // Citire sau scriere n list g_mutex.Unlock(); Primul parametru transmis ctre constructorul CMutex specific dac mutexul este iniial blocat (TRUE) sau deblocat (FALSE). Al doilea parametru desemneaz numele mutexului, care este obligatoriu dac mutexul este folosit pentru a sincroniza fire n dou procese diferite. Noi alegem numele, dar ambele procese trebuie s precizeze acelai nume astfel nct cele dou obiecte CMutex s referenieze acelai obiect mutex n nucleul 115

Windows. n mod normal, Lock un fir se blocheaz pe un mutex blocat de alt fir i Unlock elibereaz mutexul pentru ca alte fire s poat s l blocheze. Implicit, Lock va atepta pentru o perioad nedefinit de timp pentru ca un mutex s devin liber. Putem s construim un mecanism cu anse mici de eec specificnd un timp maxim de ateptare n secunde. n urmtorul exemplu, firul ateapt pn la 1 minut nainte de accesa resursa protejat de mutex. g_mutex.Lock (60000); // Citire sau scriere n lista nlnuit g_mutex.Unlock(); Valoarea returnat de Lock ne spune de ce apelul funciei a returnat. O valoare diferit de zero nseamn c mutexul s-a eliberat, iar zero indic faptul c timpul de ateptare a expirat primul. Dac funcia Lock returneaz 0, este n mod normal prudent s nu accesm resursa comun deoarece ar putea s conduc la un acces suprapus. Astfel, codul care folosete facilitatea de timp maxim de ateptare este n mod uzual structurat astfel : if (g_mutex.Lock (60000)) { // Citire sau scriere n lista nlnuit g_mutex.Unlock(); } Exist o deosebire ntre mutexuri i seciuni critice. Dac un fir blocheaz o seciune critic i se termin fr s o elibereze, celelalte fire de execuie care ateapt ca seciunea critic s se elibereze se vor bloca pentru o perioad nedefinit de timp. Totui, dac un fir de execuie care blocheaz un mutex nu reuete s l deblocheze nainte de a se termina, sistemul consider c mutexul este abandonat i l elibereaz automat astfel nct celelalte fire n ateptare s i poat continua execuia. Semafoare n SDK Semafoarele sunt folosite pentru numrarea resurselor. Ele conin un contor de utilizare, aa cum fac toate obiectele nucleu, dar ele conin de asemenea dou valori suplimentare pe 32 de bii; un contor maxim de resurse i contorul de resurse curent. Contorul maxim de resurse identific numrul maxim de resurse pe care l poate controla semaforul.; contorul curent de resurse indic numrul de resurse care sunt disponibile. Pentru a nelege mai bine acest mecanism, s analizm modul n care o aplicaie poate folosi semafoarele. S spunem c dezvoltm un proces server n care am alocat un buffer care poate pstra cererile clienilor. Am codat mrimea bufferului astfel nct poate pstra un numr de maxim de cinci clieni la un moment dat. Dac un nou client ncearc s contacteze serverul n timp ce cinci cereri sunt nerezolvate, clientul este respins, este generat o eroare care indic faptul c serverul este ocupat si clientul trebuie s ncerce mai trziu. Atunci cnd procesul server se iniializeaz, el creeaz un stoc de fire ( thread pool ) format din cinci fire, fiecare fir fiind pregtit s proceseze cereri de la clieni individuali pe msur ce apar. Iniial, nici un client nu a fcut nici o cerere, astfel nct serverul nu permite nici unui fir s fie planificabil. Totui, dac trei clieni fac cereri simultane, trei fire din acest stoc de fire trebuie s fie planificabile. Putem trata aceast monitorizare a resurselor i planificarea firelor foarte simplu folosind un semafor : contorul maxim de resurse este setat la 5 deoarece aceasta este mrimea bufferului nostru n acest exemplu. Contorul iniial de resurse este 0 deoarece nici un client nu a fcut nici o cerere. Pe msur ce cererile clienilor sunt acceptate, contorul curent de resurse este incrementat, iar dup ce cererile sunt satisfcute, contorul este decrementat. Regulile pentru un semafor sunt urmtoarele : dac contorul curent de resurse este mai mare de 0, semaforul este semnalat; dac contorul curent de resurse este 0, semaforul este nesemnalat; sistemul nu permite niciodat contorului curent de resurse s fie negativ; contorul curent de resurse nu poate fi niciodat mai mare dect contorul maxim de resurse. Atunci cnd folosim un semafor, nu trebuie s confundm contorul de utilizare al obiectului cu contorul curent de resurse. Aceast funcie creeaz obiectul nucleu semafor :

116

HANDLE CreateSemaphore(

PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);

Un alt proces poate obine identificatorul relativ la sine al unui semafor existent apelnd OpenSemaphore : HANDLE OpenSemaphore(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName); Parametrul lMaximumCount precizeaz sistemului numrul maxim de resurse pe care le poate trata aplicaia noastr. Deoarece acesta este o valoare cu semn pe 32 de bii, putem avea 2147483647 resurse. Parametrul lInitialCount precizeaz cte dintre aceste resurse sunt iniial disponibile. La iniializarea procesului server pe care lam creat, nu exist cereri sin partea clienilor, deci apelm CreateSemaphore astfel: HANDLE hsem = CreateSemaphore(NULL, 0, 5, NULL); Aceast funcie creeaz un semafor cu un contor maxim de resurse egal cu 5, dar iniial sunt disponibile 0 resurse. ntmpltor, contorul obiectului nucleu este 1 deoarece de abia am creat acest obiect nucleu; nu trebuie s facem o confuzie ntre contoare. Deoarece contorul curent de resurse este iniializat cu 0, semaforul este nesemnalat. Orice fir care ateapt acel semafor sunt astfel puse n starea de ateptare. Un fir primete acces la resurs apelnd o funcie wait, creia i transmite identificatorul semaforului care pzete resursa. Intern, funcia wait verific contorul curent de resurse al semaforului i dac acesta este mai mare dect 0 (semaforul este semnalat), contorul este decrementat cu 1 i firul apelant rmne planificabil. Cel mai bun la lucru la semafoare este c ele efectueaz aceast operaie de test i setare atomic; adic, atunci cnd cerem o resurs de la un semafor, sistemul de operare verific dac resursa este disponibil i decrementeaz contorul de resurse disponibile fr a lsa alt fir s interfereze cu acest lucru. Doar dup ce contorul de resurse a fost decrementat, sistemul permite unui alt fir s cear acea resurs. Dac funcia wait determin c contorul curent de resurse al semaforului este 0 (semaforul este nesemnalat), sistemul pune firul apelant n starea de ateptare. Atunci cnd un alt fir incrementeaz contorul curent de resurse al semaforului, sistemul i aduce aminte de firul din starea de ateptare i i permite acestuia s devin planificabil (decrementnd corespunztor contorul curent de resurse). Un fir incrementeaz contorul curent de resurse al unui semafor apelnd funcia ReleaseSemaphore : BOOL ReleaseSemaphore(HANDLE hsem, LONG lReleaseCount, PLONG plPreviousCount); Aceast funcie pur i simplu adaug valoarea din lReleaseCount la contorul curent de resurse al semaforului. n mod normal, transmitem valoarea 1 pentru parametrul lReleaseCount, dar acest lucru nu este absolut necesar. Funcia returneaz de asemenea valoarea iniial a contorului curent de resurse n *plPreviosCount. Puine aplicaii in cont de aceast valoare, astfel nct putem transmite NULL pentru a o ignora. Uneori este folositor s tim c contorul curent de resurse al unui semafor fr s modificm contorul, dar nu exist nici o funcie care face acest lucru. Semafoare n MFC Un alt tip de obiecte de sincronizare l reprezint semafoarele. Evenimentele, seciunile critice i mutexurile sunt obiect de tipul totul sau nimic n sensul c Lock se blocheaz pe ele dac orice alt fir le are deja blocate. Semafoarele sunt diferite. Semafoarele menin contoarele de resurse reprezentnd numrul resurselor disponibile. Blocarea unui semafor decrementeaz contorul acestuia de resurse, iar blocarea lui incrementeaz 117

acelai contor. Un fir se blocheaz doar dac ncearc s blocheze un semafor avnd contorul de resurse egal cu 0. n acest caz, firul se blocheaz pn cnd alt fir deblocheaz semaforul, incrementnd contorul de resurse, sau pn la expirarea unei perioade specificate. Semafoarele pot fi folosite pentru a sincroniza fire din cadrul unui proces sau din cadrul unor procese diferite. MFC-ul reprezint semafoarele cu instanele clasei CSemaphore. Prototipul constructorului acestei clase este : CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL ); Declaraia urmatoare CSemaphore g_semaphore (3, 3); construiete un obiect de tip semafor care are contorul de resurse iniial egal cu 3 (parametrul 1) i contorul maxim egal cu 3 (parametrul 2). Dac semaforul va fi folosit pentru a sincroniza fire din procese diferite, va trebui s adugm un al treilea parametru care atribuie un nume semaforului. Un al patrulea parametru opional pointeaz ctre o structur de tip SECURITY_ATTRIBUTES (valoarea implicit este NULL). Fiecare fir care acceseaz o resurs controlat de un semafor poate proceda n modul urmtor : g_semaphore.Lock (); // Accesarea resursei comune g_semaphore.Unlock (); Att timp ct nu mai mult de trei fire ncearc s acceseze resursa n acelai timp, Lock nu va suspenda firul. Dar dac semaforul este blocat de trei fire i un al patrulea apeleaz Lock, firul se va bloca pn cnd unul din celelalte trei fire apeleaz Unlock. Pentru a limita timpul n care funcia Lock ateapt ca valoarea contorului de resurse s devin pozitiv, putem s transmitem o valoare maxima de ateptare (n milisecunde, ca ntotdeauna) funciei Lock. CSemaphore::Unlock poate fi folosit pentru a incrementa contorul de resurse cu mai mult de 1 i de asemenea pentru a afla care a fost contorul de resurse nainte de apelul Unlock. De exemplu, s presupunem c acelai fir apeleaz Lock de dou ori la rnd pentru a revendica dou resurse protejate de un semafor. n loc s apelm Unlock de dou ori, firul poate face acest lucru astfel: LONG lPrevCount; g_semaphore.Unlock (2, & lPrevCount); Nu exist funcii nici n MFC nici n API care s returneze contorul de resurse al unui semafor, altele dect CSemaphore::Unlock i echivalentul din API ::ReleaseSemaphore. O folosire uzual pentru semafoare este de a permite unui grup de m fire accesul la n resurse, unde m este mai mare dect n. De exemplu, s presupunem c lansm 10 fire de lucru i le dm la fiecare sarcina de a aduna date. Ori de cte ori un fir umple un buffer cu date, el transmite datele printr-un socket, elibereaz bufferul i rencepe operaia de strngere de date. Acum s presupunem c doar trei socketuri sunt disponibile la un moment dat. Dac noi protejm aceste socketuri cu un semafor al crui contor de resurse este 3 i programm fiecare fir astfel nct s blocheze semaforul nainte de a pretinde accesul la socket, atunci firele nu vor consuma deloc timp din procesor att timp ct ateapt ca un socket s devin liber. Clasele CSingleLock i CMultiLock MFC-ul include o pereche de clase numite CSingleLock i CMultiLock care au funciile Lock i Unlock proprii. Putem mpacheta o seciune critic, un mutex, un eveniment sau un semafor ntr-un obiect CSingleLock i putem folosi CSingleLock::Lock pentru a aplica un blocaj, ca mai jos : CCriticalSection g_cs; // mpachetarea ntr-un CSingleLock 118

CSingleLock lock ( & g_cs); // Blocarea seciunii critice lock.Lock(); Exist vreun avantaj pentru blocarea seciunii critice n acest mod n locul apelrii directe a funciei Lock a obiectului CCriticalSection ? Uneori, da. S considerm ceea ce se ntmpl dac urmtorul cod arunc o excepie ntre apelurile Lock i Unlock : g_cs.Lock (); ... g_cs.Unlock (); n cazul n care apare o excepie, seciunea critic va rmne blocat definitiv deoarece apelul ctre Unlock nu va mai avea loc. Putem face ns n felul urmtor : CSingleLock (&g_cs); lock.Lock (); ... lock.Unlock (); Seciunea critic nu rmne blocat definitiv. De ce? Deoarece obiectul CSingleLock este creat n stiv, destructorul su este apelat dac apare o excepie. Acest destructor apeleaz Unlock asupra obiectului de sincronizare blocat. Cu alte cuvinte, CSingleLock este un instrument foarte util pentru a ne asigura c un obiect de sincronizare este deblocat chiar i n cazul apariiei unor excepii. CMultiLock este cu totul diferit. Folosind un CMultiLock, un fir poate bloca pe mai multe obiecte de sincronizare n acelai timp (pn la 64). n funcie de cum apeleaz CMultiLock::Lock, un fir se poate bloca pn cnd unul din obiectele de sincronizare devine liber sau pn cnd toate devin libere. CMultiLock::Lock accept trei parametri, toi opionali. 1. Primul specific o perioad maxim de ateptare (implicit INFINITE). 2. Al doilea parametru specific dac firul trebuie s fie trezit cnd unul din obiectele de sincronizare este deblocat (FALSE) sau cnd toate devin deblocate (TRUE, implicit). 3. Al treilea este o masc de trezire care specific alte condiii care vor trezi firul, de exemplu mesaje WM_PAINT sau mesaje generate de butoanele mouse-ului. Valoarea implicit pentru acest parametru egal cu 0 previne trezirea firului din alte motive dect din cauz eliberrii unuia sau mai multe obiecte de sincronizare sau a expirrii perioadei de ateptare. Urmtorul exemplu demonstreaz cum un fir se poate bloca pe dou evenimente i un mutex simultan. Trebuie s fim contieni de faptul c evenimentele, mutexurile i semafoarele pot fi mpachetate n obiecte CMultiLock, dar seciunile critice nu pot fi. CMutex g_mutex ; CEvent g_event [2]; CSyncObject* g_pObjects [3] = { &g_mutex, &g_event [0], &g_event [1]}; // Blocare pn cnd toate cele trei obiecte // devin semnalate CMultiLock multiLock (g_pObjects, 3); multiLock.Lock (); // Blocare pn cnd unul din obiecte devine semnalat CMultiLock multiLock (g_pObjects, 3); multiLock.Lock (INFINITE, FALSE);

119

Dac un fir este deblocat dup apelul CMultiLock::Lock pentru a se bloca pn cnd doar un obiect de sincronizare devine semnalat, este foarte frecvent cazul n care firul va trebui s tie ce obiect de sincronizare devine semnalat. Rspunsul poate fi obinut din valoarea returnat de Lock : CMutex g_mutex ; CEvent g_event [2] ; CSyncObject* g_pObjects [3] = {&g_mutex, &g_event[0], & g_event[1]}; CMultiLock multiLock (g_pObjects, 3) ; DWORD dwResult = multiLock.Lock (INFINITE, FALSE); DWORD nIndex = dwResult WAIT_OBJECT_0 ; if ( nIndex == 0) { // Mutexul a devenit semnalat. } else { if ( nIndex == 1) { // Primul eveniment a devenit semnalat. } else if ( nIndex == 2) { // Al doilea eveniment a devenit semnalat. } } Trebuie s fim contieni c dac trimitem funciei Lock o valoare de expirare, alta dect INFINITE, trebuie s comparm valoarea returnat cu WAIT_TIMEOUT nainte de a scdea WAIT_OBJECT_0 n cazul n care Lock a returnat din cauza expirrii perioadei de ateptare. De asemenea, dac Lock returneaz deoarece un mutex abandonat a devenit semnalat, trebuie s scdem WAIT_ABANDONED_0 din valoarea returnat n loc de WAIT_OBJECT_0. n continuare vom prezenta un exemplu de o situaie n care CMultiLock poate fi folositoare. S presupunem c trei fire separate, firele A, B, C, lucreaz mpreun pentru a pregti date ntr-un buffer. ndat ce datele sunt pregtite, firul D trimite datele prin intermediul unui socket sau le scrie ntr-un fiier. Totui, firul D nu poate fi apelat pn cnd firele A, B i C nu i-au terminat operaiile curente. Soluia ? S crem obiecte eveniment separate pentru a reprezenta firele A, B i C i s lsm firul D s foloseasc un obiect CMultiLock pentru a se bloca pn cnd toate cele trei evenimente devin semnalate. Pe msur ce fiecare fir i termin munca, el seteaz obiectul eveniment corespunztor la o stare semnalat. Firul D se blocheaz din aceast cauz pn cnd ultimul din cele trei semnale de fir este terminat. Funciile Wait Funciile wait permit unui fir s intre voluntar n starea de ateptare pn cnd obiectul nucleu specificat devine semnalat. Pe departe cea mai ntlnit funcie din aceast familie este DWORD WaitForSingleObject ( HANDLE hObject, DWORD dwMilliseconds); Atunci cnd un fir apeleaz aceast funcie, primul parametru, hObject, identific un obiect nucleu care suport s fie semnalat/nesemnalat. Un obiect menionat n list n seciunea precedent funcioneaz excelent. Al doilea parametru, dwMilliseconds, permite firului s precizeze ct timp este dispus s atepte pentru ca obiectul s devin semnalat. Urmtoarea funcie spune sistemului c firul apelant vrea s atepte pn cnd procesul identificat de hProcess i termin execuia : WaitForSingleObject (hProcess, INFINITE); 120

Al doilea parametru spune sistemului c firul apelant este dispus s atepte la infinit pn cnd procesul i termin execuia. De obicei transmitem INFINITE pentru al doilea parametru, dar putem trimite orice valoare (n milisecunde). INFINITE este definit ca 0xFFFFFFFF (sau -1). Bineneles, poate fi periculos s trimitem INFINITE. Dac obiectul nu devine niciodat semnalat, atunci firul apelant nu se trezete este blocat, din fericire fr a consuma resurse de procesor. n continuare avem un exemplu pentru funcia WaitForSingleObject cu al doilea parametru diferit de INFINITE : DWORD dw = WaitForSingleObject(hProcess, 5000); switch (dw) { case WAIT_OBJECT_0: // Procesul i termin execuia. break; case WAIT_TIMEOUT: // Procesul nu s-a terminat n 5000 de milisecunde. break; case WAIT_FAILED: // Apel greit al funciei (identificator invalid). break; } Codul de mai sus spune sistemului c firul apelant nu trebuie s fie planificabil pn cnd fie procesul specificat s-a terminat sau au trecut 5000 de milisecunde (care are loc prima). Astfel acest apel returneaz n mai puin de 5000 de milisecunde dac procesul se termin sau returneaz n 5000 de milisecunde dac procesul nu s-a terminat. Trebuie s reinem c putem transmitem 0 pentru parametrul dwMilliseconds. n acest caz, WaitForSingleObject returneaz imediat. Valoarea returnat de aceast funcie indic de ce firul apelant a devenit planificabil din nou. Dac obiectul pe care firul l ateapt devine semnalat, valoarea returnat este WAIT_OBJECT_0; dac perioada de timp expir, valoarea returnat este WAIT_TIMEOUT. Dac i transmitem un parametru invalid, valoarea returnat este WAIT_FAILED. Pentru mai multe informaii putem apela GetLastError. Funcia de mai jos WaitForMultipleObjects este similar cu WaitForSingleObject n afara cazului n care firul apelant s verifice dac starea semnalat a mai multor obiecte nucleu simultan : DWORD WaitForMultipleObjects( DWORD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds); Parametrul dwCount indic numrul de obiecte nucleu pe care dorim s l verifice funcia noastr. Aceast valoare trebuie s fie ntre 1 i MAXIMUM_WAIT_OBJECT (definit ca 64 n fiierele de definiii ale Windows-ului). Parametrul phObjects este un pointer la un tablou de identificatori de obiecte nucleu. Putem folosi WaitForMultipleObjects n dou moduri diferite s permitem firului s intre n starea de ateptare pn cnd unul din obiectele nucleu precizate devine semnalat, sau s permitem unui fir s atepte pn cnd toate obiectele nucleu precizate devin semnalate. Parametrul fWaitAll spune funciei n ce mod d funcioneze. Dac trimitem valoarea TRUE pentru acest parametru, funcia nu va permite firului apelant s se execute pn cnd toate obiectele au devenit semnalate. Parametrul dwMilliseconds funcioneaz la fel ca i pentru WaitForSingleObject. Dac n timpul ateptrii perioada de timp expir, funcia returneaz oricum. Din nou, INFINITE este trimis n mod uzual pentru acest parametru, dar trebuie s scriem codul foarte ateni pentru a evita blocajele. Valoarea returnat de aceast funcie spune apelantului de ce a fost replanificat. Valorile posibile de retur sunt WAIT_FAILED i WAIT_TIMEOUT, a cror semnificaie este evident. Dac trimitem TRUE pentru fWaitAll i toate obiectele devin semnalate, valoarea returnat este WAIT_OBJECT_0. Dac trimitem FALSE 121

pentru fWaitAll, funcia returneaz de ndat ce oricare dintre obiecte devine semnalat. n acest caz, probabil c vrem s tim care obiect a devenit semnalat. Valoare de retur este o valoare ntre WAIT_OBJECT_0 i WAIT_OBJECT_0 + dwCount 1. n alte cuvinte, dac valoarea nu este WAIT_TIMEOUT sau WAIT_FAILE, trebuie s scdem WAIT_OBJECT_0 din valoarea de retur. Membru rezultat este un index n tabloul de identificatori pe care l-am trimis ca al doilea parametru funciei WaitForMultipleObjects . Indexul ne spune care obiect a devenit semnalat. HANDLE h[3]; h[0] = hProcess1; h[1] = hProcess2; h[2] = hProcess3; DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); switch (dw) { case WAIT_FAILED: // Apel greit al funciei (identificator greit ?). break; case WAIT_TIMEOUT: // Nici unul dintre obiecte nu a devenit semnalat // n 5000 de milisecunde. break; case WAIT_OBJECT_0 + 0: // Procesul identificat de h[0] (hProcess1) s-a terminat. break; case WAIT_OBJECT_0 + 1: // Procesul identificat de h[1] (hProcess1) s-a terminat. break; case WAIT_OBJECT_0 + 2: // Procesul identificat de h[2] (hProcess1) s-a terminat. break; } Dac trimitem valoarea FALSE pentru parametrul fWaitAll, WaitForMultipleObjects scaneaz identificatorul de la indexul 0 n sus, iar primul obiect care este semnalat termin ateptarea. Acest lucru poate avea nite ramificaii nedorite. De exemplu, firul nostru ar putea s atepte trei procese copil s se termine i trimite trei identificatori acestei funcii. Dac procesul de la indexul 0 se termin, funcia WaitForMultipleObjects returneaz. Acum firul poate face ce are dorete i apoi revine n bucl, ateptnd terminarea unui alt proces. Dac firul trimite aceleai trei argumente, funcia returneaz imediat cu WAIT_OBJECT_0 din nou. Dac nu nlturm identificatorii de la care am primit deja notificri, codul nu va funciona corect. Efectele secundare ale strii de ateptare Pentru unele obiecte nucleu, un apel reuit al funciei WaitForSingleObject sau WaitForMultipleObjects schim de fapt starea obiectului. Un apel reuit este unul n care funcia vede c obiectul era semnalat i returneaz o valoare relativ la WAIT_OBJECT_0. Un apel este nereuit dac funcia returneaz WAIT_TIMEOUT sau WAIT_FAILED. Obiectele nu i schimb starea niciodat dac apelul este nereuit. Atunci cnd starea unui obiect este schimbat, numim acesta un efect secundar de ateptare. De exemplu, s spunem c un fir ateapt un obiect eveniment autoresetabil. Atunci cnd acest obiect devine semnalat, funcia detecteaz acest lucru i poate returna WAIT_OBJECT_0 firului apelant. Totui, chiar nainte ca funcia s returneze, evenimentul este setat la starea nesemnalat efectul secundar al ateptrii reuite. Acest efect secundar este aplicat obiectului eveniment autoresetabil deoarece este una dintre regulile pe care Microsoft le-a definit pentru acest tip de obiect. Alte obiecte au efecte secundare diferite, iar unele obiecte nu au efecte secundare de loc. Obiectele nucleu proces i fir de execduie nu au nici un efect secundar adic ateptarea unui astfel de obiect nu schimb niciodat starea obiectului. Ceea ce face ca funcia WaitForMultipleObjects s fie att de util este c ea efectueaz toate operaiile sale intern. Atunci cnd un fir apeleaz WaitForMultipleObjects, funcia poate testa starea semnalat a tuturor obiectelor i poate efectua efectele secundare necesare toate ca o singur operaie. 122

HANDLE h[2]; h[0] = hAutoResetEvent1; // Initially nonsignaled h[1] = hAutoResetEvent2; // Initially nonsignaled WaitForMultipleObjects(2, h, TRUE, INFINITE); Atunci cnd apelm WaitForMultipleObjects, ambele obiecte eveniment sunt nesemnalate; acest lucru forteaz ambele fire s intre n starea de ateptare. Apoi obiectul hAutoResetEvent1 devine semnalat. Ambele fire observ c evenimentul a devenit semnalat, dar nici unul dintre ele nu se poate trezi deoarece obiectul hAutoResetEvent2 este n continuare nesemnalat. Deoarece nici unul din fire nu i-a incheiat cu succes ateptarea, nu are loc nici un efect secundar asupra obiectului hAutoResetEvent1. n continuare, obiectul hAutoResetEvent2 devine semnalat. n acest moment, unul din cele dou fire detecteaz c ambele obiecte pe care le atepta au devenit semnalate. Ateptarea este reuit, ambele obiecte sunt setate la starea nesemnalat, iar firul devine planificabil. Dar cum rmne cu cellalt fir ? El continu s atepte pn cnd observ c ambele obiecte eveniment sunt semnalate. Chiar dac la nceput a detectat c hAutoResetEvent1 era semnalat, acum vede acest obiect ca nesemnalat. Aa cum am menionat anterior, este important de reinut ca WaitForMultipleObjects funcioneaz n mod atomic. Atunci cnd verific starea obiectelor nucleu, nici un alt fir nu poate schimba starea acestora. Acest lucru previne apariia blocajelor. Altfel ne putem imagina ce s-ar putea ntmpla dac un fir vede ca hAutoResetEvent1 este nesemnalat i reseteaz evenimentul la starea nesemnalat i apoi ceclalt fir vede c hAutoResetEvent2 este semnalat i l reseteaz la nesemnalat. Ambele fire ar fi blocate : un fir va atepta un obiect care este deinut ce cellalt fir, i viceversa. WaitForMultipleObjects ne asigur c acest lucru nu va avea loc. Dac fire multiple de execuie ateapt un singur obiect nucleu, ce fir va hotr sistemul c trebuie trezit atunci cnd obiectul devine semnalat ? Rspunsul Microsoft la aceast ntrebare este : Algoritmul este corect.. Microsoft nu vrea s dezvluie algoritmul intern folosit de sistem. Tot ce trebuie s tim este c dac mai multe fire de execuie sunt ateptare, fiecare ar trebui s aib posibilitatea de a se trezi de fiecare dat cnd obiectul devine semnalat. Acest lucru nsemn c prioritatea firelor nu are nici un efect : firul cu prioritatea cea mai mare nu va primi n mod obligatoriu obiectul. De asemenea nseamn c firul care a ateptat cel mai mult va primi obiectul. Este posibil ca un fir care are obiectul s fac o bucl i s l aib din nou. Totui, acest lucru nu ar fi corect fa de celelalte fire, aa c algoritmul ncearc s previn acest lucru. Dar nu exist garanii. n realitate, alogoritmul folosit de Microsoft este simpla schem primul venit, primul ieit. Astfel, firul care a ateptat cel mai mult primete obiectul. Totui, pot aprea aciuni n sistem care s modifice acest comportament, fcndu-l mai greu de ghicit. De aceea Microsoft nu explic exact modul de funcionare al algoritmului. O astfel de aciune este suspendarea unui fir. Dac firul ateapt un obiect i apoi este suspendat, sistemul uit c firul ateapt un obiect. Aceasta are loc deoarece nu exist nici un motiv s planificm un fir care este suspendat. Atunci cnd firul i reia execuia mai trziu, sistemul crede c firul de abia a nceput s atepte acel obiect. Atunci cnd depanm un proces, toate firele din proces sunt suspendate atunci cnd atingem puncte de oprire. Astfel, depanarea unui proces face algoritmul primul intrat, primul ieit foarte imprevizibil deoarece foarte des firele sunt suspendate i apoi i reiau execuia.

123

In loc de ncheiere Analizai cu atenie proiectele puse la dispoziie. Pe baza acestor proiecte construii aplicaii simple n care s se vad c ai neles arhitectura bibliotecii MFC. Probleme Creati o aplicatie de tip Win32 (Windows cu SDK) in care s tratati evenimentul clic stanga mouse. Daca tasta SHIFT este apsata veti desena in zona client un dreptunghi, daca tasta Control este apasata veti desena un cerc, iar daca nu este apasata nici o tasta veti desena un dreptunghi umplut cu culoarea rosie. La clic dreapta mouse schimbati titlul ferestrei in Clic dreapta. 2. Creati o aplicatie de tip dialog (cu MFC, proiect AppWizard(exe)) in care sa aveti aceeasi functionalitate ca la problema 1. 3. Creati o aplicatie Windows bazata pe dialog. In caseta de dialog a aplicatiei veti adauga controale de editare (2) si butoane (2) necesare astfel incat sa realizati conversia din radiani in grade si invers. Fiecare buton va executa una din conversiile din grade in radiani sau din radiani in grade. 4. Rezultatul va fi afisat intr-un control static incorporat in acel dialog. 5. Aceeasi problema ca la 3, dar veti folosi un control de editare, un check box (grade / radiani) si un singur buton. Check box-ul va indica tipul de transformare pe care doriti sa o faceti. 6. Aceeasi problema ca la 4, dar in loc de check box veti folosi doua butoane radio. Un buton radio va indica transformarea din grade in radiani, celalalt din radiani in grade. 7. Creati o aplicatie bazata pe dialog in care veti incorpora urmatoarele controale: un combo box ce va contine literele HDD din calculator, un list box ce va afisa directoarele ce se gasesc pe un HDD. Cand selectam in combo box un HDD, in list box sa apara lista directoarelor. De asemenea caseta de dialog va mai contine si un control de editare in care veti plasa articolul selectat din list box sau din combo box. 8. Aceeasi problema ca la 6, dar articolele din list box si combo box sa fie colorate (textul sa aiba culoare). Culorile se vor alege folosind o functie ce genereaza valori pentru culori, in mod aleator. 9. Creati o aplicatie bazata pe dialog in care sa incorporati doua controale unul de tip treeview si altul de tip list view. Aplicatia trebuie sa imite Explorer (sau wincmd). 10. Aceeasi problema ca la 8, dar aplicatia este de tip SDI. Se va folosi clasa CSplitterWnd. Analizati si codul generat de o aplicatie de tip SDI, like Explorer. 11. Sincronizare. Creati o aplicatie in care sa aveti pe linga firul primar inca doua fire de lucru. Functionalitate: Firul primar va deschide un fisier, firele de lucru vor numara caracterele din acel fisier, respectiv cuvintele (spatiu se considera separator pentru cuvinte) si in final firul primar va afisa cate cuvinte si cate caractere sunt in acel fisier. 12. Reluati problemele 3, 4 si 5 in care sa folositi mecanismul de transmitere a informatiei de la control la ecran si invers, mecanism numit DDX (Dynamic Data Exchange). Pentru indicatii puteti descarca si o aplicatie de pe pagina (pdiag) care care foloseste DDX si explicatiile sunt date in cod. Urmariti comentariile. 1.

124

Cuprins Introducere.....................................................................................................................................................................1 Categorii de mesaje .....................................................................................................................................................11 GetMessage .................................................................................................................................................................16 SendMessage ...............................................................................................................................................................20 Bucla de mesaje ascuns ..........................................................................................................................................22 CObject........................................................................................................................................................................29 CCmdTarget::OnCmdMsg ..........................................................................................................................................32 CDocument..................................................................................................................................................................39 CView..........................................................................................................................................................................39 Harta de mesaje ...........................................................................................................................................................40 Construirea hrii de mesaje ........................................................................................................................................40 nelegerea comenzilor ................................................................................................................................................41 Crearea unei aplicaii Windows...................................................................................................................................43 Controale clasice..........................................................................................................................................................49 Clasa CButton ..............................................................................................................................................................50 Clasa CListBox ............................................................................................................................................................57 Clasa CComboBox (control combo box) .....................................................................................................................61 Atributele Contextului de Dispozitiv...........................................................................................................................65 Modul MM_TEXT ......................................................................................................................................................67 Setarea originilor .........................................................................................................................................................68 Desenarea pe ecran ......................................................................................................................................................69 Crearea unui meniu......................................................................................................................................................70 Incrcarea i afiarea unui meniu ................................................................................................................................71 Raspunsul la comenzile din meniu ..............................................................................................................................72 Intervale pentru comenzi .............................................................................................................................................72 Actualizarea articolelor intr-un meniu.........................................................................................................................73 Modificarea programatica............................................................................................................................................75 Meniul system .............................................................................................................................................................76 Meniuri contextuale. Mesajul WM_CONTEXTMENU.............................................................................................76 Obiecte nucleu .............................................................................................................................................................78 Ce sunt obiectele nucleu ? ...........................................................................................................................................78 Contorul de resurse......................................................................................................................................................78 Securitate .....................................................................................................................................................................78 Crearea unui obiect nucleu ..........................................................................................................................................79 nchiderea unui obiect nucleu......................................................................................................................................80 Partajarea obiectelor nucleu dincolo de graniele procesului.......................................................................................80 Motenirea identificatorilor obiectelor ........................................................................................................................81 Obiecte cu nume ..........................................................................................................................................................82 Spaiile de nume Terminal Server ...............................................................................................................................84 Funcia CreateProcess..................................................................................................................................................89 Oprirea execuiei unui proces ......................................................................................................................................89 Funcia ExitProcess .....................................................................................................................................................89 Funcia TerminateProcess............................................................................................................................................89 Fire de execuie............................................................................................................................................................91 Cnd crem un fir de execuie .....................................................................................................................................91 Cnd nu trebuie s creem un fir de execuie ..............................................................................................................91 Firele de execuie n MFC ...........................................................................................................................................91 Funcia de fir de execuie n SDK................................................................................................................................92 Funcia firului de execuie n MFC..............................................................................................................................92 Crearea unul fir de execuie de lucru n MFC..............................................................................................................93 Crearea unui fir de execuie cu interfa cu utilizatorul n MFC .................................................................................94 Suspendarea i repornirea firelor de execuie ..............................................................................................................95 Punerea unui fir de execuie n starea de sleep.........................................................................................................95 125

Terminarea unui fir de execuie n SDK......................................................................................................................96 Fire de execuie, procese i prioriti ...........................................................................................................................98 Prioritatea proceselor i a firelor de execuie...............................................................................................................99 Clasele de prioritate ale proceselor..............................................................................................................................99 Programarea prioritilor ...........................................................................................................................................101 Sincronizarea firelor de execuie ...............................................................................................................................102 Seciuni critice n SDK ..............................................................................................................................................102 Seciuni critice n MFC..............................................................................................................................................107 Sincronizarea firelor de execuie folosind obiecte nucleu .........................................................................................108 Evenimente n SDK ...................................................................................................................................................108 Evenimente n MFC...................................................................................................................................................111 Mutexuri n SDK .......................................................................................................................................................113 Probleme legate de abandonare .................................................................................................................................115 Mutexuri n MFC.......................................................................................................................................................115 Semafoare n SDK .....................................................................................................................................................116 Semafoare n MFC.....................................................................................................................................................117 Clasele CSingleLock i CMultiLock .........................................................................................................................118 Funciile Wait ............................................................................................................................................................120 Efectele secundare ale strii de ateptare...................................................................................................................122

Bibliografie
Charles Petzold- Programming Windows J. Prossie Programming Windows with MFC Second Edition J. Richter Advanced Windows MSDN

126

S-ar putea să vă placă și