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
- TAGS
- procese
- pipe
- www.cs.ubbcluj.ro
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: