Sunteți pe pagina 1din 126

1

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.
2. Consecventa privind interfata cu utilizatorul.
3. Multitasking-ul. Notiunea de proces si de fir de executie.
4. Gestionarea memoriei.
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.
2
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.

3
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 multitasking-
ul. 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.);
4
2. servicii utilizator (gestiunea elementelor de interfa ale utilizatorului cum ar fi ferestre, controale, dialoguri,
etc.);
3. 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.
5
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).

6
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. Bara de titlu
2. Bara de meniu
3. Toolbar
4. Meniul sistem
5. Barele de navigare (derulare) vertical si orizontal
6. Bara de stare
7. 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.
7
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 cbSize; // sizeof(WNDCLASSEX)
UINT style; // incep cu CS_
WNDPROC lpfnWndProc; // procedura fereastra
int cbClsExtra; // bytes extra pt. clasa
int cbWndExtra; // bytes extra pt. fereastra
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON 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
8
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 Action
CS_CLASSDC Aloc un context de dispozitiv ce poate fi partajat de toate
ferestrele din clas.
CS_DBLCLKS Trimite mesajul dublu clic procedurii ferestr.
CS_OWNDC 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, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
9
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 //iden
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, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
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 identifier
HANDLE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);

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:
10
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 Set if the shift key is down.

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.

11
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 This message has no parameters.
Return Values 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;
}
12

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;
...
}
13
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

14
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
15
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.
16
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, // address of structure with message
HWND hWnd, // handle of window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax // last message
);

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 Semnificatie
NULL
GetMessage regaseste mesajele pentru orice fereastra ce apartine firului din care este apelata si
mesaje "puse" (in coada de mesaje) cu ajutorul functiei PostThreadMessage.


17
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)
18
{
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;
19
2. se nregistreaz fereastra RegisterClass(&wndClass);
3. se creaz fereastra CreateWindow;
4. se stabileste modul de afiare al ferestrei ShowWindow(hwnd, nCmdShow);
5. se afieaz fereastra propriu zis UpdateWindow(hwnd);
6. 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 Actiune
GWL_EXSTYLE Regsete stilurile extinse ale ferestrei.
GWL_STYLE Regsete stilurile ferestrei.
GWL_WNDPROC Regsete adresa procedurii fereastr, sau un handle ce reprezint
adresa procedurii fereastr. Trebuie s folosim CallWindowProc
pentru a apela procedura fereastr.
GWL_HWNDPARENT Regste handle la fereastra printe, dac exist.
GWL_ID Regsete identificatorul ferestrei.
Mai multe detalii n MSDN.
Cnd hWnd identifica o caseta de dialog, pot fi folosite si urmtoarele valori:
Valoare Actiune
DWL_DLGPROC Regsete adresa procedurii casetei de dialog.
DWL_MSGRESULT 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)
20
Putem folosi functia CallWindowProc pentru a apela procedura fereastr.

LRESULT CallWindowProc(
WNDPROC lpPrevWndFunc, // pointer to previous procedure
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // 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, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);

21
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)"");


22
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.

23
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));
24
// 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.
25

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.

26
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();
27
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.

28
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.

29

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;
30
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:
31
// 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);
}

32
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 user-
interface 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".

33
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 disabled, depending
// on whether a CMyShape is currently selected in the view, that is,
// depending on whether CMyView::m_pActiveView is NULL. It is not
// necessary to implement an ON_UPDATE_COMMAND_UI handler to enable
// or disable the menu item.

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.

