Jump to content

SUBIECTE NOI
« 1 / 5 »
RSS
casa verde 2024

Intrerupator cu N - doza doar cu ...

Incalzire casa fara gaz/lemne

Incalzire in pardoseala etapizata
 Suprataxa card energie?!

Cum era nivelul de trai cam din a...

probleme cu ochelarii

Impozite pe proprietati de anul v...
 teava rezistenta panou apa calda

Acces in Curte din Drum National

Sub mobila de bucatarie si sub fr...

Rezultat RMN
 Numar circuite IPAT si prindere t...

Pareri brgimportchina.ro - teapa ...

Lucruri inaintea vremurilor lor

Discuții despre TVR Sport HD.
 

Misterele bibliotecilor (DLL-urilor)

- - - - -
  • Please log in to reply
13 replies to this topic

#1
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007

Quote

DLL-uri ... e plin Windows-ul de eleș când instalam un program mai apar nu știu câte ... dar ce sunt ele? Care-i rolul lor?

Asa cum le zice și numele (DLL - Dynamic Link Library - Bibliotecă legată dinamic), DLL-urile sunt biblioteci de funcții.

Quote

Biblioteci de functii? Ce-s astea și de ce am nevoie de ele?

Atunci când scrii un program, pe lângă celebra funcție int main() (ar trebui să) mai ai o gramadă de funcții care rezolvă diverse parți individuale ale problemei. Când scrii tot mai multe aplicații, începi să observi că o grămadă de funcții se tot repetă.

Quote

Bun, bun ... dar mi-e greu să copiez codul acelor funcții în fiecare program?

Da ... ce faci dacă constați o greșeală în implementarea unei funcții? Te pui sa cauți toate proiectele în care ai folosit-o pentru a o actualiza?

Bibliotecile legate dinamic (.dll în cazul MS Windows, .so în cazul *nix) vin ca o soluție la aceste probleme. Ele conțin implementarea (codul obiect - compilat) a diverselor funcții. De exemplu: biblioteca standard C msvcrt.dll conține implementarea funcțiilor des utilizate în C precum printf(), pow() etc.
Ați văzut pe undeva codul sursă al acestor funcții? Nu, și nici nu aveți nevoie de el. Trebuie să știți semnătura funcției (nume, parametrii, ce returnează) și unde ii găsiți implementarea, lăsând pe seama celor ce au dezvoltat biblioteca standard să se ocupe de codul ei efectiv.

Quote

Ok ... si cum vad ce funcții îmi pune la dispoziție un DLL?

Foarte simplu, utilizând Dependency Walker
Attached File  dll1.png   81.99K   234 downloads

