28.01.2013 Views

1. Procese Windows; comunicaţii prin pipe şi IPC 2

1. Procese Windows; comunicaţii prin pipe şi IPC 2

1. Procese Windows; comunicaţii prin pipe şi IPC 2

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 1 -<br />

<strong>1.</strong> <strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> ________________________________ 2<br />

<strong>1.</strong><strong>1.</strong> Particularităţi privind dezvoltarea in cpp sub <strong>Windows</strong> __________________________ 2<br />

<strong>1.</strong><strong>1.</strong><strong>1.</strong> Constante <strong>şi</strong> tipuri de date ________________________________________________________ 2<br />

<strong>1.</strong><strong>1.</strong>2. Aplicaţii consolă ________________________________________________________________ 3<br />

<strong>1.</strong><strong>1.</strong>2.<strong>1.</strong> Exemplul 1: prima consolă ___________________________________________________ 3<br />

<strong>1.</strong><strong>1.</strong>2.2. Exemplul 2: un filtru ________________________________________________________ 4<br />

<strong>1.</strong><strong>1.</strong>3. Cum se compilează <strong>şi</strong> rulează programele cpp sub <strong>Windows</strong>? ____________________________ 4<br />

<strong>1.</strong>2. Access la fi<strong>şi</strong>ere <strong>şi</strong> aşteptarea unor evenimente <strong>Windows</strong> ________________________ 5<br />

<strong>1.</strong>2.<strong>1.</strong> Funcţii de access la fi<strong>şi</strong>ere ________________________________________________________ 5<br />

<strong>1.</strong>2.2. Funcţii de aşteptare a unor evenimente _____________________________________________ 6<br />

<strong>1.</strong>3. Lucrul cu procese sub <strong>Windows</strong> _____________________________________________ 7<br />

<strong>1.</strong>3.<strong>1.</strong> Crearea unui proces _____________________________________________________________ 7<br />

<strong>1.</strong>3.2. Terminarea unui proces <strong>Windows</strong> __________________________________________________ 8<br />

<strong>1.</strong>3.3. Exemple de utilizare de procese sub <strong>Windows</strong> ________________________________________ 9<br />

<strong>1.</strong>3.3.<strong>1.</strong> Exemplul 3: Rezumatul directorului curent ______________________________________ 9<br />

<strong>1.</strong>3.3.2. Exemplul 4: Cate perechi de numere nenule au suma numar par? __________________ 10<br />

<strong>1.</strong>3.3.3. Exemplul 5: Un program care compileaza si ruleaza alt program. ___________________ 11<br />

<strong>1.</strong>3.3.4. Exemplul 6: Prelucrarea simultana a mai multor fisiere text. _______________________ 12<br />

<strong>1.</strong>4. Pipe sub <strong>Windows</strong> _______________________________________________________ 13<br />

<strong>1.</strong>4.<strong>1.</strong> Pipe anonim <strong>Windows</strong> __________________________________________________________ 14<br />

<strong>1.</strong>4.2. Pipe cu nume sub windows ______________________________________________________ 14<br />

<strong>1.</strong>4.3. Exemplul 7: lista de fisiere cu nume de anumita forma; implementare <strong>pipe</strong> cu nume ________ 16<br />

<strong>1.</strong>5. Comunicarea între procese <strong>Windows</strong> <strong>prin</strong> memorie partajată ___________________ 20<br />

<strong>1.</strong>5.<strong>1.</strong> Fi<strong>şi</strong>ere I/O mapate în memorie <strong>şi</strong> segmente de memorie partajata _______________________ 20<br />

<strong>1.</strong>5.2. Semafoare <strong>Windows</strong> ____________________________________________________________ 22<br />

<strong>1.</strong>5.3. Exemplul 8: lista de fisiere cu nume de anumita forma; implementare memorie partajata<br />

+semafoare __________________________________________________________________________ 24<br />

<strong>1.</strong>6. Cozi de mesaje sub <strong>Windows</strong> ______________________________________________ 27<br />

<strong>1.</strong>6.<strong>1.</strong> Comunicarea între procese <strong>Windows</strong> <strong>prin</strong> Mailslot ___________________________________ 27<br />

<strong>1.</strong>6.2. Exemplul 9: lista de fisiere cu nume de anumita forma; implementare mailslot_____________ 29


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 2 -<br />

<strong>1.</strong> <strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong><br />

<strong>1.</strong><strong>1.</strong> Particularităţi privind dezvoltarea in cpp sub <strong>Windows</strong><br />

<strong>1.</strong><strong>1.</strong><strong>1.</strong> Constante <strong>şi</strong> tipuri de date<br />

Limbajul nativ de dezvoltare a aplicaţiilor <strong>Windows</strong> este C++. Din această cauză, în cele ce<br />

urmează vom descrie <strong>prin</strong>cipiile programării folosind construcţii C <strong>şi</strong> C++. Headerul<br />

conţine <strong>prin</strong>cipalele construcţii de limbaj folosite în interfaţa <strong>Windows</strong>.<br />

Constante. In se definesc o serie de constante. Numele acestora este<br />

compus din două părţi: o primă parte indică grupul din care face parte constanta, apoi<br />

caracterul “_” <strong>şi</strong> în final numele specific, de regulă suficient de lung încât să sugereze ce<br />

reprezintă. Iată câteva nume de grupe:<br />

CS stilul clasei de ferestre;<br />

CW crearea ferestrei;<br />

IDC identificator de cursor;<br />

IDI identificator de icon;<br />

DT atribut de afişare texte;<br />

WS stilul ferestrei;<br />

WM mesaj asociat unei ferestre<br />

De exemplu, pentru fixarea stilului unei clase se pot lega <strong>prin</strong> operatorul C „|‟ (sau)<br />

constantele de mai jos (care nu sunt singurele, mai pot fi <strong>şi</strong> altele):<br />

CS_DBLCLIKS - mesajele se transmit ferestrei <strong>prin</strong> dublu click;<br />

CS_HREDRAW sau CS_VREDRAW - provoacă redesenarea ferestrei după o<br />

redimensionare orizontală / verticală.<br />

Tipuri de date. <strong>Windows</strong> foloseşte o serie de tipuri de date noi, <strong>prin</strong> care s-a urmărit<br />

creşterea portabilităţii aplicaţiilor în cazul unor noi arhitecturi de calculatoare. Astfel, avem<br />

tipurile BOOL, BYTE, DWORD (32 biţi), FARPROC (pointer spre funcţie), LPSTR (pointer<br />

către string), LPMSG (pointer către o structură MSG) etc.<br />

Pentru desemnarea obiectelor sunt definite nişte tipuri de date speciale: descriptor sau<br />

handle. Acestea sunt întregi pe 16 biţi <strong>prin</strong> intermediul cărora se pot referi obiecte. Tipurile<br />

de handle folosite sunt:<br />

HANDLE indicator general;<br />

HBRUSH indicator spre obiect “pensulă”;<br />

HCURSOR indicator spre resursă cursor;<br />

HICON indicator spre icon;<br />

HMENU indicator spre o resursă meniu;<br />

HWND indicator spre o fereastră.<br />

Nume de variabile. Atribuirea de nume pentru variabile se face respectându-se anumite<br />

convenţii, provenite din experienţa programatorilor. Este vorba de notaţia ungară de<br />

denumire a variabilelor. Ele nu sunt restricţii impuse de sistem, dar este de preferat să fie<br />

respectate. De regulă numele atribuite sunt lungi, încep cu literă mică, iar în cadrul numelor


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 3 -<br />

apar litere mari la începuturile cuvintelor care le compun. De multe ori, când este vorba de o<br />

singură variabilă de un anumit tip, numele ei este numele tipului, scris cu literă mică.<br />

Tot ca <strong>şi</strong> convenţii, începuturile (prefixele) numelor de variabile au semnificaţie: b pentru<br />

BOOL, by pentru BYTE, c pentru char, dw pentru DWORD, fn pentru funcţie h pentru<br />

handle, i pentru int, lp pentru pointer lung, w pentru WORD etc.<br />

<strong>1.</strong><strong>1.</strong>2. Aplicaţii consolă<br />

In ceea ce priveşte programarea <strong>Windows</strong>, prezenta lucrare nu are, nici pe departe, intenţia de<br />

a o descrie exhaustiv. Vom aborda doar ceea ce este strict necesar pentru a crearea de procese<br />

<strong>şi</strong> comunicarea între ele.<br />

<strong>1.</strong><strong>1.</strong>2.<strong>1.</strong> Exemplul 1: prima consolă<br />

Cele mai simple aplicaţii care se pot scrie sub <strong>Windows</strong> sunt aplicaţiile consolă. Acestea sunt,<br />

în fapt, aplicaţii cu intrare <strong>şi</strong> ie<strong>şi</strong>re standard în mod text, la fel ca <strong>şi</strong> la programele simple sub<br />

Unix. Spre exemplu, un program extrem de simplu este programul PrimaConsola.cpp<br />

de mai jos:<br />

int main(int c, char* a[]) {<br />

int i;<br />

char n[120];<br />

<strong>prin</strong>tf("Numele? ");<br />

gets(n);<br />

<strong>prin</strong>tf("Salut %s\nUrmeaza parametrii liniei de comanda\n", n);<br />

for (i=0; a[i]; i++)<br />

<strong>prin</strong>tf("%s\n",a[i]);<br />

return 0;<br />

}<br />

Presupunem că în urma compilării <strong>şi</strong> link-editării acestui program s-a obţinut fi<strong>şi</strong>erul<br />

executabil PrimaConsola.exe. O execuţie a lui se face într-o fereastră MSDos Prompt<br />

(fereastră Cmd) de sub <strong>Windows</strong>. Dacă se lansează programul <strong>prin</strong> comanda:<br />

...> PrimaConsola 11111 doi trei333 4<br />

<strong>şi</strong> se dă la cerere numele, atunci efectul ei este ilustrat în figura de mai jos.


<strong>1.</strong><strong>1.</strong>2.2. Exemplul 2: un filtru<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 4 -<br />

Un al doilea exemplu de aplicaţie consolă simplă este un program filtru. Acesta citeşte linie<br />

cu linie de la intrarea standard <strong>şi</strong> dă la ie<strong>şi</strong>re acelea<strong>şi</strong> linii, scurtate la primele 10 caractere.<br />

Sursa Filtru.cpp este este prezentată în continuare.<br />

#include <br />

#include <br />

int main(int c, char* a[]) {<br />

char l[128];<br />

for (;;) {<br />

if (gets(l)==NULL)<br />

break;<br />

if (strlen(l) > 10)<br />

l[10]= 0;<br />

<strong>prin</strong>tf("%s\r\n", l);<br />

}<br />

return 0;<br />

}<br />

Lansarea unui astfel de filtru se face, de asemenea, dintr-o fereastră Cmd, putându-se, la fel ca<br />

în Unix sau Dos, să se redirecteze intrarea <strong>şi</strong> ie<strong>şi</strong>rea lui standard, astfel:<br />

Filtru.exe FisierIesire<br />

<strong>1.</strong><strong>1.</strong>3. Cum se compilează <strong>şi</strong> rulează programele cpp sub <strong>Windows</strong>?<br />

Cum se ajunge de la aceste surse cpp la fi<strong>şi</strong>erele executabile corespunzătoare? Aceasta<br />

depinde de mediul cpp cu care se dezvoltă aplicaţiile. Recomandările noastre sunt două:<br />

� Distribuţia MinGW<br />