34
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
35
// 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;
36
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))
37
{
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;

38
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;
39
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.

40
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 macro-
ului 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.
41
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
42
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.
43
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.
44
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.
45
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
46
{
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
47
// 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 Action
None Start app and open new file.
Filename Start app and open file.
/p filename Start app and print file to default printer.
/pt filename printer driver port Start app and print file to the specified printer.
48
/dde Start app and await DDE command.
/Automation Start app as an OLE automation server.
/Embedding 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.
49
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 WNDCLASS Clasa MFC
Butoane "BUTTON" CButton
List box-uri "LISTBOX" CListBox
Controale de editare "EDIT" CEdit
Combo box-uri "COMBOBOX" CComboBox
Scroll bar-uri "SCROLLBAR" CScrollBar
Controale statice "STATIC" 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;
}
50
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 Description
BS_PUSHBUTTON Creaza un control standard de tip push button
BS_DEFPUSHBUTTON 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
BS_CHECKBOX Creaza un control de tip check box
BS_AUTOCHECKBOX Creaza un buton de tip check box care se autoseteaza / reseteaza
atunci cand este facut clic pe el (este de tip On/Off)
BS_3STATE Creaza un check box cu trei stari
BS_RADIOBUTTON Creaza un control de tip radio button
BS_AUTORADIOBUTTON Creaza un control de tip radio button control care se autoseteaza /
reseteaza atunci cand este facut clic pe el
51
BS_GROUPBOX Creaza un control de tip group box
In plus, putem adauga urmatoarele valori (OR pe biti) la stilul ferestrei controlului privitoare la alinierea textului ce
insoteste controlul:

Style Description
BS_LEFT Aliniere text la stanga
BS_CENTER Centrare text
BS_RIGHT 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.
52
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_VISIBLE
WS_GROUP BS_AUTORADIOBUTTON, rect1, this, IDC_COM1);
m_wndRadioButton2.Create (_T ("COM2"), WS_CHILD WS_VISIBLE
BS_AUTORADIOBUTTON, rect2, this, IDC_COM2);
m_wndRadioButton3.Create (_T ("COM3"), WS_CHILD WS_VISIBLE
BS_AUTORADIOBUTTON, rect3, this, IDC_COM3);
m_wndRadioButton4.Create (_T ("COM4"), WS_CHILD WS_VISIBLE
BS_AUTORADIOBUTTON, rect4, this, IDC_COM4);
m_wndRadioButton1.SetCheck (BST_CHECKED);

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.
53
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 Description
SS_BITMAP Afiseaza un bitmap
SS_ENHMETAFILE Afiseaza un metafisier
SS_ICON 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
54
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.
55
ES_AUTOVSCROLL Defilarea textului se face pe verticala fara a avea atasata bara de
navigare. Bara de navigare se ataseaza folosind stilul WS_VSCROLL.
ES_MULTILINE Creaza un CE multilinie
ES_LOWERCASE Afiseaza toate caracterele in lowercase
ES_UPPERCASE Ca mai sus, dar uppercase
ES_READONLY 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);

56
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.
57

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 Message-Map Macro
EN_UPDATE Textul din control este pe cale sa se schimbe ON_EN_UPDATE
EN_CHANGE Textul din control a fost schimbat ON_EN_CHANGE
EN_KILLFOCUS Controlul de editare a pierdut focusul de intrare ON_EN_KILLFOCUS
EN_SETFOCUS Controlul de editare a primit focusul de intrare ON_EN_SETFOCUS
EN_HSCROLL The edit control is scrolled horizontally using a
scroll bar.
ON_EN_HSCROLL
Pentru alte notificari ale CE consultati MSDN.

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
58
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 | WS_VISIBLE | LBS_STANDARD, rect, this,
IDC_LISTBOX);

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.

Stiluri pentru list box
Style Description
LBS_STANDARD 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 box-
urilor 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);

59
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.
60
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 Sent When Message-Map Macro LBS_NOTIFY
Required?
LBN_SETFOCUS List box-ul obtine focusul de
intrare
ON_LBN_SETFOCUS No
LBN_KILLFOCUS List box-ul pierde focusul de
intrare
ON_LBN_KILLFOCUS No
LBN_DBLCLK S-a facut dublu clic pe un
articol
ON_LBN_DBLCLK Yes
LBN_SELCHANGE S-a schimbat selectia ON_LBN_SELCHANGE Yes
61
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 Descriere
CBS_DROPDOWN Creaza un combo box drop-down.
CBS_DROPDOWNLIST Creaza un com box drop down list.
CBS_HASSTRINGS Vezi list box.
CBS_LOWERCASE Textul din combo box va fi lower case (vezi controlul de
62
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
Drop-
Down
Drop-Down
List
CBN_DROPDOWN
Trimis cand este afisat (CB drop-down list).
ON_CBN_DROPDOWN
CBN_CLOSEUP
Trimis cand CB drop-down list este nchis.
ON_CBN_CLOSEUP
CBN_DBLCLK

ON_CBN_DBLCLK
CBN_SELCHANGE
Trimis cand s-a schimbat selectia.
ON_CBN_SELCHANGE
CBN_SELENDOK
Trimis cand s-a facut o selectie.
ON_CBN_SELENDOK
Pentru alte notificari consultati MSDN.

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.
63

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).
64
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:
65