Dupa cum se poate observa, biblioteca din imagine conține implementarea a peste 1000 de funcții. Se mai observă însă și că, în cazul funcțiilor "C", putem vedea doar numele funcției. Cel ce a dezvoltat biblioteca trebuie să ne ofere semnătura acesteia.
  • Încărcarea la runtime a unei biblioteci și apelarea unei funcții "C" exportată de aceasta

    Asa cum am mai menționat, MS Windows vine la pachet cu o gramadă de DLL-uri, conținând tot felul de funcții utile. În acest exemplu voi afisa un mesaj într-o căsuță caracteristică (message box).

    Quote

    Cu ce încep?

    Cu lecturarea documentației. În acesta, se prezintă încă de la început antetul funcției ce ne interesează.

    Așa cum am mai menționat (și cum se observă și mai jos în pagină), în cazul funcțiilor ce implică șiruri de caractere, WinAPI pune la dispoziție 2 variante ale acestora: cu sufixul A pentru șiruri de caractere "simple", ANSI, respectiv cu sufixul W pentru șiruri de caractere wide, unicode.

    În acest exemplu voi folosi varianta ANSI, și voi declara prin urmare un tip de date (pointer spre o funcție) în felul următor:
    typedef int (WINAPI *TipFunctieMessageBoxA)(HWND hWnd, const char* lpText, const char* lpCaption, UINT uType);
    

    În continuare declar o instanță a acestui tip de date (pointer la funcție) pe care o denumesc (pentru simplificare) "functie":
    TipFunctieMessageBoxA functie;
    

    Întrucât aici apar tipuri de date necunoscute, va trebui inclus headerul necesar Windows.h
    WINAPI reprezintă calling convention și este, în acest caz, un sinonim pentru __stdcall.

    Am definit un pointer la o funcție ... dar acesta momentan nu indică către nici o adresă validă din memorie și, deci, nu poate fi încă folosit.

    Pasul următor îl reprezintă încarcarea DLL-ului în memorie. Aceasta se realizează utilizând funcția LoadLibrary ce primește ca parametru locația de pe disc a bibliotecii și returnează un handle către aceasta sub forma unui HMODULE.

    Quote

    De unde știu ce bibliotecă să încarc?

    Scrie în documentație: User32.dll

    Ca mai orice în lumea asta virtuală, ceva odata încărcat trebuie și eliberat, lucru ce se va realiza la final folosind funcția FreeLibrary

    Odată încărcat DLL-ul în memorie, mai lipsește un singur pas, găsirea adresei funcției ce ne interesează precum și stocarea acestei adrese în pointerul mai sus definit. Pentru aceasta se va folosi GetProcAddress
    functie = (TipFunctieMessageBoxA)GetProcAddress(handleDLL, "MessageBoxA");
    

    Așa cum menționează documentația, funcția GetProcAddress returnează NULL în cazul în care nu a putut găsi adresa funcției cerute (ex. ea nu există), acest lucru trebuind verificat întotdeauna.

    if (NULL == functie)
    {
       _tprintf(_T("Funcția cerută nu a putut fi găsită"));
    }
    else
    {
       functie(0, "Mesaj", "Titlu", 0);
    }
    
    

    Acum se poate apela funcția, rezultatul observându-se la rulare
    Attached File  dll2.png   7.36K   190 downloads

    Codul complet al fișierului principal
    #include "stdafx.h"
    
    typedef int (WINAPI *TipFunctieMessageBoxA)(HWND hWnd, const char* lpText, const char* lpCaption, UINT uType);
    TipFunctieMessageBoxA functie;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
       HMODULE handleDLL = LoadLibrary(_T("User32.dll"));
    
       functie = (TipFunctieMessageBoxA)GetProcAddress(handleDLL, "MessageBoxA");
    
       if (NULL == functie)
       {
    	  _tprintf(_T("Funcția cerută nu a putut fi găsită"));
       }
       else
       {
    	  functie(0, "Mesaj", "Titlu", 0);
       }
    
       FreeLibrary(handleDLL);
    
       return 0;
    }
    
    

    Printre avantajele acestei metode se numără:
    • Se poate decide la runtime dacă chiar e nevoie de o anumită bibliotecă
    • Numele bibliotecii respectiv al funcțiilor este specificat sub forma unui șir de caractere; poate nici nu este cunoscut în momentul compilării codului
    • Se pot încărca la modul acesta toate DLL-urile dintr-un anumit folder, cum procedează de exemplu aplicațiile ce au diverse plugin-uri

    Cum nu pot exista doar avantaje, există și un mare dezavantaj: în cazul bibliotecilor standard, și nu numai, este prea anevoios ca pentru fiecare funcție ce dorim s-o folosim să declarăm un pointer spre ea, să apelăm GetProcAddress, etc. Acest lucru este evitat în cazul utilizării metodei următoare

  • "Legarea" de o bibliotecă la compilare

    Pentru a evita inconvenientele primei metode, există și una mai facilă, compusă din doar 2 pași:

    • Se declară toate funcțiile ce se doresc folosite, împreună cu toate tipurile de date necesare. Cum mai toți dezvoltatorii de biblioteci oferă fișiere header (.h) ce conțin aceste informații, este suficientă includerea respectivelor headere.
    • Se specifică o bibliotecă de import ce va fi folosită de linker pentru a face legătura între executabil și biblioteca dinamică
      • Visual C++ - biblioteciile de import sunt de forma nume.lib
        cl ... /link nume.lib
        
        sau (din cod, functioneaza insa doar cu VC++)
        #pragma comment (lib, "nume")
        
        sau (având în vedere că majoritatea il folosește) din mediul de dezvoltare
      • GCC - biblioteciile de import sunt de forma libnume(.dll).a
        gcc ... -lnume
        

    Quote

    Pentru a folosi printf() n-a trebuit să fac așa ceva, și a mers...

    Biblioteciile standard sunt implicit încărcate.

    În cazul utilizării acestei metode, programul va da o eroare chiar la pornire dacă vreunul din DLL-urile necesare lipsește sau este mai vechi și nu conține vreuna din funcțiile folosite.

    Dacă se încarcă executabilul în Dependency Walker, se pot observa toate DLL-urile necesare, respectiv fiecare funcție importată din fiecare bibliotecă.

  • extern "C"

    Una din diferențele dintre C++ și C este că C++ permite definirea mai multor funcții având același nume, cât timp numărul și/sau tipul parametriilor este diferit (pentru a nu exista dubii cu privire la care funcție trebuie apelată în fiecare situație).

    Intern, treaba aceasta se rezolvă adăugând un prefix fiecărei funcții, prefix ce codifică tipul fiecărui parametru. Acest lucru se întâmplă implicit pentru toate funcțiile dintr-o aplicație scrisă în C++.

    Apare însă o problemă, dacă dorim să importăm o funcție f dintr-o bibliotecă C, va fi căutată defapt funcția fsufix și va rezulta o eroare, fiindcă aceasta nu există (în bibliotecă).

    Pentru a evita această situație, funcția trebuie declarată în interiorul extern "C". Astfel ea va fi exportată/importată fără sufix în nume.

    extern "C"
    {
       int functie (float x);
    }
    
    

  • Scrierea unei biblioteci

    Așa cum o aplicație obișnuită (scrisă în C) este compusă din funcții (dintre care una main), așa și un DLL este compus din funcții, cea principală purtând în acest caz numele DllMain

    BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    	case DLL_THREAD_ATTACH:
    	case DLL_THREAD_DETACH:
    	case DLL_PROCESS_DETACH:
    		break;
    	}
    	return TRUE;
    }
    
    

    La crearea unui noi proiect de tip DLL din Microsoft Visual C++ este furnizat deja codul de mai sus. Se observă că rolul acestei funcții este ușor diferit de cel al funcției main dintr-o aplicație obișnuită. DllMain este apelat când au loc diverse evenimente de bază, gen atașarea respectiv detașarea bibliotecii la un proiect, punând astfel la dispoziție, de exemplu, un loc pentru alocarea/inițializarea/dealocarea variabilelor globale. Nu, nu trebuie apelate din DllMain toate funcțiile din bibliotecă.

    Nu toate funcțiile dintr-un DLL trebuie exportate (puse la dispoziția publicului larg).
    Cele ce se vor exportate se vor marca prin
    __declspec(dllexport)
    

    Întrucât, în mod normal, se va furniza un fișier header conținând descrierile funcțiilor către cei ce urmează să utilizeze librăria, s-a găsit o modalitate ușoară de a scrie un singur header atât pentru crearea librăriei cât și pentru utilizarea ei:

    #ifdef BUILD_DLL
    	#define DLL_EXPORT __declspec(dllexport)
    #else
    	#define DLL_EXPORT __declspec(dllimport)
    #endif
    

    Funcțiile ce se vor exportate vor fi prin urmare decorate în felul următor:
    void DLL_EXPORT functie(int parametru);
    

    Atunci când se crează biblioteca se va defini BUILD_DLL.
    Pa lângă DLL, în urma compilării se va obține și biblioteca de import.

    Dacă se utilizează MinGW pentru crearea bibliotecii, parametrul cel mai important ce trebuie furnizat este -shared

    Quote

    mingw32-g++.exe -Wall -DBUILD_DLL -g  -IC:\mingw\include -IC:\mingw\lib\gcc\mingw32\4.4.0\include -c D:\Projects\dll_tutorial\main.cpp -o obj\Debug\main.o
    mingw32-g++.exe -shared -Wl,--output-def=bin\Debug\libdll_tutorial.def -Wl,--out-implib=bin\Debug\libdll_tutorial.a -Wl,--dll -LC:\mingw\lib  obj\Debug\main.o   -o bin\Debug

    Exemplul din Visual Studio (obținut prin bifarea Export symbols la crearea unui nou proiect) ilustrează și cum pot fi exportate funcțiile membre ale unei clase, în cazul C++.

Edited by dani.user, 05 February 2014 - 18:36.


#2
neagu_laurentiu

neagu_laurentiu

    Guru Member

  • Grup: Senior Members
  • Posts: 40,570
  • Înscris: 30.07.2003
O mica sugestie, conceptul il intalnim si in Unix-like: http://www.yolinux.c...AndDynamic.html

#3
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007
Corect, am mentionat si asta.

#4
pax0xFF

pax0xFF

    Member

  • Grup: Members
  • Posts: 869
  • Înscris: 21.10.2012
Foarte reusite tutorialele, sper sa continui sa scrii! :)

#5
neagu_laurentiu

neagu_laurentiu

    Guru Member

  • Grup: Senior Members
  • Posts: 40,570
  • Înscris: 30.07.2003
Crezi ca are rost sa scrie tot ce include o carte sau documentatia furnizorului ? Te-a introdus in poveste, ai o idee iar mai departe e numai munca ta, literatura de specialitate exista.

Edited by neagu_laurentiu, 05 March 2013 - 13:05.


#6
pax0xFF

pax0xFF

    Member

  • Grup: Members
  • Posts: 869
  • Înscris: 21.10.2012
M-am referit la continuarea scrierii de tutoriale despre anumite concepte, nu si dezvoltarea lor. Dezvoltarea stiu ca se gaseste in carti de specialitate. Dar ca sa iti iei startul, sunt foarte bune.

Sincer sa fiu, ce a scris el in acest tutorial nu am gasit in carti. In carti am gasit conceptul de biblioteca, nu de DLL. Un mic tutorial ca cel de sus e binevenit oricand.

Edited by pax0xFF, 06 March 2013 - 10:41.


#7
neagu_laurentiu

neagu_laurentiu

    Guru Member

  • Grup: Senior Members
  • Posts: 40,570
  • Înscris: 30.07.2003

 pax0xFF, on 06 martie 2013 - 10:33, said:

In carti am gasit conceptul de biblioteca, nu de DLL.
http://msdn.microsof...9(v=vs.85).aspx
http://www.scribd.co...mming-WindowsÂź

#8
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007
Acum că am atins toate subpunctele, urmează şi o temă de casă :D

Să se scrie o aplicaţie ce va sorta un vector (mare, câţiva zeci de MB) de întregi prin diverşi algoritmi şi care va cronometra cât durează fiecare sortare.

  • În codul programului (.exe) nu se va implementa nici un algoritm de sortare
  • Programul va încărca din directorul relativ \plugins toate DLL-urile ce le va găsi acolo şi va apela din ele funcţia Sort() pentru a obţine un vector sortat
  • Pentru a putea oferi tuturor algoritmilor aceleaşi date pentru sortare, vectorul de întregi va fi citit dintr-un fişier mare de pe disc (găşiţi voi ceva fişier de zeci/sute de MB). Vectorul va fi recitit de pe disc înainte de fiecare nouă sortare
  • Biblioteciile plugin vor implementa următoarele 2 funcţii:
    • void GetName(char[] name, size_t size)
      
      Completează în vectorul name de dimensiune size numele algoritmului de sortare implementat. Toate caracterele neutilizate vor fi iniţializate cu '\0'.
    • void Sort(int[] data, size_t size)
      
      Sortează vectorul data de dimensiune size iar la final data va conţine vectorul sortat.
  • Programul va afişa rezultatul în forma:
    Nume_Algoritm_1 - 20.045 sec
    Nume_Algoritm_2 - 1.452 sec
    Nume_Algoritm_3 - 134.344 sec
    
    


#9
OriginalCopy

OriginalCopy

    I'm harmful, fear me please! :))

  • Grup: Senior Members
  • Posts: 27,268
  • Înscris: 10.08.2006
Cred că ar mai fi bună o funcţie (sau un câmp) char* GetAuthor(void), pentru a putea testa acelaşi algoritm implementat de diferiţi autori, şi a şti care rezultat al cui este.

#10
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007
Nu-i rea ideea.
Pentru început aș fi fericit și dacă reușesc 2-3 să implementeze ideea inițială.

#11
adrian93

adrian93

    Active Member

  • Grup: Members
  • Posts: 1,740
  • Înscris: 29.10.2009
Postez aici cam tarziu (dupa un an si ceva), insa de abia acuma am vazut tema aceasta. Am optat pentru o implementare in C.

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#define MICRO 1000000
int main(int argc, char **argv)
{
int* array = NULL;
size_t bytes = 0;
size_t count = 0;
size_t trash = 100;
void* handle;
void (*sort)(int*, size_t);
void (*getName)(char*, size_t);
char* error, *name;
char relPath[250];
struct timeval tick, tock;
FILE* bignum = fopen("data.in", "rb");
if(bignum == NULL)
{
  printf("Error on opening the file\n");
  return -1;
}
fseek(bignum, 0, SEEK_END);
bytes = ftell(bignum);
fseek(bignum, 0, SEEK_SET);
count = bytes / sizeof(int);
DIR *plugins;
struct dirent *file;
if((plugins = opendir("./plugins")) == NULL)
{
  printf("Error on opening the folder\n");
  return -2;
}
while((file = readdir(plugins)) != NULL)
{
  if(file -> d_name[0] != '.')
  {
   gettimeofday(&tick, NULL);
   strcpy(relPath, "./plugins/");
   strcat(relPath, file -> d_name);
	 
   handle = dlopen(relPath, RTLD_LAZY);
   if(!handle)
   {
	printf("Error on %s\n", relPath);
	return -3;
   }
   sort = dlsym(handle, "Sort");
   if((error = dlerror()) != NULL)
   {
	printf("%s\n", error);
	return -4;
   }
   fseek(bignum, 0, SEEK_SET);
   array = (int*)calloc(count, sizeof(int));
   fread(array, sizeof(int), count, bignum);
   (*sort)(array, bytes);
   gettimeofday(&tock, NULL);
   getName = dlsym(handle, "GetName");
   if((error = dlerror()) != NULL)
   {
	printf("%s\n", error);
	return -4;
   }
		
   name = (char*)calloc(trash, sizeof(char));
   (*getName)(name, trash);
   printf("%s - %.3f sec\n", name,
	(tock.tv_sec + tock.tv_usec / (1.0 * MICRO)) -
	(tick.tv_sec + tick.tv_usec / (1.0 * MICRO)));
   dlclose(handle);
  }
}

closedir(plugins);
fclose(bignum);
return 0;
}


Compilare:
gcc dlltest.c -o dlltest.out -ldl


Rezultate:
adrian@adrian-EP41-UD3L ~/Desktop/softsorter $ dd if=/dev/urandom of=./data.in bs=1024 count=100000
100000+0 records in
100000+0 records out
102400000 bytes (102 MB) copied, 19,2229 s, 5,3 MB/s
adrian@adrian-EP41-UD3L ~/Desktop/softsorter $ ./dlltest.out

shellSort - 46.880 sec
quickSort - 15.480 sec
bubbleSort & cocktailSort & strandSort - n-as vrea sa astept o eternitate...


Nu cred ca am inteles cum ar trebui implementate functiile GetName. De ce este nevoie de parametrul size_t size?
Eu le-am implementat astfel:
void GetName(char name[], size_t size)
{
	strcpy(name, "shellSort");
}


cu sirul respectiv alocat in programul principal. E cam aiurea ca e alocat mereu dinamic in bucla aia, cand ar putea fi alocat chiar static avand in vedere ca nu modific dimensiunea (junk), insa l-am pastrat asa pentru ca m-am gandit ca poate o sa modific nitel dupa ce inteleg care e treaba cu size din GetName + calloc se ocupa de initializarea cu \0 peste tot Posted Image.
Astept code review-ul vostru.