� Distribuţia Visual Studio (Dev Studio<br />

Vom expune, strict telegrafic, pa<strong>şi</strong>i realizării lor folosind mediul DevStudio pentru<br />

Microsoft Visual C++ 6.0.<br />

<strong>1.</strong> In meniul File se selectează New, în fereastra de dialog obţinută se selectează Project.<br />

2. In cadrul acesteia se selectează Win32 Console Application


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 5 -<br />

3. In spaţiul Project Name se scrie numele proiectului, în cazul nostru prim sau filtru.<br />

4. In spaţiul Location se trece locul în structura de directori, în care mediul î<strong>şi</strong> va dezvolta<br />

aplicaţia, de exemplu \Useri\florin. Aici, mediul creează un subdirector cu numele<br />

proiectului (în cazul nostru prim sau filtru).<br />

5. Se alege tipul de aplicaţie consolă dorit: An empty project, A simple application, A “Hello<br />

world!” application, An application that supports MFC. Considerăm că în acest exemplu<br />

am ales: A simple application.<br />

6. După Ok, se afişează un mesaj de informare, iar în subdirectorul din location sunt create<br />

subdirectoarele: SourceFiles, HeaderFiles <strong>şi</strong> ResourceFiles. De asemenea,<br />

este depus acolo un fi<strong>şi</strong>er ReadMe.txt în care se explică rolul fiecărui fi<strong>şi</strong>er sau<br />

director creat de mediu.<br />

7. Se selectează din mediu FileView, se expandează directorul cu numele proiectului, apoi<br />

subdirectorul SourceFiles în care se află un fi<strong>şi</strong>er cu numele proiectului <strong>şi</strong> tipul cpp.<br />

Acest fi<strong>şi</strong>er sursă conţine, gata generat, scheletul programului <strong>prin</strong>cipal (al funcţiei<br />

main). După un dublu click pe numele acestuia, el apare în fereastra de editare.<br />

8. Din acest moment programatorul poate să scrie efectiv conţinutul aplicaţiei sale, plecând<br />

de la scheletul furnizat de mediu.<br />

9. La terminare, meniul Build oferă, <strong>prin</strong>tre altele, posibilitatea de a compila sursa respectivă<br />

(Compile sau Ctrl+F7), respectiv să obţină un fi<strong>şi</strong>er exe (Build sau F7).<br />

10. Fi<strong>şi</strong>erul de tip exe obţinut este plasat în subdirectorul Debug al directorului cu numele<br />

proiectului. El poate fi lansat în execuţie din mediu sau dinafara lui, poate fi copiat în altă<br />

parte (cum l-am depus noi pe prim.exe în e:\florin) <strong>şi</strong> lansa de acolo, etc.<br />

<strong>1.</strong>2. Access la fi<strong>şi</strong>ere <strong>şi</strong> aşteptarea unor evenimente <strong>Windows</strong><br />

<strong>1.</strong>2.<strong>1.</strong> Funcţii de access la fi<strong>şi</strong>ere<br />

Descriem pe scurt prototipurile funcţiilor de lucru cu fi<strong>şi</strong>ere sub <strong>Windows</strong>. Pentru mai multe<br />

informatii legate de prototipurile acestor funcţii, se recomandă consultarea documentaţiei<br />

MSDN.<br />

HANDLE CreateFile (LPCTSTR numeFisier, DWORD acces,<br />

DWORD partajare, LPSECURITY_ATTRIBUTES descr_sec,<br />

DWORD mod_deschid, DWORD atributeFisier);<br />

numeFisier –numele fi<strong>şi</strong>erului care se va crea<br />

acces –modul de acces (în citire <strong>şi</strong>/sau scriere)<br />

partajare –modul de partajare a fi<strong>şi</strong>erului<br />

mod_deschid –modul de creare a fi<strong>şi</strong>erului<br />

atributeFisier –atribute fi<strong>şi</strong>er<br />

HFILE OpenFile ( LPCSTR numeFisier, LPOFSTRUCT lpBuf, UINT uActiune);<br />

numeFisier –numele fi<strong>şi</strong>erului care se va deschide<br />

lpBuf –pointer la o structură care reţine informaţii despre fi<strong>şi</strong>er<br />

uActiune –indică operaţia care se va efectua asupra fi<strong>şi</strong>erului<br />

BOOL WriteFile (HANDLE hFisier, LPCVOID lpBuf, DWORD nNrOctetiDeScris,<br />

LPDWORD lpNumarOctetiScrisi, LPOVERLAPPED lpStructIO);


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 6 -<br />

hFisier –handle-ul fisierului în care se va scrie<br />

lpBuf –pointer la datele care se vor scrie în fi<strong>şi</strong>er<br />

nNrOctetiDeScris –număr de octeţi de scris<br />

lpNumarOctetiScrisi –pointer la numărul de octeţi scri<strong>şi</strong> efectiv<br />

lpStructIO –de obicei are valoarea NULL<br />

BOOL ReadFile ( HANDLE hFisier, LPCVOID lpBuf, DWORD nNrOctetiDeCitit,<br />

LPDWORD lpNumarOctetiCititi, LPOVERLAPPED lpStructIO);<br />

hFisier –handle-ul fisierului din care se va citi<br />

lpBuf –pointer la un bufer în care se vor citi datele<br />

nNrOctetiDeCitit –număr de octeţi de citit<br />

lpNumarOctetiCititi –pointer la numărul de octeţi citiţi efectiv<br />

lpStructIO –de obicei are valoarea NULL<br />

BOOL CloseHandle ( HANDLE hObject );<br />

hObject –handle-ul la un obiect deschis<br />

<strong>1.</strong>2.2. Funcţii de aşteptare a unor evenimente<br />

Win32 API oferă un set de funcţii de aşteptare pentru a permite unui program să î<strong>şi</strong> suspende<br />

temporar execuţia în aşteptarea unui eveniment. Funcţiile de aşteptare blochează execuţia<br />

programului până când criteriul specificat a fost îndeplinit. Tipul funcţiei de aşteptare<br />

determină criteriul utilizat. În timpul aşteptării procesul consumă foarte puţine resurse sistem,<br />

fiind vorba de o aşteptare pasivă – intrare în sleep. Există trei tipuri de funcţii de aşteptare:<br />

<strong>1.</strong> single-object: WaitForSingleObject, SignalObjectAndWait,<br />

WaitForSingleObjectEx<br />

2. multiple-object: WaitForMultipleObjects, WaitForMultipleObjectsEx,<br />

MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx.<br />

3. alertable: MsgWaitForMultipleObjectsEx, SignalObjectAndWait,<br />

WaitForMultipleObjectsEx, WaitForSingleObjectEx<br />

Tabelul următor prezintă pe scurt rolurile <strong>prin</strong>cipalelor funcţii de aşteptare.<br />

Funcţie wait Descriere<br />

WaitForSingleObject() Aşteaptă după un anumit obiect ca acesta să<br />

ajungă în starea setat (de exemplu<br />

terminarea unui proces sau valoarea pozitiva<br />

a unui semafor).<br />

WaitForSingleObjectEx() Ca <strong>şi</strong> precedentul, plus aşteptarea a altor<br />

două evenimente: terminarea unei operaţii<br />

de intrare ie<strong>şi</strong>re, sau sosirea unui apel<br />

asincron în threadul curent.<br />

WaitForMultipleObjects() Aşteaptă după o mulţime de obiecte. Ie<strong>şi</strong>rea<br />

din aşteptare se poate face fie când unul<br />

dintre obiecte este setat, fie când toate<br />

obiectele ajung în starea setat.<br />

WaitForMultipleObjectsEx() Ca <strong>şi</strong> precedentul, plus aşteptarea celor două<br />

evenimente specificate în cazul funcţiei<br />

WaitForSingleObjectEx.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 7 -<br />

Cea mai simplă dintre aceste funcţii <strong>şi</strong> cea mai des utilizată este WaitForSingleObject,<br />

al cărei prototip îl vom prezenta <strong>şi</strong> pe care o vom utiliza <strong>şi</strong> noi în secţiunile următoare. Pentru<br />

detalii privind celelalte funcţii recomandaconsultarea MSDN.<br />

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMillisec);<br />

hHandle -handler-ul obiectului la care se aşteaptă<br />

dwMillisec -intervalul de timp maxim de aşteptare. Valoarea INFINITE indică aşteptare<br />

până la semnalarea obiectului.<br />

Funcţia poate să întoarcă următoarele valori:<br />

� WAIT_ABANDONED -dacă obiectul nu a fost eliberat de threadul său proprietar înainte<br />

ca apelul să se termine<br />

� WAIT_OBJECT_O -dacă obiectul a fost semnalat<br />

� WAIT_TIMEOUT -dacă intervalul de timp a expirat<br />

<strong>1.</strong>3. Lucrul cu procese sub <strong>Windows</strong><br />

<strong>1.</strong>3.<strong>1.</strong> Crearea unui proces<br />

Crearea unui proces în <strong>Windows</strong> se face <strong>prin</strong> apelul funcţiei CreateProcess dintr-un alt<br />

proces. Funcţia are următorul prototip:<br />

BOOL CreateProcess (LPCTSTR lpszImageName,<br />

LPCTSTR lpszCommandLine,<br />

LPSECURITY_ATTRIBUTES lpsaProcess,<br />

LPSECURITY_ATTRIBUTES lpsaThread,<br />

BOOL fInheritHandles,<br />

DWORD fdwCreate,<br />

LPVOID lpvEnvironment,<br />

LPTSTR lpszCurDir,<br />

LPSTARTUPINFO lpsiStartInfo,<br />

LPPROCESS_INFORMATION lppiProcInfo);<br />

Atunci când se apelează funcţia CreateProcess, sistemul creează un spaţiu de adresare <strong>şi</strong><br />

încarcă noul proces în acest spaţiu. După această operaţie, sistemul creează threadul primar<br />

pentru noul proces <strong>şi</strong>-l lansează în execuţie. Să vedem semnificaţia parametrilor funcţiei<br />

CreateProcess:<br />

lpszImageName - parametrul identifică numele fi<strong>şi</strong>erului executabil în care este memorat<br />

codul pentru procesul ce se va crea. CreateProcess va căuta fi<strong>şi</strong>erul mai întâi în directorul<br />

curent, <strong>şi</strong> apoi în directoarele descrise de variabila de mediu PATH. Dacă acest parametru<br />

este NULL, funcţia va lua numele fi<strong>şi</strong>erului din primul cuvânt al liniei de comandă<br />

specificată de parametrul lpszCommandLine.<br />

lpszCommandLine - parametrul specifică argumentele care trebuie transmise către noul<br />

proces. La pornirea procesului, aceste argumente vor fi regăsite în argumentul<br />

lpszCmdLine al funcţiei WinMain. lpszCommandLine punctează către un sir de<br />

caractere ANSI.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 8 -<br />

lpsaProcess <strong>şi</strong> lpsaThread identifică atributele de securitate care trebuie date noului<br />

proces <strong>şi</strong> threadului său primar. Dacă valorile lor sunt NULL, sistemul va furniza valori<br />

implicite pentru aceşti parametri.<br />

fInheritHandles - specifică modul în care se moştenesc referinţele la obiecte de către<br />

procesul fiu. In mod normal, în <strong>Windows</strong> acestea nu sunt acelea<strong>şi</strong> pentru fiecare proces.<br />

Chiar dacă este vorba de acela<strong>şi</strong> obiect sistem, procese diferite pot lucra cu valori diferite.<br />

Dacă dorim ca aceste valori să fie identice în procesul tată <strong>şi</strong> procesul fiu, atunci putem<br />

specifica valoarea TRUE acestui parametru.<br />

fdwCreate - conţine comutatorii (flags) ce afectează modul în care este creat noul proces.<br />

