Funcțiile tale sunt inevitabile, numele tău este necunoscut
Deci, este găsită decizia cu privire la ceea ce lipsește încă obiectul nostru de server, astfel încât clientul să se poată descurca în mod normal. Au rămas câteva detalii despre proiectarea acestei soluții în proiectarea exactă a programului. În articolul precedent am constatat că avem nevoie de un aparat pentru conversia tipurilor de pointeri în interfețe și de dispozitivul de calcul al referințelor obiectului, cu un "autodistructor" integrat. Acestea pot fi accesate de client doar prin metodele obiectului pe care clientul îl apelează, iar aceste metode trebuie fie să fie în structura fiecărei interfețe implementate de obiect sau sunt decorate cu o interfață specială separată. Și absolut orice obiect, indiferent de fel și trib este - este obligat să realizeze această funcționalitate.
Având în vedere că prezintă aceste caracteristici pot fi doar ca o interfață, și subiectul de discuție: ceea ce este mai bine - să le elibereze sub forma unei singure interfețe, obligația de a expune faptul că la impute orice obiect (inclusiv toate celelalte interfețe obiect), sau - pentru a adăuga această funcționalitate pentru fiecare interfață, obiect expus ?
Stabiliți despre ce vorbim. Prima metodă va fi numită QueryInterface - trebuie să accepte IID a unei alte interfețe expuse de același obiect și să întoarceți un pointer la această interfață. A doua metodă va fi numită AddRef - nu are parametri, iar fiecare apel va duce la avansul numărătorului de referință al obiectului înainte de unul. A treia metodă este Release. Sarcina sa este inversa AddRef. și când numărul de referință atinge zero, Release va de asemenea apela șterge acest lucru.
De ce am venit cu două în locul uneia dintre metodele de gestionare a numărului de referință? Cel puțin pentru că codul pentru a apela o metodă fără parametri este mai scurt. Hai cu câțiva octeți, dar acești câțiva octeți vor fi în client să se întâlnească peste tot unde avem un pointer. Și aditivul total la cod poate fi mare.
Deci, aceste trei metode:
putem emite o interfață separată. Sau - ne putem înregistra în fiecare interfață. Care este mai bine? Și de ce?
Să presupunem că această funcție este afișată într-o interfață complet separată X, iar toate celelalte interfețe ale aceluiași obiect nu o au. Ce se va întâmpla? Și asta e ceea ce se întâmplă - în cazul în care obiectul este creat, vom cere serverului să ne dea înapoi un pointer la X. interfață, apoi în posesia indicelui, putem obține cu ușurință indicii și toate celelalte interfețe obiect - pentru QueryInterface este, de asemenea, o parte din X. interfață Dar dacă avem serverul a cerut să se întoarcă orice altă interfață de același obiect, deci cu această interfață și va rămâne - care este interfața nu este QueryInterface. Acest lucru forțează de fiecare dată când ne cerem interfața dorită, și anume, X., și apoi de la ea au dreptul de a ne face un pointer. Există o dublă lucrare pe partea clientului - când primiți un pointer la interfață.
Dacă această funcționalitate este plasată în interiorul oricărei interfețe care prezintă obiectul, atunci fiecare interfață suplimentară va fi ocupată de trei celule Vtbl suplimentare. dar nu este nevoie să efectuăm nici o lucrare dublă pe partea clientului, toate gestionarea obiectului se poate face prin orice interfață. Și cumva pare că a doua cale este mult mai convenabilă. din partea clientului, desigur - serverul este refolosit.
Dar, de fapt, este posibil să construim și un astfel de obiect la care să nu existe o interfață "utilă"? Poți. Dar această funcționalitate a serviciului ar trebui să fie în orice caz. Și puteți multiplica pointerul, apoi distrugeți obiectul. Și tot ce este dincolo de asta - este determinat numai de esența sarcinii care este rezolvată de programator.
Funcționalitatea descrisă este atât de fundamentală încât fără ea "nimic nu funcționează deloc" - ne-am gândit la asta, deoarece în implementarea noastră a interacțiunii componente nu au fost suficiente fragmente esențiale. Este posibil să o implementăm în mod diferit? În detaliu - da, de fapt - nu. La urma urmei, motivul prezenței acestei funcționalități în compunerea obiectului este filozofic. Dacă compilatorul nostru cunoștea exact tipul static al obiectului și acest tip a fost unul atât pentru client, cât și pentru server, atunci, desigur, compilatorul ar putea implementa atât apelul corect nou, cât și apelul corect de ștergere *. și compilatorul însuși ar putea converti indicii la un tip. Dar, de fapt, aceasta înseamnă că atât clientul și serverul trebuie să fie plasate în cadrul aceluiași proiect - și tocmai am avea exact opuse „condițiile inițiale“. La noi, atât clientul, cât și serverul trebuie să se stabilească în mod diferit în diferite proiecte, în diferite contexte. La noi, la toate, programarea de la componente binare.
Este în vedere acest lucru, am luat mai întâi tabele piesa compilare încorporat în obiectul în sine, astfel încât acestea să fie menținute și run-time (vtbl) (a se vedea. Articolul Introducere în teoria compilării. Și scoate din ea.), Dar acum avem nevoie pentru a încărca obiectul și funcții cum ar fi gestionarea timpului de viață și turnarea de tip. Și nu putem evita acest lucru, fie noi, fie compilatorul.
Amuzant că nu înțelege critica de tip cunoscut „Windows - mastday și COM - suks“ atunci când se referă la faptul că, de „COM - cod suplimentar“, care este în „dreptul de proiect normal“ nr. Da, există un astfel de cod în proiect, dar aici vom include în mod explicit și „cu mâinile lor“, și apoi face compilator, care nu transferă în mod necesar conținutul tabelelor lor în executarea de cod de timp - o parte a unui astfel de cod poate fi introdus pur și simplu „într-un apel loc "și complet transparent pentru programator. COM nu include o mulțime de cod suplimentar, dar la urma urmei, problemele pe care COM rezolvă nu sunt rezolvate de niciun compilator.
Ar trebui remarcat în mod special faptul că, deși studiem COM. ceea ce este formulat acum are un caracter filosofic. Și, prin urmare, într-o formă sau alta pot fi găsite în punerea în aplicare a CORBA și ar trebui să fie, în general, în orice tehnologie componentă binar. "Înfășurarea" acestui lucru poate fi diferită, dar esența este aceeași.
În COM, acest lucru este într-adevăr esența fundamentală se numește „interfața IUnknown“, care vag tradus poate suna ca „interfață necunoscută“ și este de cel puțin oarecum derutat - ceea ce este necunoscut interfață, în cazul în care este garantat de prezența oricărui obiect? Cu toate acestea, dacă aceasta este tradusă ca interfața "Necunoscută necunoscută", totul intră în vigoare. Mai mult decât atât, obiectul component binar este un obiect COM dacă și numai dacă acesta pune în aplicare cel puțin, interfața IUnknown. Dacă nu există o astfel de interfață, acesta nu este un obiect COM. deși, așa cum am văzut în exemplul # 1, obiectul poate fi "component binar" și fără a utiliza COM.
Cu această interfață, trebuie să cunoaștem foarte îndeaproape - este "alfa și omega" a tuturor interfețelor, el determină comportamentul special al obiectului. Și acum, pentru moment, vom nota - orice interfață COM ar trebui să fie moștenită de la interfața IUnknown. Ar trebui să fie clar de ce - este necunoscut în orice interfață care asigură controlul duratei de viață a obiectului și turnarea tipului de indicator. Și pentru aceasta, nu sunt necesare costuri suplimentare pentru clienți.