Hack # 11: obținerea interfeței GUID prin referință la interfață
Randy Magruder ma contactat recent cu un proiect foarte interesant pe care lucrează la: Ce încerc să fac: adăugați și eliminați interfețele suport pentru clasă în timp de execuție și apelați-le. Am făcut destul, pentru a putea folosi modelul OTAServices de la Delphi pentru a trece interfețe suplimentare, pentru a le adăuga în lista internă și a înlocui comportamentul QueryInterface. astfel încât apelul să returneze o interfață din listă, în locul tabelului de obiect standard. Sună ca un proiect rece (și complex)! El continuă: Dar dacă apelantul unui obiect nu cunoaște decât GUID-ul interfeței de care are nevoie și dorește să obțină o interfață adecvată și să numească o metodă în nume propriu, atunci ce pot face în legătură cu aceasta? Folosesc RTTI avansată cu IInvoker. Ei bine, nu sunt sigur cum poate fi rezolvată această problemă. AFAIK, nu există o proiecție simplă de la obiectul care implementează interfața la informații despre tipul acestei interfețe. O soluție ar putea fi implementarea proiectării interfețelor GUID pe informații de tip. Și din nou: nu cunoașteți în mod accidental modul de extragere a interfeței GUID în timpul run-time. Acest lucru ar trebui să fie posibil, deși puțin complicat.
Îți poți aminti codul. pe care am scris pentru a converti referința interfeței la obiectul de implementare. Este posibil să modificați acest cod în altul, care va returna o compensare în obiectul în care este localizată interfața de înregistrare în tabela de interfață a obiectului. Îl conectăm cu apelul TObject.GetInterfaceTable pentru a obține PInterfaceTable. Intrarea TInterfaceEntry a interfeței implementate. Comparând decalajul calculat cu câmpul IOffset. putem găsi un meci și să învățăm GUID din câmpul IID.
De la System.pas. Dar, desigur, acest lucru va funcționa numai pentru interfețele declarate în modul de proiectare.
Pentru a ne atinge scopul de a obține un GUID pentru referința front-end, trebuie să returnați acest offset în locul rezultatului finalizat (o referință de obiect). Să schimbăm funcția de mai sus: După cum puteți vedea, acesta este aproape același cod ca mai sus, dar returnează Integer în loc de TObject. Numele funcției este un pic "gykovato" - PIMT este o abreviere pentru un pointer la tabelul cu metode de interfață. Acesta este un "câmp" special generat de compilator, care este inserat în instanța obiectului când declarați că clasa implementează interfața. Funcția returnează offsetul acestui câmp. Rețineți că compilatorul folosește comanda ADD pentru a corecta parametrul Self - dar, de fapt, adaugă un offset negativ. De aceea există un minus înainte de valoarea returnată din cod.
Acum putem rescrie funcția originală GetImplementingObject în ceea ce privește această nouă subrutină: O funcție minunată - cât de frumos a fost eliminarea codului duplicat. Rețineți că PAnsiChar este singurul tip de date (în vechiul Delphi) care permite aritmetica pointer; îl folosim doar pentru a simplifica scrierea calculelor (notați exercițiul - ce este în neregulă cu tipul PChar în acest context?).
Suntem deja la un pas departe de a obține interfața GUID (sau IID - care este în mod formal numele corect). Acum putem obține un offset PIMT, dar în sine nu este foarte util. Ceea ce o face util este faptul că îl putem folosi pentru a compara cu offseturile stocate ca parte a intrărilor InterfaceEntry. generate de compilator pentru toate interfețele implementate de acesta. Așa cum am menționat mai sus, putem folosi clasa TObject și funcția ei (clasă) GetInterfaceTable pentru a obține un pointer la acest tabel. Cu aceste cunoștințe, putem scrie o funcție care încearcă să găsească intrarea InterfaceEntry. corespunzând referinței front-end: Mai întâi, folosim subrutinele de serviciu de mai sus pentru a obține o referință la obiect și la decalajul PIMT. Apoi vom trece prin clasă și strămoșii ei în căutarea InterfaceEntry. care are același decalaj ca și câmpul PIMT. Când găsim un meci, vom întoarce un indicator la înregistrarea găsită. Această înregistrare conține atât PIMT offset cât și IID. Să scriem o funcție de înfășurare simplă care citește IID: A fost foarte simplu. Deci, acum avem toate funcțiile și tot codul de ajutor - acum trebuie doar să testați că funcționează. Aici este aplicația mea simplă de testare: Acest program declară o interfață cu metoda Foo și clasa care o implementează. Creează o instanță a clasei - atribuindu-i referința interfeței. În primul rând, testăm funcția GetImplementingObject și scoatem numele interfeței care implementează interfața. Apoi apelăm GetInterfaceIID de trei ori și tipăriți GUID-ul rezultat. În primul apel, folosim direct interfața potrivită - dacă codul nostru este corect, ar trebui să funcționeze. În cel de-al doilea apel, trimitem referința interfeței la link-ul IUnknown. În funcție de modul în care compilatorul implementează atribuirea referințelor între interfețe compatibile, acest lucru poate sau nu ar putea să funcționeze. Vom vedea. Între timp, pentru a treia oară, atribuim o nouă instanță a clasei la referința IUnknown. În acest caz, ne așteptăm să obținem (în sensul de ieșire la consolă) GUID IUnknown.
Dacă interfața IUnknown este atribuită prin as. atunci se obțin rezultate diferite: În acest caz, obținem următorul rezultat: Avem IID IUnknown. nu IMyInterface. Motivul este că compilatorul generează as -cast ca acesta: Această versiune a codului utilizează apelul System._IntfCast. pentru a efectua asignarea și conversia - și pentru a vedea codul său care solicită QueryInterface. Pentru a efectua conversia, vom concluziona că acest apel va returna o altă referință de interfață (rețineți câmpul PIMT) - pe care TInterfacedObject a adăugat-o pentru interfața IUnknown (aka IInterface). Desigur, această interfață are propriul IID, iar IDD inițial, pe care Randy a vrut să o primească în întrebarea sa, este pierdut pentru totdeauna.
Șirul lung în hex, care este reprezentarea IID, nu este foarte "de citit de om". Unele interfețe (în special interfețele COM) înregistrează denumirile IID și "citibile de către oameni" în registru. De exemplu, HKEY_LOCAL_MACHINE \ SOFTWARE \ Classes \ Interface \ = IUnknown. Puteți vizualiza înregistrarea interfețelor în registry pentru a obține numele interfeței lizibile de la IID. Această operație rămâne ca un exercițiu pentru cititori;)