Se pot specifica mai mulţi comutatori combinându-i <strong>prin</strong> operatorul “|”.<br />

lpvEnvironment - referă un bloc de memorie în care sunt memorate <strong>şi</strong>rurile, care descriu<br />

variabilele de mediu ce trebuiesc utilizate de noul proces. De obicei, valoarea acestui<br />

parametru trebuie să fie NULL, caz în care procesul fiu primeşte acelea<strong>şi</strong> variabile de mediu<br />

ca <strong>şi</strong> părintele.<br />

lpszCurDir - parametrul permite specificarea unui nou disc <strong>şi</strong> a unui nou director curent<br />

pentru procesul nou creat. Dacă valoarea transmisă pentru acest parametru este NULL,<br />

directorul <strong>şi</strong> discul de lucru ale noului proces sunt acelea<strong>şi</strong> ca <strong>şi</strong> cele ale procesului părinte.<br />

lpsiStartInfo – punctează către o structură STARTUPINFO. Aceste informaţii sunt<br />

necesare pentru subsistemul Win32 la crearea unui nou proces. Această structură este prea<br />

complexă pentru a o descrie aici. Să reţinem doar că în ea se pot specifica caracteristicile unei<br />

ferestre consolă: culoare, număr de linii, coloane, poziţie pe ecran, titlu, etc.; informaţii<br />

despre modul în care va fi afişată fereastra aplicaţiei: maximizată, icon, etc.; informaţii despre<br />

comportarea sistemului în timpul încărcării aplicaţiei: forma cursorului, etc.<br />

lppiProcInfo - conţine adresa unei structuri care va fi completată cu informaţii de către<br />

funcţia CreateProces înainte ca aceasta să-<strong>şi</strong> termine execuţia. Această structură va conţine<br />

referinţele pentru noul proces, pentru threadul său primar, precum <strong>şi</strong> identificatorii acestora.<br />

Structura este declarată astfel:<br />

typedef struct _PROCESS_INFORMATION {<br />

HANDLE hProcess;<br />

HANDLE hThread;<br />

DWORD dwProcessId;<br />

DWORD dwThreadId;<br />

} PROCESS_INFORMATION;<br />

<strong>1.</strong>3.2. Terminarea unui proces <strong>Windows</strong><br />

Un proces poate fi terminat pe două căi: apelând din interior funcţia ExitProcess sau<br />

apelând din exterior funcţia TerminateProcess. Este preferabilă prima cale, cea de-a doua<br />

trebuie folosită doar pentru situaţii extreme.<br />

Prototipul funcţiei ExitProcess este:<br />

VOID ExitProcess (UINT fuExitCode);<br />

Funcţia termină procesul care a apelat-o <strong>şi</strong> setează codul de retur pe valoarea fuExitCode.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 9 -<br />

Funcţia TerminateProcess are următorul prototip:<br />

BOOL TerminateProcess (HANDLE hProcess, UINT fuExitCode);<br />

Cu această funcţie, un process poate termina orice alt proces din sistem, inclusiv pe el însu<strong>şi</strong>.<br />

Procesul terminat este dat de referinţa hProcess. Codul de retur a procesului terminat va fi<br />

setată pe valoarea parametrului fuExitCode. Folosirea funcţiei TerminateProcess poate<br />

fi periculoasă la o programare neîngrijită. In mod normal, evenimentul de terminare a unui<br />

proces este semnalat de către <strong>Windows</strong> tuturor DLL-urilor ataşate de proces. La terminarea<br />

procesului, folosind funcţia TerminateProcess, aceste DLL-uri nu vor fi avertizate,<br />

producându-se eventuale pierderi de date. <strong>Windows</strong> garantează totu<strong>şi</strong> că toate resursele<br />

sistem utilizate de proces vor fi eliberate indiferent cum este terminat procesul.<br />

<strong>1.</strong>3.3. Exemple de utilizare de procese sub <strong>Windows</strong><br />

<strong>1.</strong>3.3.<strong>1.</strong> Exemplul 3: Rezumatul directorului curent<br />

Pentru utilizare sub <strong>Windows</strong>, vom folosi ca intermediar un fisier de comenzi ls.bat, care<br />

contine o singura linie:<br />

dir %1<br />

Sursa execWin.cpp a programului este:<br />

#include <br />

#include <br />

int main() {<br />

STARTUPINFO si = { sizeof(si) };<br />

PROCESS_INFORMATION pi;<br />

// ls.bat contine linia: dir %1<br />

<strong>prin</strong>tf("Procesul parinte %d va creea un fiu\n", GetCurrentProcessId());<br />

// Rulati alternativ cu una dintre urmatoarele doua linii comentata:<br />

BOOL b = CreateProcess("ls.bat", NULL, NULL, NULL,<br />

// BOOL b = CreateProcess("ls.bat", "ls.bat *.cpp", NULL, NULL,<br />

FALSE, 0, NULL, NULL, &si, &pi);<br />

WaitForSingleObject(pi.hProcess, INFINITE);<br />

<strong>prin</strong>tf("Terminat procesul fiu %d creeat de parintele %d\n",<br />

pi.dwProcessId, GetCurrentProcessId());<br />

return 0;<br />

}<br />

Efectul compilarii si a executiei este:<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc execWin.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>a<br />

Procesul parinte 2320 va creea un fiu<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>dir<br />

Volume in drive D is fmbData<br />

Volume Serial Number is F6C0-1474<br />

Directory of D:\Florin\Didactic\20102011\SO\probleme\WProc-H


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 10 -<br />

04/10/2011 12:47 PM .<br />

04/10/2011 12:47 PM ..<br />

04/10/2011 12:47 PM 18,597 a.exe<br />

04/10/2011 12:13 PM 679 capitalizare.cpp<br />

04/10/2011 12:08 PM 18,945 ceva.exe<br />

04/10/2011 12:08 PM 435 compilerun.cpp<br />

04/10/2011 11:40 AM 671 execWin.cpp<br />

04/10/2011 11:19 AM 23 ls.bat<br />

04/10/2011 12:18 PM 656 master.cpp<br />

03/23/2011 07:32 PM 17,874 OS_U<strong>IPC</strong>-H.txt<br />

03/23/2011 01:06 PM 13,081 OS_UPipe-H.txt<br />

03/19/2011 03:51 PM 14,999 OS_UProc-H.txt<br />

04/10/2011 11:58 AM 982 paritate.cpp<br />

04/10/2011 11:58 AM 18,945 paritate.exe<br />

04/10/2011 11:45 AM 320 paritateFiu.cpp<br />

04/10/2011 11:45 AM 17,413 paritateFiu.exe<br />

03/23/2011 12:45 PM 19,781 SO_U<strong>IPC</strong>-H.txt<br />

03/23/2011 01:11 PM 13,805 SO_UPipe-H.txt<br />

03/19/2011 03:40 PM 14,738 SO_UProc-H.txt<br />

03/19/2011 03:40 PM 14,738 SO_WProc-H.txt<br />

18 File(s) 186,682 bytes<br />

2 Dir(s) 239,633,485,824 bytes free<br />

Terminat procesul fiu 4972 creeat de parintele 2320<br />

Daca se comenteaza invers linia care il defineste pe b, efectul este:<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>a<br />

Procesul parinte 6068 va creea un fiu<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>dir *.cpp<br />

Volume in drive D is fmbData<br />

Volume Serial Number is F6C0-1474<br />

Directory of D:\Florin\Didactic\20102011\SO\probleme\WProc-H<br />

04/10/2011 12:13 PM 679 capitalizare.cpp<br />

04/10/2011 12:08 PM 435 compilerun.cpp<br />

04/10/2011 12:51 PM 671 execWin.cpp<br />

04/10/2011 12:18 PM 656 master.cpp<br />

04/10/2011 11:58 AM 982 paritate.cpp<br />

04/10/2011 11:45 AM 320 paritateFiu.cpp<br />

6 File(s) 3,743 bytes<br />

0 Dir(s) 239,633,485,824 bytes free<br />

Terminat procesul fiu 4824 creeat de parintele 6068<br />

<strong>1.</strong>3.3.2. Exemplul 4: Cate perechi de numere nenule au suma numar par?<br />

Problema este trivial de simpla, insa potrivita pentru a exemplifica utilizarea<br />

CreateProcess, WaitForSingleObject si ExitProcess.<br />

Enuntul problemei: Se dau la linia de comanda n perechi de numere intregi. Programul va<br />

crea n procese fii, fiecare primind doua argumente consecutive din linia de comanda.<br />

Oricare dintre fii intoarce codul de retur:<br />

� 0 daca perechea are suma para,<br />

� 1 daca suma este impara,<br />

� 2 daca unul dintre argumente este nul sau nenumeric.<br />

Parintele asteapta terminarea fiilor si va afisa rezultatul. In continuare vom implementa un<br />

program separat pentru procesul fiu. Sursa lui, paritateFiu.cpp este:


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 11 -<br />

#include <br />

#include <br />

main(int argc, char* argv[]) {<br />

int n1, n2;<br />

n1 = atoi(argv[1]); // atoi intoarce 0<br />

n2 = atoi(argv[2]); // si la nenumeric<br />

if (n1 == 0 || n2 == 0) ExitProcess(2);<br />

if ((n1 + n2) % 2 == 0) ExitProcess(0);<br />

else ExitProcess(1);<br />

}<br />

Acesta va fi compilat cu:<br />

gcc -o paritateFiu paritateFiu.cpp<br />

Sursa paritate.cpp este:<br />

#include <br />

#include <br />

main(int argc, char* argv[]) {<br />

STARTUPINFO si = { sizeof(si) };<br />

PROCESS_INFORMATION pi[100];<br />

char linieCom[1000];<br />

int pare = 0, impare = 0, nenum = 0, i;<br />

DWORD n1;<br />

for (i = 1; i < argc-1; i += 2) {<br />

strcpy(linieCom, "paritateFiu.exe ");<br />

strcat(linieCom, argv[i]);<br />

strcat(linieCom, " ");<br />

strcat(linieCom, argv[i+1]);<br />

BOOL b = CreateProcess("paritateFiu.exe", linieCom, NULL, NULL,<br />

FALSE, 0, NULL, NULL, &si, &pi[i]);<br />

}<br />

// Parintele asteapta terminarile fiilor<br />

for (i = 1; i < argc-1; i += 2) {<br />

WaitForSingleObject(pi[i].hProcess, INFINITE);<br />

GetExitCodeThread(pi[i].hThread, &n1);<br />

switch (n1) {<br />

case 0: pare++;break;<br />

case 1: impare++;break;<br />

default: nenum++;<br />

}<br />

}<br />

<strong>prin</strong>tf("Pare %d, impare %d, nenumerice %d\n",pare, impare, nenum);<br />

}<br />

<strong>1.</strong>3.3.3. Exemplul 5: Un program care compileaza si ruleaza alt program.<br />

Sursa compilerun.cpp a acestuia este:<br />

#include <br />

#include <br />

main(int argc, char* argv[]) {<br />

STARTUPINFO si = { sizeof(si) };<br />

PROCESS_INFORMATION pi;<br />

char com[200];<br />

strcpy(com, "gcc -o ceva "); // fabricat comanda<br />

strcat(com, argv[1]);<br />

if (system(com) == 0)


}<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 12 -<br />

BOOL b = CreateProcess("ceva", NULL, NULL, NULL,<br />

FALSE, 0, NULL, NULL, &si, &pi);<br />

else <strong>prin</strong>tf("Erori de compilare\n");<br />

Compilarea lui se face<br />

gcc -o comprun compilerun.cpp<br />

Executia se face, de exemplu, <strong>prin</strong><br />

comprun sursa.cpp<br />