hdcMeta = CreateMetaFile(pszFileName);
...
hmf = CloseMetaFile(hdcMeta);

Atributele Contextului de Dispozitiv
Cele mai uzuale atribute ale DC sunt date in urmatorul tabel:

Attribute Default Set with Get with
Text color Black CDC::SetTextColor CDC::GetTextColor
Background color White CDC::SetBkColor CDC::GetBkColor
Background mode OPAQUE CDC::SetBkMode CDC::GetBkMode
Mapping mode MM_TEXT CDC::SetMapMode CDC::GetMapMode
Drawing mode R2_COPYPEN CDC::SetROP2
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
Axa x Axa y
MM_TEXT Pixel spre dreapta in jos
MM_LOMETRIC 0,1 mm spre dreapta in sus
MM_HIMETRIC 0,01 mm spre dreapta in sus
MM_LOENGLISH 0.01 inci spre dreapta in sus
MM_HIENGLISH 0.001 inci spre dreapta in sus
MM_TWIPS 1/1440 inci spre dreapta in sus
MM_ISOTROPIC arbitrar (x = y) Selectabil selectabil
MM_ANISOTROPIC Arbitrar (x!=y) Selectabil Selectabil

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..
66
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.
67
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);
68

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));
69

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.
70
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, ID_FILE_NEW
...
MENUITEM SEPARATOR
END
POPUP &View
BEGIN
...
END
END

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!).

71
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), WS_OVERLAPPEDWINDOW, rectDefault,
NULL, MAKEINTRESOURCE(IDR_MAINFRAME));

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();
72

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 ID-
urile 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()
{
73
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.

74
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
75

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
76
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;
77
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.

78

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;
79
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,
80
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.
81
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
82
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
83
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.
84
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.
85
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);
86
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
87
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);
88
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 general-
protection-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);

89
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);
90
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.
91
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 ntr-
un 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
92
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.
93
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
94
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 ()
95
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.
96
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 :
97
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 s-
a 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);
98
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
99
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.
100
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.

Prioritatea relativ a firelor de execuie Descriere
Time-critical Firul ruleaz la 31 pentru clasa de prioritate real-
time 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:


Clasa de prioritate a procesului Prioritatea reltiv
a firului Idle Below
Normal
Normal Above
Normal
High Real-
Time
Time-critical 15 15 15 15 15 31
Highest 6 8 10 12 15 26
Above normal 5 7 9 11 14 25
Normal 4 6 8 10 13 24
Below normal 3 5 7 9 12 23
Lowest 2 4 6 8 11 22
Idle 1 1 1 1 1 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.
101
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.
102
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.
103
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 :
104
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
105
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]; // O resurs comun.
TCHAR g_cChars[100]; // O alt resurs comun.
CRITICAL_SECTION g_cs; // 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:
106

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
107
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.)

108
// 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).
109
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);
110
}

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);
111
}
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
112
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);
113
// Firul A
// Iniializarea bufferului
InitBuffer (& buffer);
// Eliberm firele B i C
g_event.PulseEvent ();

// Firul B
g_event.Lock (); // Ateptm semnalul

//Firul C
g_event.Lock (); // 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;
114
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.
115
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
116
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 :


117


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 l-
am 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
118
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
119
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);

120
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);
121
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
122
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.

123
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.

124

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
1. 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.
125

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
126
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

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