EDIT: Link si pe Pastebin, ca sa arate un pic mai ok indentarea.
http://pastebin.com/GB95DuKF

Edited by adrian93, 15 June 2014 - 21:35.


#12
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007
Felicitari pentru incercare.
Acel size in GetName e pentru a stii cat e name[] de mare, sa nu scrii cumva in el mai mult decat incape.

Numele il poti declara linistit static:
char name[128] = {0};
getName(name, sizeof(name)/sizeof(name[0]); //nu trebuie neaparat (*getName)(...)

Initializarea cu 0 ar trebui s-o faci in GetName sa fii sigur ca orice junk primesti, rezultatul va fi corect.

Memoria alocata in array n-o eliberezi niciunde si ai un leak destul de urat.
Daca numarul elementelor e constant, poti sa aloci la inceput, iar apoi doar suprascrii array-ul cand testezi alt algoritm, iar la final eliberezi.

Pentru o precizie mai buna, cronometreaza doar apeleul functiei Sort. Tu acum cronometrezi si citirea de pe disc, care poate incurca rezultatul din cauza cache-ului.

#13
adrian93

adrian93

    Active Member

  • Grup: Members
  • Posts: 1,740
  • Înscris: 29.10.2009
http://pastebin.com/LaPCfcHU

Multumesc pentru review. Am modificat codul tinand cont de observatii.

shellSort - 45.265 sec
quickSort - 14.350 sec


Da, in legatura cu eliberarea memoriei, am un obicei (cam nesanatos presupun) de a nu elibera memoria respectiva atunci cand am nevoie de ea pana la finalul executiei, pentru ca oricum sistemul de operare o sa se ocupe de acest lucru dupa ce procesul se incheie. Dar presupun ca e mai 'clean' sa cuplam fiecare new cu cate un delete (sau un m/calloc cu free) Posted Image .
Acuma pare sa fie ok (am testat pe un fisier mai mic, ca sa nu astept o groaza).

shellSort - 0.101 sec
quickSort - 0.067 sec
==3608==
==3608== HEAP SUMMARY:
==3608==	 in use at exit: 0 bytes in 0 blocks
==3608== total heap usage: 13 allocs, 13 frees, 134,914 bytes allocated
==3608==
==3608== All heap blocks were freed -- no leaks are possible
==3608==
==3608== For counts of detected and suppressed errors, rerun with: -v
==3608== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


Mai am o intrebare cu privire la citirea de pe disc a vectorului, de fiecare data cand schimbam algoritmul: procedam astfel pentru a avea un program relativ eficient si din punct de vedere al memoriei? Eu eram tentat sa fac copie a vectorului si sa restaurez de fiecare data vectorul, folosind copia si astfel sa fac o singura citire de pe disc (presupun ca memcpy-urile sunt mai rapide decat citirile de pe disc). Insa presupun ca ai optat pentru aceasta varianta in cerinta, deoarece daca tineam si o copie, avand in vedere ca lucram cu fisiere maricele, atunci am fi avut cateva sute de MB folositi, care ar reprezenta o bucata semnificativa din RAM?

Edited by adrian93, 16 June 2014 - 11:09.


#14
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,194
  • Înscris: 24.02.2007
Da

Anunturi

Chirurgia cranio-cerebrală minim invazivă Chirurgia cranio-cerebrală minim invazivă

Tehnicile minim invazive impun utilizarea unei tehnologii ultramoderne.

Endoscoapele operatorii de diverse tipuri, microscopul operator dedicat, neuronavigația, neuroelectrofiziologia, tehnicile avansate de anestezie, chirurgia cu pacientul treaz reprezintă armamentarium fără de care neurochirurgia prin "gaura cheii" nu ar fi posibilă. Folosind tehnicile de mai sus, tratăm un spectru larg de patologii cranio-cerebrale.

www.neurohope.ro

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

Forumul Softpedia foloseste "cookies" pentru a imbunatati experienta utilizatorilor Accept
Pentru detalii si optiuni legate de cookies si datele personale, consultati Politica de utilizare cookies si Politica de confidentialitate