Ca efect, daca compilarea sursei argument (sursa.cpp) este corecta, atunci compilatorul<br />

gcc creeaza fisierul ceva si intoarce cod de retur o, dupa ceva este lansat <strong>prin</strong><br />

CreateProcess. Daca esueaza compilarea, se va tipari doar mesajul.<br />

<strong>1.</strong>3.3.4. Exemplul 6: Prelucrarea simultana a mai multor fisiere text.<br />

Dorim sa transformam un fisier text intr-un alt fisier text, cu acelasi continut, dar in care toate<br />

cuvintele din el sa inceapa cu litera mare. Un astfel de program va fi apelat:<br />

capitalizare fisierintrare fisieriesire<br />

Ne propunem sa prelucram simultan mai multe astfel de fisiere. De aceea vom creea un<br />

proces master, care primeste la linia de comanda numele fisierelor al caror continut va fi<br />

capitalizat:<br />

master fisier1 fisier2 - - - fisiern<br />

Rezultatul va consta din fisierele:<br />

fisier<strong>1.</strong>CAPIT, fisier2.CAPIT, - - - fisiern.CAPIT<br />

Procesul master va crea n procese fii, iar fiecare fiu i va lansa <strong>prin</strong> CreateProcess<br />

programul:<br />

capitalizare fisi fisi.CAPIT<br />

Sursa capitalizare.cpp este:<br />

#include <br />

#include <br />

#include<br />

#define MAXLINIE 100<br />

main(int argc, char* argv[]) {<br />

FILE *fi, *fo;<br />

char linie[MAXLINIE], *p;<br />

fi = fopen(argv[1], "r");<br />

fo = fopen(argv[2], "w");<br />

if (fi == NULL && fo == NULL) ExitProcess(1);<br />

for ( ; ; ) {<br />

p = fgets(linie, MAXLINIE, fi);<br />

linie[MAXLINIE-1] = '\0';<br />

if (p == NULL) break;<br />

for (p = linie; ; ) {


}<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 13 -<br />

p = strstr(p, " ");<br />

if (p == NULL) break;<br />

p++;<br />

if (*p == '\n') break;<br />

*p = toupper(*p);<br />

}<br />

f<strong>prin</strong>tf(fo, "%s", linie);<br />

}<br />

fclose(fo);<br />

fclose(fi);<br />

Programul primeste la linia de comanda numele celor doua fisiere. Se deschid aceste fisiere si<br />

se citeste fisierul de intrare linie cu linie. Cu ajutorul pointerului p, se parcurge linia curenta<br />

si se cauta pe rand cate un spatiu, dar care sa nu fie ultimul caracter din linie. Urmatorul<br />

caracter este apoi transformat in litera mare (toupper face aceasta transformare numai daca<br />

caracterul este efectiv o litera mica).<br />

Sursa master.cpp este:<br />

#include <br />

#include <br />

main(int argc, char* argv[]) {<br />

STARTUPINFO si = { sizeof(si) };<br />

PROCESS_INFORMATION pi[100];<br />

int i;<br />

char nume[200];<br />

for (i=1; argv[i]; i++) {<br />

strcpy(nume, "capitalizare ");<br />

strcat(nume, argv[i]);<br />

strcat(nume, " ");<br />

strcat(nume, argv[i]);<br />

strcat(nume, ".CAPIT"); // fabricat numele iesirii<br />

// incarcat programul de capitalizare<br />

BOOL b = CreateProcess("capitalizare.exe", nume, NULL, NULL,<br />

FALSE, 0, NULL, NULL, &si, &pi[i]);<br />

}<br />

<strong>prin</strong>tf("Lansat simultan %d procese de capitalizare\n",i-1);<br />

}<br />

Se parcurg argumentele liniei de comanda si pentru fiecare dintre ele se creeaza un proces fiu.<br />

In tabloul nume se construieste numele fisierului de iesire. Apoi se incarca programul<br />

capitalizare cu cele doua nume de fisiere date "la linia de comanda".<br />

Cele doua programe se compileaza:<br />

gcc -o capitalizare capitalizare.c<br />

gcc -o master master.c<br />

Lansarea se face:<br />

master fis1 fis2 - - - fisn<br />

<strong>1.</strong>4. Pipe sub <strong>Windows</strong><br />

În <strong>Windows</strong> există două posibilităţi de a folosi <strong>pipe</strong> în <strong>IPC</strong>. O primă variantă este <strong>pipe</strong><br />

anonime, care se pot folosi numai pentru comunicarea între procese de pe aceea<strong>şi</strong> ma<strong>şi</strong>nă. A


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 14 -<br />

doua variantă este <strong>pipe</strong> cu nume, folosite pentru comunicarea între procese ce operează nu<br />

neapărat pe aceea<strong>şi</strong> ma<strong>şi</strong>nă <strong>Windows</strong>.<br />

<strong>1.</strong>4.<strong>1.</strong> Pipe anonim <strong>Windows</strong><br />

Ne vom ocupa mai întâi de <strong>pipe</strong> anonime, ca <strong>şi</strong> mecanism <strong>IPC</strong> în programarea concurentă sub<br />

<strong>Windows</strong>. Un <strong>pipe</strong> anonim poate fi folosit, ca <strong>şi</strong> <strong>pipe</strong>-ul de sub Unix, pentru comunicarea<br />

între procese descendente din creatorul <strong>pipe</strong>-ului. In urma creării, procesul creator obţine doi<br />

descriptori - handle - unul de citire <strong>şi</strong> altul de scriere. Procesul creator poate trimite fiilor<br />

(nepoţilor etc.) handle-urile <strong>pipe</strong>-ului, în momentul creării proceselor fii <strong>prin</strong> apeluri ale<br />

funcţiei CreateProces. Pentru ca fiul să moştenească handle-ul la <strong>pipe</strong>, părintele trebuie<br />

să seteze parametrul fInheritedHandle din apelul CreateProces, la valoarea TRUE.<br />

Un <strong>pipe</strong> fără nume se creează folosind apelul CreatePipe. Pipe-ul se închide cu ajutorul<br />

funcţiei CloseHandle. Funcţia CreatePipe creează un <strong>pipe</strong> fară nume <strong>şi</strong> are următorul<br />

prototip:<br />

BOOL CreatePipe (PHANDLE phRead,PHANDLE phWrite,<br />

LPSECURITY_ATTRIBUTES lpsa, DWORD cbPipe);<br />

Funcţia întoarce TRUE în caz de succes sau FALSE la eşec.<br />

phRead <strong>şi</strong> phWrite sunt pointerii spre cele două handle-uri (de citire <strong>şi</strong> de scriere) obţinute<br />

în urma creării.<br />

parametrul lpsa are o dublă semnficaţie: determină dacă handle-ul la <strong>pipe</strong>, returnat de<br />

funcţie poate fi moştenit în procesele fii, proprietate care are loc pentru o valoare diferită de<br />

NULL. Acela<strong>şi</strong> parametru reprezintă un descriptor de securitate. Dacă se specifică pentru<br />

acest parametru valoarea NULL, sistemul va fixa atributele de securitate implicite<br />

cbPipe specifică dimensiunea buferului rezervat pentru operaţiile de I/O <strong>prin</strong> <strong>pipe</strong>. Dacă<br />

această valoare este 0, atunci dimensiunea implicită a buferului o stabileşte sistemul de<br />

operare.<br />

Scrierea <strong>şi</strong> citirea din <strong>pipe</strong>urile anonime se face folosind funcţiile ReadFile <strong>şi</strong><br />

WriteFile, similare cu read <strong>şi</strong> write de sub Unix. Operaţiile sunt atomice. O citire va opri<br />

procesul până când va reu<strong>şi</strong> să se execute. In mod similar, o scriere va bloca procesul până<br />

când va avea suficient spaţiu în <strong>pipe</strong> pentru efectua operaţia de scriere dorită.<br />

Atât procesul creator, cât <strong>şi</strong> procesele fii care moştenesc cele două handle-uri ale <strong>pipe</strong>-ului, le<br />

pot folosi între momentul primirii lor <strong>şi</strong> momentul închiderii. Astfel, procesul creator poate<br />

începe imediat să folosească <strong>pipe</strong> <strong>prin</strong> apelurile ReadFile <strong>şi</strong> WriteFile. După creare <strong>şi</strong><br />

primirea handle-urilor, procesele fii pot să folosească la rândul lor funcţiile ReadFile <strong>şi</strong><br />

WrieFile pentru a citi sau scrie din <strong>pipe</strong>.<br />

<strong>1.</strong>4.2. Pipe cu nume sub windows<br />

Pipe cu nume este un mecanism de comunicare între două sisteme diferite, ambele fiind<br />

operaţionale pe platforme <strong>Windows</strong>


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 15 -<br />

In figura următoare sunt prezentate succesiunile apelurilor sistem, atât pentru server, cât <strong>şi</strong><br />

pentru client. Cititorul poate uşor observa particularizările necesare pentru comunicarea <strong>prin</strong><br />

<strong>pipe</strong> anonim.<br />

<strong>1.</strong> C re at eN am ed Pi pe<br />

2. Co nn ec tN am ed Pi pe<br />

3. R ea dF il e<br />

4. W ri te Fi le<br />

5. D is co nn ec tN am ed Pi pe<br />

6. C lo se Ha nd le<br />

<strong>1.</strong> C re at eF il e<br />

2. R ea dF il e<br />

3. W ri te Fi le<br />

4. C lo se Ha nd le<br />

Server Client<br />

P ipe cu num e<br />

Crearea unui <strong>pipe</strong> cu nume se face <strong>prin</strong> apelul sistem CreateNamedPipe, cu prototipul:<br />

HANDLE CreateNamedPipe(LPSTR numePipe,<br />

DWORD optiuniModOpen,<br />

DWORD optiuniModPipe,<br />

DWORD nMaxInstances,<br />

DWORD lungBufOut,<br />

DWORD lungBufIn,<br />

DWORD timeOut,<br />

LPSECURITY_ATTRIBUTES lpsa)<br />

numePipe este un string <strong>prin</strong> care se indică numele <strong>pipe</strong>-ului. Convenţiile Microsoft de<br />

specificare a acestor nume impun două sintaxe, una pentru <strong>pipe</strong> local <strong>şi</strong> alta pentru <strong>pipe</strong> de pe<br />

o altă ma<strong>şi</strong>nă. Aceste specificări sunt:<br />

\\.\PIPE\numePipePeMAsina<br />

\\adresaMasina\PIPE\numePipePeMasina<br />

adresa ma<strong>şi</strong>nii este fie o specificare Internet, fie o adresă IP. Atenţie! In constantele string de<br />

specificare a acestor nume, fiecare caracter \ trebuie dublat, aşa cum cer regulile de evitare<br />

specifice limbajului C.<br />

optiuniModOpen specifică direcţia de deschidere a <strong>pipe</strong>-ului. Valoarea lui poate fi una<br />

dintre constantele: PIPE_ACCESS_DUPLEX, PIPE_ACCESS_INBOUND,<br />

PIPE_ACCESS_OUTBOUND, indicând fie ambele sensuri, fie numai de la client la server,<br />

fie numai de la server spre client.<br />

optiuniModPipe precizează caracteristicile acestui <strong>pipe</strong>. Pentru specificare, se folosesc<br />

constante sau combinaţii legate între ele <strong>prin</strong> operatorul '|':<br />

� PIPE_TYPEBYTE, PIPE_TYPE_MESSAGE pentru scrierea ca flux de octeţi, respectiv<br />

ca <strong>şi</strong> mesaj;<br />

� PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE pentru citirea după<br />

regulile fluxului de octeţi sau citirea ca <strong>şi</strong> mesaj;<br />

� PIPE_WAIT, PIPE_NOWAIT pentru comunicarea sincronă, respectiv asincronă.<br />

nMaxInstances specifică numărul de clienţi care se vor conecta la acest <strong>pipe</strong>. La nevoie,<br />

se poate folosi constanta PIPE_UNLIMITED_INSTANCES.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 16 -<br />

lungBufOut <strong>şi</strong> lungBufIn specifică dimensiunile bufferelor, pentru ie<strong>şi</strong>re <strong>şi</strong> intrare.<br />

Sistemul ia aceste numere doar ca sugestii, în funcţie de context el putând să redimensioneze<br />

aceste buffere.<br />

timeOut indică, în milisecunde, durata maximă de aşteptare.<br />

lpsa specifică atributele de securitate. De cele mai multe ori se specifică NULL, lăsând<br />

astfel pe seama sistemului fixarea acestora.<br />

Apelul sistem întoarce un handle, care va fi folosit ca <strong>şi</strong> argument în celelalte apeluri sistem<br />

legate de <strong>pipe</strong>.<br />

După crearea unui <strong>pipe</strong> cu nume, serverul apelează:<br />

ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpo)<br />

Primul handle este cel întors de crearea <strong>pipe</strong>. Al doilea parametru, de regulă NULL, indică<br />

faptul că se aşteaptă la conectare până când un client se conectează efectiv la <strong>pipe</strong>. (A se<br />

compara această regulă cu cea similară de la FIFO de sub Unix).<br />

La fel ca <strong>şi</strong> la <strong>pipe</strong> anonime, se folosesc apelurile ReadFile <strong>şi</strong> WriteFile pentru<br />

schimbul cu <strong>pipe</strong>.<br />

Serverul î<strong>şi</strong> încheie activitatea apelând:<br />

DisconnectNamedPipe(HANDLE hNamedPipe);<br />

CloseHandle (HANDLE hNamedPipe);<br />

Pentru client, conectarea la un <strong>pipe</strong> cu nume presupune un apel sistem CreateFile:<br />

In cazul creării unui <strong>pipe</strong> cu nume, numeFisier reprezintă numele <strong>pipe</strong>-ului, cu sintaxa<br />

specificată mai sus, la apelul CreateNamedPipe.<br />

<strong>1.</strong>4.3. Exemplul 7: lista de fisiere cu nume de anumita forma; implementare<br />

<strong>pipe</strong> cu nume<br />

Enuntul problemei este: Se cere un server si un client. Clientul trimite serverului un intreg l si<br />

un string s. Serverul ii intoarce clientului lista a maximum l fisiere din directorul lui curent,<br />

ale caror nume se termina cu stringul s.<br />

Vom descrie implementarea separat pe functionalitati. Pentru fiecare functionalitate am<br />

intocmit o sursa separata.<br />

Mai intai vom construi o functie cu rolul de a furniza lista numelor de fisiere:<br />

Vom descrie mai intai tipul de date Mesaj. El va diferi de la un tip de canal la altul, insa<br />

pentru functiile de mai sus aceasta diferenta nu le va influenta raspunsurile.<br />

Structura Mesaj este:


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 17 -<br />

/--PLUS--\ /------------- MAXS --------------\<br />

|lung| |s |<br />

|----|----------|xxxxxxxxxxxxxxxxxxxxxxxxxxxx|------|<br />

| |<br />

\ -------------- lung ----------------/<br />

lung este un intreg ce contine lungimea efectiva a continutului mesajului.<br />

Zona de PLUS octeti contine cativa intregi, depinde de tipul canalului pe care se comunica.<br />

s este un tablou de maximum MAXS caractere, aflat in partea finala a mesalului.<br />

Evident, in functie de natura problemei utilizatorul poate sa isi defineasca suprapuneri<br />

convenabile (union) in Mesaj.<br />

Sursa dir.cpp contine functia ce obtine lista fisierelor:<br />

Mesaj *dir(int l, char *s) {<br />

static Mesaj resp;<br />

WIN32_FIND_DATA d;<br />

char *p, *q, *n, dirc[] = ".\\*.*";<br />

int i;<br />

p = resp.s;<br />

resp.lung = 0;<br />

HANDLE hf = FindFirstFile(dirc, &d);<br />

for (i=0; i MAXS) break;<br />

i++;<br />

strcpy(p, n);<br />

resp.lung += strlen(n)+1;<br />

p += strlen(n)+1;<br />

}<br />

resp.lung += PLUS;<br />

return &resp;<br />

}<br />

Functia dir primeste intregul l si stringul s precizati in enuntul problemei "lista a maximum<br />

l nume de fisiere din directorul curent al caror nume se termina cu s".<br />

La terminare intoarce un pointer la un Mesaj care contine lista numelor.<br />

Mesajul contine in s succesiunea de stringuri (conventie C) cu numele fisierelor raspuns,<br />

iar lung suma lungimilor acestor stringuri (plus zerourile terminale), plus PLUS.<br />

Pentru comunicarea <strong>prin</strong> <strong>pipe</strong> cu nume structura mesajului este descrisa in sursa mesaj.h:<br />

#define MAXS 10000<br />

#define MAXL 1000<br />

typedef struct {<br />

int lung;<br />

int i;<br />

char s[MAXS];<br />

} Mesaj;<br />

#define PLUS (sizeof(int))<br />

Citirea / scrierea unui Mesaj la comunicarea <strong>prin</strong> <strong>pipe</strong> se face in doua etape:<br />

<strong>1.</strong> Se scrie / citeste lung.<br />

2. Se fac scrieri / citiri repetate, pana cand sunt schimbati toti octetii continutului<br />

mesajului.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 18 -<br />

Pentru implementarea schimbului de mesaje sunt folosite functiile Read, Write,<br />

ReadMes, WriteMes.<br />

Primele doua aplica repetat apeluri ReadFile, WriteFile pana la schimbarea a exact n<br />

octeti, n parametru de intrare.<br />

ReadMes, WriteMes schimba mai intai lungimea continutului, dupa care cere schimbul<br />

complet al acestuia.<br />

Cele patru functii sunt prezentate in sursa ReadWrite.cpp:<br />

void Read(HANDLE f, char *t, int n) {<br />

char *p;<br />

int c;<br />

DWORD i;<br />

for (p=t, c=n; ; ) {<br />

ReadFile(f, p, c, &i, NULL);<br />

if (i == c) return;<br />

c -= i;<br />

p += i;<br />

}<br />

}<br />

void Write(HANDLE f, char *t, int n) {<br />

char *p;<br />

int c;<br />

DWORD i;<br />

for (p=t, c=n; c; ) {<br />

WriteFile(f, p, c, &i, NULL);<br />

if (i == c) return;<br />

c -= i;<br />

p += i;<br />

}<br />

}<br />

Mesaj *ReadMes(HANDLE canal) {<br />

static Mesaj mesaj;<br />

DWORD no;<br />

ReadFile(canal, (char*)&mesaj.lung, sizeof(int), &no, NULL);<br />

Read(canal, (char*)&mesaj+sizeof(int), mesaj.lung);<br />

return &mesaj;<br />

}<br />

void WriteMes(HANDLE canal, Mesaj *pm) {<br />

DWORD no;<br />

WriteFile(canal, (char*)pm, sizeof(int), &no, NULL);<br />

Write(canal, (char*)pm+sizeof(int), pm->lung);<br />

}<br />

Sursa parinte.cpp este:<br />

void parinte(HANDLE in, HANDLE out) {<br />

Mesaj *pm;<br />

for ( ; ; ) {<br />

pm = ReadMes(in);<br />

pm = dir(pm->i, pm->s);<br />

WriteMes(out, pm);<br />

}<br />

}<br />

Actiunea <strong>prin</strong>cipala a clientului este dirijata de functia descrisa in sursa fiu.cpp:<br />

void fiu(HANDLE in, HANDLE out) {


}<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 19 -<br />

Mesaj *pm, mesaj;<br />

char *pc,linie[MAXL];<br />

int i;<br />

for ( ; ; ) {<br />

<strong>prin</strong>tf("Dati: numar|sufix: ");<br />

pc = (char*)fgets(linie, MAXL, stdin);<br />

if (pc == NULL) break;<br />

linie[strlen(linie)-1] = '\0';<br />

pc = strstr(linie, "|");<br />

if (pc == NULL) continue;<br />

mesaj.i = atoi(linie);<br />

strcpy(mesaj.s, pc+1);<br />

mesaj.lung = PLUS + strlen(mesaj.s) + 1;<br />

}<br />

WriteMes(out, &mesaj);<br />

pm = ReadMes(in);<br />

pc = pm->s;<br />

<strong>prin</strong>tf("%d\n",pm->lung);<br />

for (i = PLUS; i < pm->lung; ) {<br />

<strong>prin</strong>tf("%d %s\n", i, pc);<br />

i += strlen(pc) + 1;<br />

pc += strlen(pc) + 1;<br />

}<br />

In sfarsit, vom prezenta programele <strong>prin</strong>cipale.<br />

Sursa fifos.cpp prezinta programul <strong>prin</strong>cipal al serverului:<br />

#include <br />

#include <br />

#include "mesaj.h"<br />

#include "ReadWrite.cpp"<br />

#include "dir.cpp"<br />

#include "parinte.cpp"<br />

main() {<br />

HANDLE f1, f2;<br />

fclose(stdin);<br />

fclose(stdout);<br />

f1=CreateNamedPipe("\\\\.\\PIPE\\fifo1", PIPE_ACCESS_INBOUND,<br />

PIPE_TYPE_BYTE|PIPE_WAIT, 3, 0, 0, 0, NULL);<br />

f2=CreateNamedPipe("\\\\.\\PIPE\\fifo2", PIPE_ACCESS_OUTBOUND,<br />

PIPE_TYPE_BYTE|PIPE_WAIT, 3, 0, 0, 0, NULL);<br />

ConnectNamedPipe(f1, NULL);<br />

ConnectNamedPipe(f2, NULL);<br />

parinte(f1, f2);<br />

// Prin specificul problemei noastre cuncrete, nu se va ajunge la<br />

// instructiunile de mai jos. Le scriem totusi pentru exemplificare.<br />

DisconnectNamedPipe(f1);<br />

DisconnectNamedPipe(f2);<br />

CloseHandle(f1);<br />

CloseHandle(f2);<br />

}<br />

Sursa fifoc.cpp prezinta programul <strong>prin</strong>cipal al clientului:<br />

#include <br />

#include <br />

#include "mesaj.h"<br />

#include "ReadWrite.cpp"<br />

#include "dir.cpp"<br />

#include "fiu.cpp"


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 20 -<br />

main() {<br />

HANDLE f1, f2;<br />

f1=CreateFile("\\\\.\\PIPE\\fifo1", GENERIC_WRITE, FILE_SHARE_WRITE,<br />

NULL, OPEN_EXISTING, 0, NULL);<br />

f2=CreateFile("\\\\.\\PIPE\\fifo2", GENERIC_READ, FILE_SHARE_READ,<br />

NULL, OPEN_EXISTING, 0, NULL);<br />

fiu(f2, f1);<br />

CloseHandle(f1);<br />

CloseHandle(f2);<br />

}<br />

In final prezentam comenzile de compilare, de lansare in executie si cateva cereri de liste de<br />

fisiere:<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o fifos fifos.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o fifoc fifoc.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>start fifos<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>fifoc<br />

Dati: numar|sufix: 5|cpp<br />

66<br />

4 capitalizare.cpp<br />

21 compilerun.cpp<br />

36 dir.cpp<br />

44 execWin.cpp<br />

56 fifoc.cpp<br />

Dati: numar|sufix: 10|<br />

103<br />

4 ..<br />

7 a.exe<br />

13 b.bat<br />

19 capitalizare.cpp<br />

36 compilerun.cpp<br />

51 dir.cpp<br />

59 execWin.cpp<br />

71 fifoc.cpp<br />

81 fifoc.exe<br />

91 fifocre.cpp<br />

Dati: numar|sufix:<br />

<strong>1.</strong>5. Comunicarea între procese <strong>Windows</strong> <strong>prin</strong> memorie partajată<br />

<strong>1.</strong>5.<strong>1.</strong> Fi<strong>şi</strong>ere I/O mapate în memorie <strong>şi</strong> segmente de memorie partajata<br />

Maparea (găzduirea) fi<strong>şi</strong>erelor în memoria internă este o facilitate preluată de <strong>Windows</strong> de la<br />

sistemul de operare DOS. Această mapare permite aplicaţiilor să acceseze părţi ale fi<strong>şi</strong>erului<br />

mapat folosind pointeri din memoria internă, în loc de accesare a discului. Prin aceasta se<br />

obţine o viteză de acces mult mai mare. Printre alte avantaje ale mapării, mai amintim faptul<br />

că se beneficiază de mecanismul de cache-ing <strong>şi</strong> de paginare a memoriei interne, oferit de<br />

<strong>Windows</strong>.<br />

O aplicaţie poate mapa fi<strong>şi</strong>ere de la 1 octet până la 2G octeţi. Fişerele mapate în memorie<br />

permit, de asemenea, ca două sau mai multe procese să partajeze aceste fi<strong>şi</strong>ere <strong>şi</strong> implicit să<br />

partajeze memoria internă.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 21 -<br />

In continuare, prezentăm o succesiune de 5 (cinci) pa<strong>şi</strong> ce trebuie urmaţi de orice aplicaţie<br />

<strong>Windows</strong> care foloseşte o zonă de memorie partajată:<br />

<strong>1.</strong> Crearea segmentului de memorie partajată folosind funcţia CreateFileMapping.<br />

Această operaţie se poate realiza în două moduri:<br />

a) folosind un fi<strong>şi</strong>er definit anterior de către utilizator, cu ajutorul apelurilor<br />

CreateFile sau OpenFile.<br />

b) folosind o pagină sistem, specificată cu ajutorul unui handler predefinit, cu valoarea<br />

0xFFFFFFFF.<br />

Dacă segmentul de memorie partajatăError! Bookmark not defined. există deja,<br />

deschiderea accesului la segment se obţine cu ajutorul funcţiei OpenFileMapping.<br />

Obiectul <strong>Windows</strong>, asociat segmentului de memorie partajată, poartă numele de filemapping.<br />

Atât funcţia CreateFileMapping, cât <strong>şi</strong> funcţia OpenFileMapping întorc<br />

un handle la obiectul file-mapping asociat segmentului de memorie partajată referit.<br />

2. Maparea propriu-zisă a segmentului de memorie partajată (reprezentat <strong>prin</strong> obiectul filemapping),<br />

în spaţiul de memorie al procesului curent, este realizată cu ajutorul apelului<br />

MapViewOfFile. Această funcţie întoarce un pointer la o porţiune din această memorie<br />

partajată.<br />

3. Pointerul obţinut în urma apelului precedent, permite aplicaţiei apelante să acceseze zona<br />

de memorie partajată în citire <strong>şi</strong>/sau scriere, în funcţie de parametrii specificaţi la apel.<br />

4. Operaţia complementară celei de mapare, se realizează cu autorul apelului<br />

UnmapViewOfFile. Astfel, se realizează “detaşarea” aplicaţiei curente de la segmentul de<br />

memorie partajată, <strong>prin</strong> eliberarea spaţiului de memorie ocupat <strong>prin</strong> operaţia de mapare.<br />

5. In final, închiderea handle-ului la obiectul file-mapping se realizează cu funcţia<br />

CloseHandle. De asemenea, folosind CloseHandle, se închide fi<strong>şi</strong>erul deschis cu<br />

CreateFile sau OpenFile.<br />

Crearea / deschiderea în varianta <strong>1.</strong>b, cu handle-ul special 0xFFFFFFFF permite folosirea<br />

unei singure zone partajate în sistem. Folosind numai acest handle, NU este posibil ca pe<br />

acela<strong>şi</strong> sistem să existe mai multe zone de memorie partajată. Pentru a permite ca fiecare grup<br />

de procese să-<strong>şi</strong> folosească propria zonă de memorie partajată, trebuie să se folosească<br />

crearea / deschiderea în varianta <strong>1.</strong>a, CreateFile / OpenFile. Astfel, se crează /<br />

deschide câte un fi<strong>şi</strong>er mapat în memorie pentru fiecare zonă de memorie partajată dorită.<br />

In continuare, punctăm o comparaţie, la nivel API, între operaţiile de lucru cu memorie<br />

partajată sub Unix <strong>şi</strong> sub <strong>Windows</strong>:<br />

CreateFileMapping shmget<br />

MapViewOfFile shmat<br />

UnmapViewOfFile shmdt<br />

CloseHandle shmctl<br />

Sintaxa exactă, a funcţiilor implicate în utilizarea segmentelor de memorie partajată sub<br />

<strong>Windows</strong>, va fi descrisă mai jos.<br />

HANDLE CreateFileMapping(HANDLE hFis, LPSECURITY_ATTRIBUTES descr_sec,<br />

DWORD prot, DWORD maxHigh, DWORD maxLow, LPCTSTR nume_file_mapping);


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 22 -<br />

hFis –handle la fi<strong>şi</strong>erul de mapat; valoarea 0xFFFFFFFF pentru acest parametru indică o<br />

pagină implicită sistem.<br />

descr_sec –descriptor de securitate; dacă se specifică valoarea NULL pentru acest<br />

parametru, sistemul va folosi atributele de securitate implicite.<br />

prot –atribut de protecţie pentru obiectul de mapat.<br />

maxHigh, maxLow –compun dimensiunea pe 32 de biţi a obiectului de mapat.<br />

nume_file_mapping –numele obiectului file-mapping<br />

HANDLE OpenFileMapping( DWORD acces, BOOL bHandleMostenire,<br />

LPCTSTR nume_file_mapping);<br />

acces –indică modul de acces (citire <strong>şi</strong>/sau scriere)<br />

bHandleMostenire –indică dacă obiectul file-mapping va fi moştenit de eventualele procese<br />

fii<br />

nume_file_mapping –numele obiectului file-mapping<br />

LPVOID MapViewOfFile( HANDLE hFileMap, DWORD acces, DWORD offsetHigh,<br />

DWORD offsetLow, DWORD nrOctetiDeMapat);<br />

hFileMap –handle la obiectul file-mapping (handle returnat de CreateFileMapping)<br />

acces –indică modul de acces (citire <strong>şi</strong>/sau scriere)<br />

offsetHigh, offsetLow –compun offsetul pe 32 de biţi, a zonei de memorie de mapat<br />

nrOctetiDeMapat –numărul de octeţi de mapat<br />

BOOL UnmapViewOfFile(LPCVOID lpAdresaMapata);<br />

lpAdresaMapata –adresa de memorie unde începe maparea<br />

Pentru mai multe informatii legate de semnificaţia parametrilor <strong>şi</strong> prototipurile funcţiilor ce<br />

operarează cu memorie partajată sub <strong>Windows</strong>, se recomandă consultarea documentaţiei<br />

MSDN<br />

<strong>1.</strong>5.2. Semafoare <strong>Windows</strong><br />

Crearea unui obiect semafor windows de către un proces se face folosind apelul<br />

CreateSemaphore. Semaforul windows poate fi:<br />

� semafor cu nume, folosit pentru comunicarea între procesele de pe aceea<strong>şi</strong> ma<strong>şi</strong>nă. In<br />

acest scop, numele este elementul de identificare al semaforului de către toate procesele<br />

interesate, care folosesc acest nume în apelul OpenSemaphore.<br />

� semafor anonim, folosit pentru comunicarea între thread-urile aceluia<strong>şi</strong> proces. Elementul<br />

de identificare în această situaţie este constituit de handle-ul întors de funcţia<br />

CreateSemaphore, care este moştenit de thread-urile fii interesate.<br />

Prototipul CreateSemaphore este:<br />

HANDLE CreateSemaphore (LPSECURITY_ATTRIBUTES securitate,<br />

LONG valoareInitiala,<br />

LONG valoareMaxima,<br />

LPCTSTR numeSemafor);<br />

securitate –reprezintă un descriptor de securitate, care specifică dacă handlerul la<br />

semafor va fi moştenit în procesele fii; pentru valoarea NULL a acestui parametru, handlerul<br />

nu poate fi moştenit.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 23 -<br />

valoareInitiala – specifică valoarea iniţială a semaforului; trebuie să fie mai mare sau<br />

egală decât 0 <strong>şi</strong> mai mică sau egală decât valoareMaxima.<br />

valoareMaxima –indică valoarea maximă pe care o poate lua semaforul <strong>şi</strong> trebuie să fie<br />

mai mare decât 0.<br />

numeSemafor –specifică numele semaforului.<br />

Funcţia întoarce un handle la semafor, care poate fi utilizat în thread-urile fii.<br />

valoareInitiala <strong>şi</strong> valoareMaxima sunt numere nenegative. Dacă numeSemafor<br />

este un string, atunci avem de-a face cu un semafor cu nume, iar dacă această valoare este<br />

NULL avem de-a face cu semafor anonim.<br />

Se observă, că spre deosebire de semafoarele Unix, în acest caz se impune o limită superioară<br />

a valorii acestora.<br />

Odată creat un semafor cu nume, se poate face deschiderea accesului la semafor de către un<br />

proces. Deschiderea se face cu OpenSemaphore, care are prototipul:<br />

HANDLE OpenSemaphore (DWORD modDeAcces,<br />

BOOL mostenire,<br />

LPCTSTR numeSemafor);<br />

modDeAcces –indică modul de acces la semafor <strong>şi</strong> poate lua una (combinaţii):<br />

SEMAPHORE_ALL_ACCESS, SEMAPHORE_MODIFY_STATE, SYNCHRONIZE.<br />

(aceste valori pot fi restricţionate <strong>prin</strong> atribute de securitate la creare).<br />

mostenire –atribut de moştenire: indică (pentru valoarea TRUE) dacă handlerul la<br />

semafor va fi moştenit în procesele fii<br />

Orice semafor windows este caracterizat <strong>prin</strong> starea lui, care poate fi una dintre următoatele:<br />

� setat sau semnalat atunci când valoarea semaforului este strict pozitivă;<br />

� nesetat sau nesemnalat atunci când valoarea este egală cu zero.<br />

Cresterea valorii unui semafor se face <strong>prin</strong> apelul:<br />

BOOL ReleaseSemaphore(HANDLE hSemafor,<br />

LONG valoareDeAdaugat,<br />

LPLONG adresaVechiiValori );<br />

hSemafor –specifică handlerul semaforului de incrementat.<br />

valoareDeAdaugat –indică valoarea cu care se incrementează valoarea curentă a<br />

semaforului.<br />

adresaVechiiValori –specifică adresa unde va fi memorată vechea valoare a<br />

semaforului.<br />

Dacă semaforul hSemafor are valoarea maximă (specificată la creare), atunci acest apel de<br />

incrementare eşuează. De asemenea, dacă se specifică o valoare negativă pentru parametrul<br />

valoareDeAdaugat, apelul eşuează.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 24 -<br />

Decrementarea valorii semaforului este realizată atunci când asupra obiectului semafor se<br />

aplică o operaţie de aşteptare. Această operaţie se realizează <strong>prin</strong> intermediul funcţiilor de<br />

aşteptare, prezentate mai sus.<br />

<strong>1.</strong>5.3. Exemplul 8: lista de fisiere cu nume de anumita forma; implementare<br />

memorie partajata +semafoare<br />

In cele ce urmeaza, reluam exemplul 7 prezentat mai sus. Aici vom prezenta doar fisierele ce<br />

sunt modificate fata de acesta.<br />

Segmentul de memorie partajata va fi numit aici "SegmentMemoriePartajata", iar<br />

HANDLE la el shm.<br />

In segmentul de memorie partajata se va afla un singur mesaj, care poate fi o cerere sau un<br />

raspuns.<br />

Vom folosi doua semafoare:<br />

� unul pentru a asigura accesul exclusiv la segmentul de memorie partajata, numit<br />

"OcupaSegment" iar HANDLE la el semafor.<br />

� al doilea va indica daca in segment exista o cerere, semaforul este numit<br />

"ExistaCerere" iar HANDLE la el cerere.<br />

Sursa dir.cpp este identica cu cea de la exemplul de mai sus.<br />

Definirea tipului Mesaj si a structurii de date Segment aflata in segmentul de memorie<br />

partajata, este facuta in fisierul shmmesaj.h:<br />

#define MAXS 10000<br />

#define MAXL 1000<br />

typedef struct {<br />

int lung;<br />

int i;<br />

char s[MAXS];<br />

} Mesaj;<br />

#define PLUS (sizeof(int))<br />

typedef struct {<br />

int tip;<br />

int pid;<br />

Mesaj mesaj;<br />

} Segment;<br />

Segmentul de memorie partajata contine:<br />

� tip - identifica ce informatie se afla in segmentul de memorie partajata: -1 cerere, 1<br />

raspuns, 0 nimic.<br />

� pid - este PID-ul serverului. El va fi folosit de server si de clienti pentru trimitere spre<br />

server a unor semnale de adormire sau de trezire pentru rezolvarea unei cereri.<br />

� mesaj - este spatiul pentru mesaj, folosit atat pentru cerere, cat si pentru raspuns.<br />

Implementarea schimbului de mesaje inseamna de fapt copierea in / din cu segmentul de<br />

memorie partajata.<br />

Fisierul shmReadWrite.cpp contine functiile ReadMes si WriteMes


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 25 -<br />

void Wait(HANDLE semafor) {WaitForSingleObject(semafor, INFINITE);}<br />

void Signal(HANDLE semafor) {ReleaseSemaphore(semafor, 1, NULL);}<br />

Mesaj *ReadMes(Segment *segment) {<br />

static Mesaj mesaj;<br />

mesaj.lung = segment->mesaj.lung;<br />

memcpy((char*)&mesaj+sizeof(int),<br />

(char*)segment+3*sizeof(int), mesaj.lung);<br />

return &mesaj;<br />

}<br />

void WriteMes(Segment *segment, Mesaj *pm) {<br />

segment->mesaj.lung = pm->lung;<br />

memcpy((char*)segment+3*sizeof(int),<br />

(char*)pm+sizeof(int), pm->lung);<br />

}<br />

Actiunea <strong>prin</strong>cipala a serverului este dirijata de functia parinte. Sursa parintelui este in<br />

shmparinte.cpp<br />

void parinte(Segment *segment, HANDLE semafor, HANDLE cerere) {<br />

Mesaj *pm;<br />

for ( ; ; ) {<br />

Wait(cerere); // Doarme, asteptand cereri<br />

if (segment->tip != -1) continue;<br />

}<br />

}<br />

Wait(semafor);<br />

pm = ReadMes(segment);<br />

pm = dir(pm->i, pm->s);<br />

WriteMes(segment, pm);<br />

segment->tip = 1;<br />

Signal(semafor);<br />

Actiunea <strong>prin</strong>cipala a clientului este dirijata de functia fiu. Sursa fiului este in shmfiu.cpp<br />

void fiu(Segment *segment, HANDLE semafor, HANDLE cerere) {<br />

Mesaj *pm, mesaj;<br />

char *pc,linie[MAXL];<br />

int i;<br />

for ( ; ; ) {<br />

<strong>prin</strong>tf("Dati: numar|sufix: ");<br />

pc = (char*)fgets(linie, MAXL, stdin);<br />

if (pc == NULL) break;<br />

linie[strlen(linie)-1] = '\0';<br />

pc = strstr(linie, "|");<br />

if (pc == NULL) continue;<br />

mesaj.i = atoi(linie);<br />

strcpy(mesaj.s, pc+1);<br />

mesaj.lung = PLUS + strlen(mesaj.s) + 1;<br />

Wait(semafor);<br />

segment->tip = -1; // Cerere<br />

WriteMes(segment, &mesaj);<br />

Signal(semafor);<br />

Signal(cerere); //Desteptarea<br />

for( ; segment->tip != 1; Sleep(100)); // Eventual wait<br />

Wait(semafor);<br />

pm = ReadMes(segment);<br />

segment->tip = 0; //Liber<br />

Signal(semafor);


}<br />

}<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 26 -<br />

pc = pm->s;<br />

<strong>prin</strong>tf("%d\n",pm->lung);<br />

for (i = PLUS; i < pm->lung; ) {<br />

<strong>prin</strong>tf("%d %s\n", i, pc);<br />

i += strlen(pc) + 1;<br />

pc += strlen(pc) + 1;<br />

}<br />

In sfarsit vom prezenta programele <strong>prin</strong>cipale.<br />

Programului <strong>prin</strong>cipal al serverului este dat in shms.cpp:<br />

#include <br />

#include <br />

#include "shmmesaj.h"<br />

#include "shmReadWrite.cpp"<br />

#include "dir.cpp"<br />

#include "shmparinte.cpp"<br />

main() {<br />

HANDLE shm, semafor, cerere;<br />

Segment *segment;<br />

fclose(stdin);<br />

fclose(stdout);<br />

shm = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE,<br />

0, sizeof(Segment), "SegmentMemoriePartajata");<br />

segment = (Segment*)MapViewOfFile(shm, FILE_MAP_WRITE, 0, 0,<br />

sizeof(Segment));<br />

segment->tip = 0;<br />

segment->pid = GetCurrentProcessId();<br />

semafor = CreateSemaphore(NULL, 1, 1000, "OcupaSegment");<br />

cerere = CreateSemaphore(NULL, 0, 1000, "ExistaCerere");<br />

parinte(segment, semafor, cerere);<br />

// Prin specificul problemei noastre cuncrete, nu se va ajunge la<br />

// instructiunile de mai jos. Le scriem totusi pentru exemplificare.<br />

CloseHandle(cerere);<br />

CloseHandle(semafor);<br />

UnmapViewOfFile(segment);<br />

CloseHandle(shm);<br />

}<br />

Sursa clientului este data in shmc.cpp:<br />

#include <br />

#include <br />

#include "shmmesaj.h"<br />

#include "shmReadWrite.cpp"<br />

#include "shmfiu.cpp"<br />

main() {<br />

HANDLE shm, semafor, cerere;<br />

Segment *segment;<br />

shm = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE,<br />

"SegmentMemoriePartajata");<br />

segment = (Segment*)MapViewOfFile(shm, FILE_MAP_WRITE, 0, 0,<br />

sizeof(Segment));<br />

semafor = OpenSemaphore(EVENT_ALL_ACCESS, TRUE, "OcupaSegment");<br />

cerere = OpenSemaphore(EVENT_ALL_ACCESS, TRUE, "ExistaCerere");<br />

fiu(segment, semafor, cerere);<br />

CloseHandle(cerere);<br />

CloseHandle(semafor);<br />

UnmapViewOfFile(segment);<br />

CloseHandle(shm);


}<br />

In final iata cateva executii:<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 27 -<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o shms shms.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o shmc shmc.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>start shms<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>shmc<br />

Dati: numar|sufix: 5|p<br />

66<br />

4 capitalizare.cpp<br />

21 compilerun.cpp<br />

36 dir.cpp<br />

44 execWin.cpp<br />

56 fifoc.cpp<br />

Dati: numar|sufix: 10|<br />

109<br />

4 ..<br />

7 a.exe<br />

13 capitalizare.cpp<br />

30 compilerun.cpp<br />

45 dir.cpp<br />

53 execWin.cpp<br />

65 fifoc.cpp<br />

75 fifoc.exe<br />

85 fifocre.cpp<br />

97 fifodel.cpp<br />

Dati: numar|sufix:<br />

<strong>1.</strong>6. Cozi de mesaje sub <strong>Windows</strong><br />

<strong>1.</strong>6.<strong>1.</strong> Comunicarea între procese <strong>Windows</strong> <strong>prin</strong> Mailslot<br />

Arhitectura mailslot permite dezvoltarea de aplicaţii client/server care pot să comunice<br />

unidirecţional, de la client la server.<br />

Mailslot este o facilitate de <strong>comunicaţii</strong> în care un client, (de obicei staţie de lucru <strong>Windows</strong>),<br />

poate transmite <strong>prin</strong> datagramă un mesaj:<br />

� unui server mailslot -unicast;<br />

� serverelor mailslot din reţeaua locală care aparţin unui domeniu stabilit de administrator -<br />

multicast;<br />

� tuturor serverelor mailslot din reţeaua locală - broadcast;<br />

O comunicare de la client spre server se constituie într-un mesaj, deci serverul, dacă primeşte<br />

mesajul, este sigur că acesta conţine mesajul complet. Din cauză că se operează cu datagrame<br />

(pentru spor de viteză), nu este asigurată livrarea, nici livrarea de duplicate. Aşa că<br />

proiectantul trebuie să-<strong>şi</strong> proiecteze mecanisme proprii de control al livrărilor. Este adevărat<br />

că, într-o reţea LAN bine construită <strong>şi</strong> bine configurată, probabilitatea de eşec a unei livrări<br />

este foarte mică.<br />

Sintaxa numelor folosite în mailslot este prezentată în tabelul de mai jos:


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 28 -<br />

Sintaxa Descriere<br />

\\.\mailslot\numeMailslot Serverul mailslot de pe ma<strong>şi</strong>na locală<br />

\\adresaMasina\mailslot\numeMailslot Serverul mailslot de pe ma<strong>şi</strong>na având<br />

adresa specificată<br />

\\numeDomeniu\mailslot\numeMailslot Multicast spre toate serverele mailslot<br />

care fac parte din domeniul specificat<br />

\\*\mailslot\numeMailslot Broadcast spre toate serverele<br />

mailslot din reţeaua LAN<br />

Un server mailslot creează un obiect mailslot care citeste mesajele trimise de clienţi. Serverul<br />

<strong>şi</strong> clienţii se pot afla pe aceea<strong>şi</strong> ma<strong>şi</strong>nă (local) sau pot rula pe două ma<strong>şi</strong>ni diferite.<br />

Crearea unui server mailslot se face cu funcţia CreateMailslot. Stabilirea unui contact<br />

de către un client cu serverul - deschidere -de mailslot se face <strong>prin</strong> funcţia CreateFile.<br />

Citirea unui mesaj de către server se face <strong>prin</strong> funcţia ReadFile. Scrierea de către client a<br />

unui mesaj se face <strong>prin</strong> funcţia WriteFile.<br />

Inchiderea unui mailslot, atât la client cât <strong>şi</strong> la server se face folosind funcţia<br />

CloseHandle.<br />

In continuare, prezentăm prototipul funcţiei CreateMailslot.<br />

HANDLE CreateMailslot (LPCTSTR lpszName, DWORD cbMaxMsg,<br />

DWORD dwReadTimeout, LPSECURITY_ATTRIBUTES lpsa);<br />

lpszName– specifică numele mailslot indicând obligatoriu ma<strong>şi</strong>na locală, conform tabelului<br />

de mai sus.<br />

cbMaxMsg – indică dimensiunea maximă a mesajului în octeţi. Serverul poate primi unul<br />

sau mai multe mesaje, fiecare trebuind sã fie egal sau mai mic decât dimensiunea maximã<br />

specificatã. Nu existã limitã pentru numãrul de mesaje care se pot recepţiona. Serverul poate<br />

specifica NULL ca <strong>şi</strong> dimensiune maximã pentru a putea recepţiona mesaje de orice<br />

dimensiune acceptatã de sistem.<br />

dwReadTimeout – specificã, în milisecunde, perioada implicitã de time-out asociatã citirii<br />

unui mesaj din mailslot cand acesta este gol. Valoarea 0 inseamnã cã ReadFile se opreste<br />

imediat fãrã nici o eroare când mailslot-ul este vid. Valoarea<br />

MAILSLOT_WAIT_FOREVER asigurã serverul cã ReadFile nu se va bloca panã când<br />

este accesibil un mesaj. Dacã existã deja un mesaj în coada de mesaje atunci functia<br />

ReadFile se va executa imediat indiferent de valoarea dwReadTimeout.<br />

lpsa– permite asocierea unei variabile de tipul SECURITY_ATRIBUTES mailslot-ului.<br />

Astfel, serverul poate restricţiona accesul la mailslot. Specificând NULL ca <strong>şi</strong> atribut de<br />

securitate, se lasã în seama sistemului de operare.<br />

În urma apelului CreateMailslot() serverul obţine un handle către noul mailslot creat,<br />

pe care îl foloseste pentru a citi mesaje folosind funcţia ReadFile. Serverul specifică<br />

funcţiei handle-ul respectiv <strong>şi</strong> un buffer unde doreşte să recepţioneze mesajele.


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 29 -<br />

Dacă există deja un mesaj în coada de mesaje funcţia ReadFile se execută imediat copiind<br />

în bufer mesajul. Dacă coada de mesaje este goală, funcţia va aştepta până în mailslot vor fi<br />

atâţia octeţi câţi aşteaptă funcţia.<br />

Un client mailslot este un program care trimite mesaje către un server mailslot. dimensiunea<br />

mesajului este limitată la 64K octeţi atunci când clientul scrie către un anumit mailslot,<br />

respectiv la 400 octeţi atunci când clientul trimite un broadcast către toate serverele mailslot<br />

din domeniu.<br />

<strong>1.</strong>6.2. Exemplul 9: lista de fisiere cu nume de anumita forma; implementare<br />

mailslot<br />

In cele ce urmeaza, reluam exemplul 7 prezentat la mai sus. Aici vom prezenta doar fisierele<br />

ce sunt modificate fata de acest exemplu.<br />

Sursa dir.cpp este identica cu cea deja prezentată.<br />

Pentru comunicarea <strong>prin</strong> mailslot, folosim fisierul msgmesaj.h:<br />

#define MAXS 10000<br />

#define MAXL 1000<br />

typedef struct {<br />

int lung;<br />

long pid;<br />

int i;<br />

char s[MAXS];<br />

} Mesaj;<br />

#define PLUS (sizeof(int))<br />

S-a introdus in plus campul pid. In el este retinut PID-ul clientului solicitant. Spre deosebire<br />

de implementarea <strong>prin</strong> <strong>pipe</strong> cu nume, aici serverul aplicatiei, cel care livreaza liste cu nume<br />

de fisiere, poate servi mai multi clienti. Fiecare client solicitant isi pune in mesaj PID-ul,<br />

pentru ca serverul sa stie cui sa ii intoarca lista.<br />

Aceasta implementare are un mailslot pe serverul aplicatiei, in care fiecare client isi scrie<br />

cererile. Din cauza specificului mailslot, si pentru a folosi tot mailslot pentru raspunsuri,<br />

fiecare client isi creeaza un mailslot propriu (numele lui contine PID). Prin intermediul<br />

acestuia serverul aplicatiei ii scrie clientului raspunsul.<br />

Pentru implementarea schimbului de mesaje sunt folosite functiile ReadMes si WriteMes.<br />

Continutul acestora este prezentat in sursa msgReadWrite.cpp<br />

Mesaj *ReadMes(HANDLE canal) {<br />

static Mesaj mesaj;<br />

DWORD no;<br />

ReadFile(canal, (char*)&mesaj, sizeof(Mesaj), &no, NULL);<br />

return &mesaj;<br />

}<br />

void WriteMes(HANDLE canal, Mesaj *pm) {<br />

DWORD no;<br />

WriteFile(canal, (char*)pm, sizeof(Mesaj), &no, NULL);<br />

}


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 30 -<br />

Se scriu si se citesc mesaje de aceeasi lungime (maxima).<br />

Actiunea <strong>prin</strong>cipala a serverului este dirijata de functia parinte care are doar un parametru,<br />

descriptorul mailslotului de pe server. Sursa serverului este data in fisierul<br />

msgparinte.cpp<br />

void parinte(HANDLE in) {<br />

Mesaj *pm;<br />

HANDLE out;<br />

long pid;<br />

char nume[100];<br />

for ( ; ; ) {<br />

pm = ReadMes(in);<br />

pid = pm->pid;<br />

pm = dir(pm->i, pm->s);<br />

s<strong>prin</strong>tf(nume, "\\\\.\\mailslot\\p2f%d", pid);<br />

out = CreateFile(nume, GENERIC_WRITE,<br />

FILE_SHARE_WRITE|FILE_SHARE_READ,<br />

NULL, OPEN_EXISTING, 0, NULL);<br />

WriteMes(out, pm);<br />

CloseHandle(out);<br />

}<br />

}<br />

In mod ciclic, serverul aplicatiei repeta:<br />

� Parintele asteapta (cea mai mare parte a timpului) sa citeasca un mesaj din mailslotul<br />

lui (cel ce contine cereri).<br />

� Preia din cerere PID-ul clientului.<br />

� Intocmeste lista de fisiere ceruta.<br />

� Creeaza un fisier de acces la mailslotul clientului.<br />

� Ii scrie acestuia mesajul de raspuns.<br />

� Inchide fisierul creeat.<br />

Actiunea <strong>prin</strong>cipala a clientului este dirijata de functia fiu, unde in si out sunt descriptorii<br />

celor doua mailsloturi cu care lucreaza, iar pid este PID-ul clientului. Sursa clientului este<br />

data in fisierul msgfiu.cpp<br />

void fiu(HANDLE in, HANDLE out, long pid) {<br />

Mesaj *pm, mesaj;<br />

char *pc,linie[MAXL];<br />

int i;<br />

for ( ; ; ) {<br />

<strong>prin</strong>tf("Dati: numar|sufix: ");<br />

pc = (char*)fgets(linie, MAXL, stdin);<br />

if (pc == NULL) break;<br />

linie[strlen(linie)-1] = '\0';<br />

pc = strstr(linie, "|");<br />

if (pc == NULL) continue;<br />

mesaj.pid = pid;<br />

mesaj.i = atoi(linie);<br />

strcpy(mesaj.s, pc+1);<br />

mesaj.lung = PLUS + strlen(mesaj.s) + 1;<br />

WriteMes(out, &mesaj);<br />

pm = ReadMes(in);<br />

pc = pm->s;<br />

<strong>prin</strong>tf("%d\n",pm->lung);<br />

for (i = PLUS; i < pm->lung; ) {<br />

<strong>prin</strong>tf("%d %s\n", i, pc);


}<br />

}<br />

}<br />

<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 31 -<br />

i += strlen(pc) + 1;<br />

pc += strlen(pc) + 1;<br />

Fata de sursa corespondenta din exemplul de mai sus, aici apare in plus un parametru si<br />

atribuirea <strong>prin</strong> care este depus in mesaj.<br />

In sfarsit vom prezenta programele <strong>prin</strong>cipale. Sursa programului <strong>prin</strong>cipal al serverului este<br />

data in msgs.cpp<br />

#include <br />

#include <br />

#include "msgmesaj.h"<br />

#include "ReadWrite.cpp"<br />

#include "dir.cpp"<br />

#include "msgparinte.cpp"<br />

main() {<br />

HANDLE f1;<br />

fclose(stdin);<br />

fclose(stdout);<br />

f1 = CreateMailslot("\\\\.\\mailslot\\f2p", sizeof(Mesaj),<br />

MAILSLOT_WAIT_FOREVER, NULL);<br />

parinte(f1);<br />

// Prin specificul problemei noastre cuncrete, nu se va ajunge la<br />

// instructiunile de mai jos. Le scriem totusi pentru exemplificare.<br />

CloseHandle(f1);<br />

}<br />

Se creeaza mailslotul pentru cereri dupa care se apeleaza functia parinte. Sursa programului<br />

<strong>prin</strong>cipal al clientului este data in msgc.cpp<br />

#include <br />

#include <br />

#include "msgmesaj.h"<br />

#include "ReadWrite.cpp"<br />

#include "fiu.cpp"<br />

main() {<br />

HANDLE f1, f2;<br />

long pid = GetCurrentProcessId();<br />

char nume[100];<br />

f2 = CreateFile("\\\\.\\mailslot\\f2p", GENERIC_WRITE,<br />

FILE_SHARE_WRITE|FILE_SHARE_READ,<br />

NULL, OPEN_EXISTING, 0, NULL);<br />

s<strong>prin</strong>tf(nume, "\\\\.\\mailslot\\p2f%d", pid);<br />

f1 = CreateMailslot(nume, sizeof(Mesaj),<br />

MAILSLOT_WAIT_FOREVER, NULL);<br />

fiu(f1, f2, pid);<br />

CloseHandle(f1);<br />

CloseHandle(f2);<br />

}<br />

Aici se creeaza mai intai fisierul de acces la mailslotul de cereri. Apoi se creeaza un mailslot<br />

propriu, <strong>prin</strong> intermediul caruia serverul ii trimite raspunsul la cerere. Dupa cum se poate<br />

vedea din sursele msgc.cpp, msgs.cpp si msgparinte.cpp, se fac mai intai<br />

creerile legate de mailslotul de cereri (al serverului aplicatiei), apoi cele ale mailslotului<br />

propriu al clientului <strong>prin</strong> care primeste raspunsul. A se revedea observatia facuta mai sus<br />

pentru cazurile cand se folosesc mai multe mailsloturi.<br />

In final iata cateva executii:


<strong>Procese</strong> <strong>Windows</strong>; <strong>comunicaţii</strong> <strong>prin</strong> <strong>pipe</strong> <strong>şi</strong> <strong>IPC</strong> - 32 -<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o msgs msgs.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>gcc -o msgc msgc.cpp<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>start msgs<br />

D:\Florin\Didactic\20102011\SO\probleme\WProc-H>msgc<br />

Dati: numar|sufix: 5|p<br />

66<br />

4 capitalizare.cpp<br />

21 compilerun.cpp<br />

36 dir.cpp<br />

44 execWin.cpp<br />

56 fifoc.cpp<br />

Dati: numar|sufix: 10|<br />

109<br />

4 ..<br />

7 a.exe<br />

13 capitalizare.cpp<br />

30 compilerun.cpp<br />

45 dir.cpp<br />

53 execWin.cpp<br />

65 fifoc.cpp<br />

75 fifoc.exe<br />

85 fifocre.cpp<br />

97 fifodel.cpp<br />

Dati: numar|sufix:

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!