![]() |
Interface referenzen auf gleichheit prüfen?
Moin,
ich bin gezwungen (von mir selbst) diverse interface-referenzen (COM) zu vergleichen, was sich als schwer heraustellt, da verschiednene ITypen verschiedene binäre-adressen auf ein und das selbe objekt haben. Stark vereinfachter versuchsaufbau:
Delphi-Quellcode:
das ergebnis wäre zB:
unit Unit1;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type // test typen iA = interface procedure A; end; TA= class(TInterfacedObject,iA) public procedure A; end; iB = interface(iA) procedure B; end; TAB = class(TA,iB) public procedure B; end; {*********************************************} TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; Button2: TButton; Button3: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } fA:iA; fB:iB; fRef:TAB; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin FRef := TAB.Create; // object erzeugen, das einzige fB := fRef; // ref 1 fA := fRef; // ref 2 end; procedure TForm1.Button1Click(Sender: TObject); begin // hier sieht man, dass alle drei referenzen verschiedene addressen haben... memo1.Lines.Add(format('ref = %p | a = %p | b = %p',[pointer(fRef),pointer(fA),pointer(fB)])); // zB so: ref = 008D24E4 | a = 008D24F0 | b = 008D24F4 end; { TA } procedure TA.A; begin ShowMessage('A'); end; { TAB } procedure TAB.B; begin ShowMessage('B'); end; procedure TForm1.Button2Click(Sender: TObject); begin fA.A; end; procedure TForm1.Button3Click(Sender: TObject); begin FB.B; FB.A; end; end.
Code:
und ich sehe auch ein, dass das intern für die COM implementierung irgendwie so sein muss...
ref = 008D24E4 | a = 008D24F0 | b = 008D24F4
...ABER wie vergleicht man diese referenzen so, dass TRUE bei rumm kommt? Danke, in erwartung :-D |
Re: Interface referenzen auf gleichheit prüfen?
Du könntest ein weiteres Interface einführen:
Delphi-Quellcode:
Alle Klassen sollten dann dieses Interface implementieren:
ICompareRef = interface
function GetCookie:Integer; end;
Delphi-Quellcode:
Und so wird's benutzt
TA= class(TInterfacedObject,iA, ICompareRef)
private FCookie : Integer; protected function GetCookie:Integer; public constructor Create; procedure A; end; function TA.GetCookie:Integer; begin Result := FCookie; end; constructor TA.Create; begin // GlobalCookieCounter ist eine globale Variable // sie beim Erzeugen eines Objekte um 1 hochgezählt FCookie := InterlockedIncrement(GlobalCookieCounter); end;
Delphi-Quellcode:
if (fA as ICompareRef).GetCookie=(fB as ICompareRef).GetCookie then
ShowMessage('die Dinger sind gleich'); |
Re: Interface referenzen auf gleichheit prüfen?
Ok, du meinst ich soll allen objekten eine eindeutoge ID geben. Das wäre IMO schon fast ein overkill, da ein objekt ja eigentlich eindeutig ist... Dann könnte ich eigentlich an stelle der ID auch die Object-referenz zurück liefern, denn wenn ich erstmal in dem TObject bin, dann ist self natürlich immer gleich.
Ich dachte eigentlich eher an eine native delphi funktionen oder so? Sowas wie
Delphi-Quellcode:
Das ist bestimmt möglich!?
function isSameIntfObject(const i1,i2:IInterface):boolean;
//edit: equal <> same |
Re: Interface referenzen auf gleichheit prüfen?
Ich vermute mal das du in deinem Konzept einen Denkfehler hast.
Verschiedene Objecte können mehrere verschiedene Interfaces implementieren. Zb.
Delphi-Quellcode:
Nun hast du mehrere allozierte Interfaces obiger Typen und möchtest wissen ob dieses das Interface A verwenden. Das geht indem man GUIDs benutzt, denoch könnten dann "gleiche" Objecte die das IInterface_A implementieren der Klassen TClass_AB und TClass_AC angehören, d.h. der Implementor des Interfaces ist denoch nicht gleich.
TClass_AB = class(TInterfacedObject, IInterface_A, IInterface_B)
end; TClass_AC = class(TInterfacedObject, IInterface_A, IInterface_C) end; TClass_BC = class(TInterfacedObject, IInterface_B, IInterface_C) end; Willst du aber nun überprüfen ob die Implementierenden Klassen identisch sind dann musst du selber eine Schnittstelle dafür programmieren. Dazu könntest du das Delphi Klassen Konzept benutzen, etwa so:
Delphi-Quellcode:
Gruß Hagen
type
IImplementor = interface ['0896943-093893-498574'] // <- deine GUID hier eintragen !! in IDE [STRG+ALT+G] function Object: TObject; end; TClass_AB = class(TInterfacedObject, IImplementor, IInterface_A, IInterface_B) end; TClass_AC = class(TInteefacedObject, IImplementor, IInterfase_A, IInterface_C) end; function TClass_AB.Object: TObject; begin // Result := Self; // <- da EAX schon Self enthält, und EAX das Result ist reicht eine leere Funktion hier aus !! end; function Compare(const A,B: IInterface): Boolean; var AI,BI: IImpelementor; begin Result := (A.QueryInterface(AI, IImplementor) = H_OK) and (B.QueryInterface(BI, IImplementor) = H_OK) and (AI.Object.ClassType = BI.Object.ClassType); end; |
Re: Interface referenzen auf gleichheit prüfen?
Hallo Maximov,
wie Hagen bereits dargestellt hat, handelt es sich bei Schnittstellen (Interfaces) um unterschiedliche "Sichten" auf ein und dasselbe Objekt. Weil die Identität eines Objekts jedoch ein Wesensmerkmal darstellt, scheint es tatsächlich überaschend, dass der von Dir diskutierte Code bei einen Vergleich der Art
Delphi-Quellcode:
den Wert False zurückgibt.
myObj := TMyClass.Create;
myRefToInterfaceA := myObj; myRefToInterfaceBA := myObj; Result := Pointer(myRefToInterfaceA) = Pointer(myRefToInterfaceB); Tatsächlich ist dies lediglich spezifisch für Wahl der Implementierung von Interfaces im COM-Umfeld, bei der jede Klasse für jedes implementierte Interface seine eigene "Methodentabelle" bereitstellt, auf die (indirekt) durch die Referenz auf ein Interface verwiesen wird. Obgleich es möglich ist, Schnittstellen voneinander erben zu lassen und Zuweisungen der Art
Delphi-Quellcode:
durchzuführen, obwohl die Klasse TMyClass aus dem Beispiel das Interface IMyInterface selbst nicht implementiert, ist eine Zuweisung der Art
type
IMyInterface = interface ['{A GUID}'] end; IMyInterfaceDescendant = interface(IMyInterface) ['{Another GUID}'] end; TMyClass = class(TInterfacedObject, IMyInterfaceDescendant) end; var myObject: TMyClass; myRefToInterface: IMyInterface; myRefToInterfaceDescendant: IMyInterfaceDescendant; begin myObject := TMyClass.Create; myRefToInterfaceDescendant := myObject; myRefToInterface := myRefToInterfaceDescendant;
Delphi-Quellcode:
nicht zulässig wie auch der Cast
myRefToInterface := myObject
Delphi-Quellcode:
zu einer Exception führt: Das Interface IMyInterface wird von TMyClass nicht implementiert, es kann keine Referenz auf die "Methodentabelle" zurückgegeben werden.
myObject as IMyInterface
Was Du also machen kannst, wenn Du ohne "Identitätsinterface", wie Hagen es vorschlägt, an die Identität der Objekte zweier unterschiedlicher Interfaces herankommen möchtest, ist der Cast auf auf ein gemeinsames Interface, z.B. IInterface, und der anschließende Vergleich der Art
Delphi-Quellcode:
Achtung: Wie oben beschrieben sind Zuweisungen zu Vorfahren-Interfaces gültig, also im Speziellen Zuweisungen zum Wurzelinterface IInterface. Ein Vergleich nach
Result := (myRefToAnInterface as IInterface) = (myRefToAnotherInterface as IInterface)
Delphi-Quellcode:
kann demnach zu fehlern führen!
var
myHelper1: IInterface; myHelper2: IInterface; begin myHelper1 := ARefToAnInterface; myHelper2 := ARefToAnnotherInterface; Result := myHelper1 = myHelper2; |
Re: Interface referenzen auf gleichheit prüfen?
@Choose:
der Vergleich
Delphi-Quellcode:
ist nicht zwangsläufig durchführbar.
Result := (myRefToAnInterface as IInterface) = (myRefToAnotherInterface as IInterface)
Angenommen:
Delphi-Quellcode:
dann würde obiger Vergleich TRUE liefern auch wenn man ein Object vom Typ TClassA mit TClassB vergleicht, denn beide basieren in ihrer Impelementierung von IInterface_A auf TClassBase. Man hätte also nur die Information das beide Objecte die IInterface_A Schnittstelle auf gleicher Basis impelemntiert haben. Da aber TClass_B zB. die Methoden von IInterface_A intern überschrieben haben können, wären auch untrschiedliche Implementierungen vom IINterface_A möglich. Der Vergleich würde als TRUE zurückliefern obwohl es sich a.) um unterschiedliche Klassen handelt und b.) deren Implementierungen unterschiedlich wären.Result := (myRefToAnInterface as IInterface_A) = (myRefToAnotherInterface as IInterface_A) und type TClassBase = class(TInterfacedObject, IInterface_A) end; TClassA = class(TClassBase, IInterface_B) end; TClassB = class(TClassBase, IInterface_C) end; Somit wäre das Result TRUE logisch gesehen eine Falschaussage. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Hallo Hagen,
nach Deinem Ansatz von oben und der daraus abgeleiteten Implementierung:
Delphi-Quellcode:
ergibt der Vergleich
type
IMyBaseInterface = interface ['{3F3E45E4-3FD4-4326-A2FE-637339B4E8A9}'] end; IMyInterfaceA = interface end; IMyInterfaceB = interface end; TMyBaseClass = class(TInterfacedObject, IMyBaseInterface) end; TMyClassA = class(TMyBaseClass, IMyInterfaceA) end; TMyClassB = class(TMyBaseClass, IMyInterfaceB) end; var myRefToInterfaceA : IMyInterfaceA; myRefToInterfaceB : IMyInterfaceB; myCompareRefA : IMyBaseInterface; myCompareRefB : IMyBaseInterface; begin myRefToInterfaceA := TMyClassA.Create; myRefToInterfaceB := TMyClassB.Create; myCompareRefA := myRefToInterfaceA as IMyBaseInterface; myCompareRefB := myRefToInterfaceB as IMyBaseInterface;
Delphi-Quellcode:
entgegen Deiner Aussage immer False, weil die beiden erzeugten Exemplare (unabhängig davon, ob sie Exemplare unterschiedlicher Klassen, einen gemeinsamen Vorfahren, der besagtes Interface IMyBaseInterface implementiert, besitzen oder derselben Klasse angehören), weil sie voneinandert verschiedene Identitäten haben.
Result := myCompareRefA = myCompareRefB
Innerhalb einer Schnittstellenreferenz ist (indirekt) die "Methodentabelle" der Klasse zur Realisierung des Interfaces und die Identität des Objekts codiert. Wäre dies nicht so und zeigte myCompareRefA für jedes Exemplar der Klasse TMyClassA auf dieselbe Adresse, wäre die Verwendung der Pseudovariablen Self innerhalb von so referenzierten Methoden nicht möglich... Ich hoffe, dass ich Dich richig verstanden und nicht am Thema vorbeigeschrieben habe :gruebel: |
Re: Interface referenzen auf gleichheit prüfen?
Nene, ich meinte das die gemeinsamme Vorfahrklasse das zu vergleichende Interface implementieren. Also:
Delphi-Quellcode:
Beide Klassen, B und C impelementieren auf Grund von Vererbung auch IInterface_A.
TClassBase = class(..., IInterface_A)
TClass_B = class(TClassBase, IInterface_B) TClass_C = class(TClassBase, IInterface_C) Ein Vergleich von Objecten der Klassen B und C auf das Interface IInterface_A würde demnach TRUE ergeben. Ich müsste das aber auch noch erst praktisch überprüfen. Mein Grundgedanke lief aber daraus hinaus das nur die Klasse TClassBase in ihrer RTTI die VMT für IInterface_A im Codesegment speichert. Die Klassen A und B wiederum erben diese VMT indirekt durch die Verebung von TClassBase. Aber egal, wenn man meinen obigen Vorschlag benutzt so ist man definitiv auf der sauberen Seite, denn das muss immer korrekt funktionieren da wir eben nicht auf interne und undokumentirte Implementierungsdetails des Delphi Compilers und dessen RTTI/VMTs aufsetzen. Generell muß man eben wissen das ein Interface nur eine reine Deklaration "wie was" sein sollte, aber eben nicht "wo was" tatsächlich ist, darstellt. Somit kann man bei einer Variable nur überprüfen ob sie ein Interface X unterstützt aber nicht wie, wo und wer sie tatsächlich implementiert. Somit kann man auf Grund des Interface Konzeptes nicht ermitteln ob die Implemntation == Delphi Klasse/Object identische Typen sind. Mit meinem Vorschlag wird aber genau das ermöglicht, ohne das man sich auf undokumentierte Funktinalitäten eines Compilers verlassen muß. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Hey Hagen,
ich glaube, dass wir aneinander vorbeischreiben, vielleicht könnte Maximov an dieser Stelle seine Anfrage richtigstellen. Wenn ich ihn richtig verstanden habe, möchte er feststellen, ob es sich bei zwei Objekten, die sich hinter Interfacereferenzen unterschiedlichen Typs verbergen, um ein und dasselbe Exemplar (und damit implizit derselben Klasse, weil Delphi keine Mehrfahvererbung unterstützt) handelt. Du, Hagen, scheinst jedoch zeigen zu wollen, ob die Klassen zweier Exemplare hinter zweier Interfacereferenzen und nur die Klassen identisch sind, ohne auf die Identität der Exemplare einzugehen. Ohne Interfaces also
Delphi-Quellcode:
bzw.
//prüfen, ob Objekte identisch
Result := AnObject = AnotherObject;
Delphi-Quellcode:
Bitte korrigiere micht jemand, wenn ich falsch liege.
//prüfen, ob Klassen identisch
Result := AnObject.ClassType = AnotherObject.ClassType; |
Re: Interface referenzen auf gleichheit prüfen?
Ja und, schau dir obigen Code mal genaur an ;)
Delphi-Quellcode:
vergleicht die Klassen, und daraus wird
function Compare(const A,B: IInterface): Boolean;
var AI,BI: IImpelementor; begin Result := (A.QueryInterface(AI, IImplementor) = H_OK) and (B.QueryInterface(BI, IImplementor) = H_OK) and (AI.Object.ClassType = BI.Object.ClassType); end;
Delphi-Quellcode:
um die zu implementierenden Objecte zu vergleichen. Bei TRUE wird das Interface in A und B durch das selbe Object implementiert, ergo A und B stellen das selbe Object dar, auch WENN eben Pointer(A) == Pointer(B) FALSE ist. Ich hätte gedacht das nach dem Studium meines obigen Postings das eigentlich trivial ist ;) und jeder das so ableiten kann.
function Compare(const A,B: IInterface): Boolean;
var AI,BI: IImpelementor; begin Result := (A.QueryInterface(AI, IImplementor) = H_OK) and (B.QueryInterface(BI, IImplementor) = H_OK) and (AI.Object = BI.Object); end; Da wir das absichtlich so konstruiert haben muß das funktionioren, es basiert also nicht auf undokumentierten Annahmen wie Delphis Compiler intern was macht. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Es bleibt maximovs Einwand, dass diese Information eigentlich schon implizit vorhanden und gegeben ist durch die Identät des Objekts. Darüber hinaus sehe ich keine triviale Lösung, beschriebenes Konzept zu generalisieren, also auch auf solche Objekte anzuwenden, deren Klassen man nicht selbst implementiert hat.
Außerdem erwarte ich, wie maximov, dass eine Spache selbst in der Lage ist, auf Identität gemäß der Referenzsemantik prüfen zu lassen. Der von mir beschriebene Ansatz, zwei Referenzen auf das identische Objekte "noch gleicher" zu machen, ist kein Trick, funktioniert auf der Grundlage der wohldokumentierten und (für uns Delphianer) unveränderlichen COM-Interface-Implementierung unter Win32, und ist genauso gültig nach dem allgemeinen Konzept von Schnittstellen in der OOP. |
Re: Interface referenzen auf gleichheit prüfen?
Nein, dein Weg funktioniert eben nicht. Der AS Operator ist ein verkapselter Aufruf von A.QueryInterface(var B, GUID: TGUID). D.h. der Code
Delphi-Quellcode:
So erstmal der erste Schritt, nun der zweite:
A := B as IInterfaceType
ist identisch mit if B.QueryInterface(A, IInterfaceType) <> S_OK then raise Exception.Create(...); Eine Impelentierende Klasse kann nun .QueryInterface() überschreiben und dynamisch auf Anforderung ein neues Implemntierendes Object erzeugen das die geforderte Schnittstelle implementiert, also in etwa so:
Delphi-Quellcode:
function TClassA.QueryInterface(var Unk; const GUID: TGUID): HResult; stdcall;
begin Result := S_OK; if GUID = IInterfaceType then IInterface(Unk) := TImplementorClass.Create else Result := inherited QueryInterface(Unk, GUID); end; In diesem Moment erzeugt also die Klasse TClassA ein ganz neues Object das die geforderte Schnittstelle IInterfaceType tatsächlich implementiert. Bei solch einer Implementierung, die sehr oft verwendet wird, kann dein einfacher Vergleich der Variablenpointer nicht mehr funktionieren, denn nun würde ja alles, selbst die Impelemntierende Klasse vollständig von der eigentlichen Klasse gekapselt sein. Also sowas wie:
Delphi-Quellcode:
B ist immer ungleich C da der cast (A as IInterfaceType) -> A.QueryInterface() eben intern bei jeder Abfrage ein eigenes neues Interface erzeugt.
var
A,B,C: IInterface; begin A := TClassA.Create; B := A as IInterfaceType; C := A as IInterfaceType; Assert( B <> C ); end; Zitat:
Der Delphi Compiler legt im Codesegement die VMT des Interfaces als Konstante an. Die einzelnen Methoden dieses Interfaces zeigen eber nicht direkt auf die Methoden der implementierenden Klasse sondern auf Dispatcher Methoden die durch den Compiler erzeugt wurden. Für jede Methode im Interface gibt es eine eigene Dispatcher Funktion. Diese Dispatcherfunktion berechnet nun aus dem übergebenen Interface Zeiger per Offset den Self Zeiger des Objectes. Dieser Offset ist für jede Interface implementierende Klasse und für jedes einzelene Interface dieser Klasse selber unterschiedlich. Nun, auch wenn man dies weis und als Ausgangsbasis für eine Identität zum direkten Vergleich von Interfacezegern heranziehen könnte, so ist diese Art und Weise der Impelemntierung von Interfaces eben Delphi typisch und zudem auch undokumentiert. Nein, das was ich oben sagte stimmt auch weiterhin. Maximov sollte überlegen ob sein Konzept richtig ist, und ob er nicht zuviel von den Interfaces abverlangt. Mein oben vorgeschlagener Weg ist eine saubere Lösung, und für meine Begriffe die einzigst saubere überhaupt. Denn selbst wenn Delphi die Art und Weise wie es Interfaces mit Klassen/Objecten verbindet ändert, oder sogar wenn man auf diese Weise Interfaces die durch verschiedene Compiler erzeugt wurden, vergleicht, so funktioniert das immer sauber. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Hallo Hagen,
Zitat:
Der von Dir dargestellte Code
Delphi-Quellcode:
funktioniert bei mir mir in den Delphi Versionen 5, 6 und 7 so, wie von mir beschrieben: Die Zusicherung schlägt fehl, oder mit anderen Worten: Es gilt
var
A,B,C: IInterface; begin A := TClassA.Create; B := A as IInterfaceType; C := A as IInterfaceType; Assert( B <> C ); end;
Delphi-Quellcode:
Und das "Konzept" der Interfaces entspricht im Gedanken denen des "Typen" (vgl Booch), also "Sichten" auf Objekte. Sie abstrahieren vom Verhalten einer Klasse und führen lediglich das Protokoll (in Form der Signaturen der Nachrichten), mit dessen Hilfe der Klient mit Ihnen interagieren kann, ein. Trotz dieser Sicht verbirgt sich hinter einer solchen Interfacereferenz also ein (Proxy-)Objekt und hat somit eine Identität inne.
B = C;
Objekte könnten also durch Stellvetreter (Proxies) dynamisch bei jeder Zuweisung generiert werden und ganze Programmkonzepte (zB dynamische Proxies unter Java oder div Persistenz-Frameworks unter Smalltalk arbeiten nach diesem Prinzip) beruhen auf diesem Ansatz. Der Delphi-Compiler allerdings bedient sich jedoch eines anderen Ansatzes, der einen Mittelweg zwischen Performance und Speicherbedarf beschreibt. Im Folgenden möchte ich Zeigen, dass die Interfacereferenz nicht nur im allgemeinenen sondern auch bei der Delphi-Implementierung nach dm COM-Interface-Paradigma ein direkter Zusammenhang zur Objektidentität existiert. Während bei einem virtuellen Aufruf eines Objekts einer Klasse der Compiler die Basisklasse eines Objekts bereits zur Übersetzungszeit kennt und er die Klasse eines Objekts zur Laufzeit ermitteln kann, hat er die Möglichkeit, in der VMT der Klasse nach der durch die Basisklasse angeführten Stelle die Referenz auf die Methode allein durch die indirekte Kenntnis der Objektklasse durch die Referenz auf das Objekt zu erlangen. Bei einer Referenz auf ein Interface wurde wieder dieser weg Beschritten, weil es hier keine Basisklasse gibt, auch wurde kein Slot-Mechnismus wie bei dynamischen Methoden oder Message Methoden verwendet:
Code:
Weil zwei Interfaces voneinander erben können, die Implementierung einer Methode, die durch das "Oberinterface" jedoch gänzlich von der Implementierung des "Unterinterface" verschieden sein kann, ist der direkte Abgriff an der Stelle, die durch diesen "Vorfahren" eingeführt wurde, nicht möglich, so dass der Ansatz von Delphi etwas anders aussieht (ich beschreibe den letzten Teil des Castes, der, wie bereits von Dir beschrieben auf der Methode QueryInterface beruht, die ihrerseits GetInterface verwendet):
Jede Klasse besitzt eine "Methodentabelle" pro Interface, dass sie implementiert.
Delphi-Quellcode:
Die Methode GetInterface hat die Aufgabe, in den Parameter Out eine Interfacereferenz auf die durch IID beschriebene Schnittstelle zum betrachteten Objekt zurückzugeben, sofern vorhanden. Der Bool'sche Rückgabewert signalisiert den Erfolg der Operation.
function TObject.GetInterface(const IID: TGUID; out Obj): Boolean;
var InterfaceEntry: PInterfaceEntry; begin Pointer(Obj) := nil; InterfaceEntry := GetInterfaceEntry(IID); if InterfaceEntry <> nil then begin if InterfaceEntry^.IOffset <> 0 then begin Pointer(Obj) := Pointer(Integer(Self) + InterfaceEntry^.IOffset); if Pointer(Obj) <> nil then IInterface(Obj)._AddRef; end else IInterface(Obj) := InvokeImplGetter(Self, InterfaceEntry^.ImplGetter); end; Result := Pointer(Obj) <> nil; end;
Delphi-Quellcode:
Dieser Aufruf der Klassenmethode GetInterfaceEntry ermittelt (indirekt) die bereits zuvor beschriebene VMT der Klasse zum Interface. Der Rückgabewert des Aufrufs ist für alle Exemplare der Klasse identisch und nil, falls die Klasse das Interface nicht implementiert. Betrachtet man den Aufbau der Struktur, auf die eine Referenz zurückgegeben wird, etwas genauer
InterfaceEntry := GetInterfaceEntry(IID);
Delphi-Quellcode:
fällt auf, dass sie eine Referenz auf die tatsächliche VMT enthält (VTable) und die GUID des Interface enthält, letztere ist für die Methoden GetInterfaceEntry erforderlich.
type
TInterfaceEntry = packed record IID: TGUID; VTable: Pointer; IOffset: Integer; ImplGetter: Integer; end; Interessant für die weitere Betrachtung ist das Feld IOffset. Betrachtet man die Implementierung von GetInterface weiter
Delphi-Quellcode:
so erkennt man, dass dieser Offset einfach zu dem Speicherbereichsbeginn des Objekts selbst addiert wird. Der so ermittelte Wert ist (im Regelfall) dann die Interfacereferenz.
if InterfaceEntry^.IOffset <> 0 then
begin Pointer(Obj) := Pointer(Integer(Self) + InterfaceEntry^.IOffset); if Pointer(Obj) <> nil then IInterface(Obj)._AddRef; end Wie also zu erkennen ist, muss der Wert von GetInterface bei jedem Aufruf an dasselbe Objekt den identischen Rückgabewert haben, weil IOffset von der zu einem (Delphi-)Objekt unveränderlichen Klasse und der andere Summand von dem (für Delphiobjekte unveränderlichen) Speicherort abhängt (der bei Delphiobjekte der Identität entspricht). Um zu klären, wie der Compiler Code zum Aufruf einer Methode erzeugen kann, ohne Kenntnis über die Klasse eines Objekts, zu dem eine Interfacereferenz bekannt ist, zu haben, verfolgt man im Debugger, was an der Stelle Verfolgt man im Debugger, was an der Stelle
Delphi-Quellcode:
zu finden ist.
Pointer(Integer(Self) + InterfaceEntry^.IOffset)
Entdecken kann man bei einem Aufruf der Art
Delphi-Quellcode:
eine Technik, die auch bei diversen anderen Stellen durch den Delphi-Compiler erzeugt wird:
MyInterfaceRef.AnOperationIntrocuedByMyInterface;
Code:
; MyInterfaceRef = InterfaceReferenz = Objektrefernz+IOffset
; Hier: IOffset = 16d = $10 mov eax, [MyInterfaceRef] ; eax => "magischer Zeiger" mit Objektreferenz+IOffset (s.o.) mov edx, [eax] ; edx => "magischer Bereich" call edx, [edx+$0c] ; Sprung nach [edx+Methodenselektor]
Code:
Nach diesen Zeilen also wird die Methode AnOperationIntroducedByMyInterface aufgerufen und in eax befindet sich, gemäß der Delphi-Aufruf-Konvention nach Registern, die Referenz auf das Objekt, um Zugriffe auf die Pseudovariable Self zu ermöglichen.
; "magische Tabelle" hinter der Klasse
; mit IOffset = 16d = $10 add eax, -$10 ; eax = "magischer Zeiger"-IOffset = Objektreferenz jmp TMyClass.AnOperationIntroducedByMyInterface Verfolgen wir den Verlauf von eax fällt auf, dass es zunächst mit der Interfacereferenz (=Objektreferenz+IOffset, siehe oben) beladen wurde und anschließend, in einer "magischen Sprungtabelle" durch harten Quelltext um den für die Klasse konstanten Wert von IOffset dekrementiert wird, um -wieder hart im Code pro Klasse- zur entsprechenden Methode zu springen. Die tatsächliche Unterscheidung, welche Implementierung verwendet werden soll, wird also innerhalb des "magischen Bereichs" innerhalb des Speicherbereichs des Objekts getroffen: Hier wird pro Objekt eine Referenz auf den "Beginn" der "magischen Sprungtabelle", deren Struktur für alle Klassen identisch ist, abgelegt; Ein Zeiger pro Objekt. Wir haben es also mit einer doppelten Indirektion zu tun, die dynamisch aufgelöst werden kann und deren veriablen Anteile die InterfaceReferenz, einem Zeiger auf die Objektreferenz plus einem für die Klasse-Interface-Kombination konstanten Offset IOffset sowie einem bei der Anlage eines Objekts erzeugten Verweis an dieser Stelle auf die "Methodentabelle" der Klasse für ein konkretes Interface, die, im Wissen um IOffset (weil konstant und zur Übersetzungszeit bekannt), die Objektreferenz rekonstruieren kann. Ich hoffe, ich konnte zeigen, dass es keinen Grund für die Trennung zwischen der Identität eines Objekts und der eines Interface gibt und dass bei Delphi 5, 6 und 7 (ich habe die früheren Compiler leider nicht zur Hand) zumindest eine direkte Abhängigkeit zwischen den Objekt- und Interfacereferenzen bestehen, die unter Delphi der Identität entspricht. Diese Erkenntnis:
Code:
kann nun mit dem Wissen, dass TInterfacedObject das Interface IInterface implementiert, verwendet werden, um von der Identität der Interfacereferenzen IRef1 und IRef2 eben dieses Typs auf die Objektidentität zu schließen, gemäß der Folgerung
In Delphi 5, 6 und 7 entspricht die Interfacereferenz [i]IRef[/i] des Interfacetyps [i]ITyp[/i] immer der Objektreferenz [i]ORef[/i] plus einem konstanten Wert [i]ClassTyp.IOffset(ITyp)[/i] seiner Klasse [i]ClassTyp[/i] in Abhängigkeit des betrachteten Interfacetyps.
Es gilt für beliebige aber feste [i]ORef[/i], [i]ITyp[/i] mit [i]ClassTyp = ORef.ClassTyp[/i] daher ORef.ClassTyp.IOffset(ITyp) = const und somit IRef = ORef + ORef.ClassTyp.IOffset(ITyp) = const
Code:
und der aus Zeitgründen nicht hinreichend gezeigten Beschaffenheit von IOffset (es gilt: Für alle gültigen IOffset, ORef : IOffset<=ORef.InstanceSizePlusMagic, so dass insbesondere die Kombination niemals "in" den Speicherbereich eines von ORef verschiedenen Objekts zeigt) folglich gilt:
ORef1 = ORef2 => IRef1 = IRef2
Code:
(man verzeihe mir bitte, diese etwas "pragmatische Darstellung").
ORef1 = ORef2 <= IRef1 = IRef2
Der Test nach
Delphi-Quellcode:
ist somit auch unter Delphi möglich.
Result := (AnInterfaceRefOfAnObjectThatImplementsIInterface as IInterface)
= (AnotherInterfaceRefOfAnObjectThatAlsoImplementsIInterface as IInterface); Interessant ist nun noch die Betrachtung von TInterfaceEntry.ImplGetter aber ich glaube, dass dies nach diesem trockenen Stoff niemanden mehr interessiert ;) |
Re: Interface referenzen auf gleichheit prüfen?
Hi Choose,
so langsam nähern wir uns dem Punkt:
Delphi-Quellcode:
ist natürlich nicht ganz richtig erklärt ;)
mov eax, [MyInterfaceRef] ; eax => "magischer Zeiger" mit Objektreferenz+IOffset (s.o.)
mov edx, [eax] ; edx => "magischer Bereich" call edx, [edx+$0c] ; Sprung nach [edx+Methodenselektor]
Delphi-Quellcode:
Allerdings stehtin der VMT eben NICHT der direkte Aufruf von .AnOperationIntrocuedByMyInterface drinnen sondern eine Addresse zu einem durch den Compiler erzeugten Disptacher. Dieser Dispatcher "subtrahiert" von der aktuelle Intrface Referenez in EAX = Self.Interface den IOffset und springt danach zu .AnOperationIntrocuedByMyInterface.
mov eax, [MyInterfaceRef] ; lade eax mit Interface aus Variable MyInterfaceRef
mov edx, [eax] ; lade edx mit VMT des Interfaces call edx, [edx+$0c] ; spinge indirect an die Addresse die in der Interface VMT an Index 3 steht, eg. rufe .AnOperationIntrocuedByMyInterface Dispatcher auf Nun dieser Mechanismus ist natürlich in Delphi 5,5,7 gleich geblieben, und stellt auch tatsächlich einen direkten Zusammenhang zu den implementirenden Klassen dar. Das ist alles richtig. ABER, diese Funktionalität ist eben undokumentiert und absolut Delphi typisch. Normale Interfaces in anderen Sprachen arbeiten absolut nicht so. Soll heisen: man kann und darf sich eigentlich darauf nicht verlassen. Desweiteren kann man sehr wohl dieses Verhalten zur Laufzeit dynamisch verändern. Man kann nämlich den VMT-Zeiger auf die VMT des Interfaces im "Datenbereich" des Objectes dynamisch verbiegen. Dieser VMT-Zeiger auf das Interface liegt so wie der VMT Zeiger auf die Klasse innerhalb des Datenbereiches des Objectes. Zb. Pointer(Self^) zeigt auf die VMT der Klasse. Und Pointer(Self + IAnyInterface.IOffset)^ zeigt auf die VMT des Interfaces. Eine VMT eines Interfaces ist sehr simpel. Sie besteht immer aus mindestens 3 Zeigern auf die proceduren ._QueryInterface(), ._AddRef, ._Release. Danach kommen die durch das Interface zusätzlich deklarierten Methoden. Also so
Delphi-Quellcode:
Eine allozierte Interface-Variable sieht dann minimal so aus:
type
PMyVMT = ^TMyVMT; TMyVMT = packed record _QueryInterface: Pointer; _AddRef: Pointer; _Release: Pointer; AnOperationIntrocuedByMyInterface: Pointer; end;
Delphi-Quellcode:
Exakt so sehen auch Objecte aus, und das ist ein Problem für die Entwickler bei Borland, denn nun müssen sie beide VMT's, die der Objecte und die der Interfaces in ein Object reinbekommen. Der Trick besteht nun darin das ein Object im Speicher aus einer "Kette" von solchen VMTs besteht. Als erste, ausgehende vom Pointer Self kommt das ursprüngliche Object mit seinem VMT zeiger zur Klasse und danach die einzelnen VMTs der verschiedenen impelemntierten Interfaces. Normalerweise sind diese nur die Zeiger auf die VMTs der Interfaces, also ohne zusätzliche Datenfelder.
PMyIntf = ^TMyIntf;
TMyIntf = packed record VMT: PMyVMT; Field1: Integer; Field2: Integer; end; Ein Object mit Interfaces sieht im Speicher also so aus:
Delphi-Quellcode:
Nun erklärt sich auch .IOffset, denn eine Interface Variable die auf ein Klassen implementiertes Interface in Delphi zeigt, zeigt im Grund mitten in den Speicherbereich des Objectes selber, also exakt an Addresse Self + IInterface1.IOffset.type PMyObjectIntf = ^TMyObjectIntf; TMyObjectIntf = packed record VMT_Class: Pointer; Field1: type; Field2: type; VMT_Interface1: Pointer; VMT_Interface2: Pointer; VMT_Interface3: Pointer; end; Aber exakt das wird bei Interfaces anderer Programmiersprachen nicht so sein, und es stellt noch keinen Bezug auf die Klasse eines Objectes dar und es stellt auch NICHT sicher das die Reichenfolge und die .IOffsets bei ausschließlicher Kenntnis der Interface-Varibale von aussen berechnet werden können. Soll heisen, nur das implementierende Object selber hatt Zugriff auf seine Klassen-RTTI und kann die .IOffsets errechnen. Über einen normaler Interface-Zeiger geht dies nicht da die .IOffsets abhängig von der Klasse unterschiedlich sein können eben auch wenn verschiedene Klassen das gleiche Intrface implementieren. Zwei Klassen, A und B implementieren das Interface C. Die VMT von C liegt aber in der Klasse A an einem ganz anderen IOffset als in Klasse B. Somit hat man eben keine Möglichkeit, von Aussen nur mit Hilfe einer Interface Variablen auf Self -> Self.ClassType -> Self.RTTI zu berechnen. In jedem Falle benötigt man dazu ein spezielles Interface das dann wie in meinem obigen Source den Interface-Zeiger umrechnet in einen Objectzeiger. Diese "Umrechnung" wird eben im Gegensatz zu anderen Programmiersprachen, durch den Delphi Compiler über die hardcoded erzeugten Dispatcher Funktionen erledigt. WENN, man aber nun ein zusätzliches Interface zwingend benötigt, so kann man auch gleich den sauberen Weg wie oben angedeutet beschreiten. Auf alle Fälle gilt: Da es zwischen einer Interface-Referenz keinen zwingenden Zusammenhang zum implementierenden Object gibt, kann man auch nicht Interface-Referenzen direkt in Object-referenzen umrechnen. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Jungs, ihr seid gold wert :-D
Da hab ich ja genau die richtigen erwischt, um diese oberflächlich simple frage zu diskutieren. Tut mir leid, dass ich erst jetzt von mir hören lassen. Ihr wisst ja wie das ist, wenn man viel zu tun hat ( :zwinker: ). Da ihr ja hier ganze romane geschrieben habt, kann ich leider nicht auf alles eingehen, freue mich aber über die tiefe eurer diskussion. Finde man sollte solche diskusionen in eine gesonderte sparte verschieben, damit sie nicht zwischen dan ganzen 'wieso geht diese komponente nicht so wie ich will'-themen untergeht :-D Zum thema: Ich bin dabei ein spezielles modell zu entwickeln, das nach aussen (für die view schicht) nur aus interfaces besteht. Dabei ist es notwendig, dass ich auch eines dieser interfaces zur identifikation nutzen kann. ZB. um über ein assozioativen array, andere objekte addressieren zu können. Um jetzt das gesuchte object zu finden, muss ich besprochene identität vergleichen können, was zunächst, überraschenderweisen, fehl schlug... Zitat:
Zitat:
Bei dieser möglichkeit, die ihr ja teilweise für problematisch haltet, werden im hintergrund nun einige sachen gemacht, unter anderem vermutlich auch _Addreff, _Release und QueryInterface. Da stellt sich mir natürlich die frage, ob die nutzung eines identitäts-interfaces nicht grundsätzlich schneller wäre? zumal auch komzeptionell sauberer. Vielen dank vorerst! :cheers: |
Re: Interface referenzen auf gleichheit prüfen?
Es wäre um die beiden Methoden Aufrufe .Object langsammer. Auch über das gemeinsamme Identität-Interface muß man ja vorher mit .QueryInterface() -> ala "as" das Identitäts-Interface anfordern. Das sollte aber im Grunde vernachlässigenbar sein. Soll heisen: der minimale Performanceunterschied zu ungunsten meiner Lösung sollte dich nicht weiter stören da die Methode einfach sauberer und besser wartbar ist für die Zukunft. Im Grunde fallen ca. 16 zusätzliche Assemblerbefehle an.
Vorrausgesetzt man benutz .QueryInterface() statt den "as" Operator. Der "as" Operator macht wesentlich mehr als .QueryIntrface(). Bei deinem Problem wäre der "as" Operator eh die schlechteste Wahl das dieser eben auch eine Exception auslösen wird wenn das Object das angeforderte Interface nicht enthält. Du kannst aber in deinem Falle noch mehr an Performance rausholen. Deine Arrays[] auf die Interfaces sollten sortiert sein, und zwar binär nach .Object: TObject, so als wären es Cardinals. Wenn du nun in diesem Array[] nachschlagen willst ob ein Interface==Object schon vorhanden ist dann kannst du die Binäre Suche benutzen. Bei 1024 Einträgen im Array[] rufst du also maximal 12 mal die Methode .Object auf und weist danach a.) ob das Obhject/Interface schon im Array[] ist, und oder b.) an welcher Stelle im Array[] du es einfügen müsstest. Somit wird die Frage wie schnell der Aufruf der Methode .Object: TObject ist, stark relativiert, weil du bessere Algorithmen benutzt. Falls du aber diese Arrays[] sehr häufig aktualisieren musst, sprich es kommt sehr häufig vor das du neue Interface/Objecte dort einfügen und entfernen musst, dann sollte man dafür eine verlinkte Liste benutzen. D.h. statt sich um 16 Taktzyklen mehr oder eniger Gedanken zu machen, solltest du die übergeordneten Algorithmen optimieren. Das bringt bei weitem mehr. Beispiel, array mit 1024 Elementen, Aufrufe von .QueryInterface() 100 Taktzyklen, Aufruf von .Object +50 Taktzyklen - binäre Suche -> 12 Aufrufe von .Object, macht 12 * 150 = 1800 Taktzyklen - normale sequentiell Suche -> 513 Aufrufe von .Object durchschnitliche, macht 513 * 100 = 51300 Taktzyklen. Wie man sieht selbst mit 50 Taktzyklen mehr bei der Benutzung von .Object + .QueryInterface ist das bei dem richtigen übergeordeneten und optimierten Algorithmus bei weitem effizienter als ein schlechter Algorithmus aber dafür nur .QueryInterface() Fazit: halte den Code sauber und idiotensicher, optimiere besser andere Bereiche, wie zb. deine Ararys[], Listen etc. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Hallo Maximov,
wenn ich den Ursprung Deines Problems, das Verwenden einer Interfacereferenz als Schlüssel in einem Assoziativen Array (Dictionary) zu verwenden, richtig verstehe, könntest Du aus Performancegrunden die dafür gängige Variante des ![]() Weil Du zu diesem Zweck einen Hashwert aus einem eineindeutigen Schlüssel (hier: unter Zuhilfenahme der Interfacereferenz), bilden, Du die Schlüssel letztlich vergleichen musst und in Deinem Dictionary letztlich nur solche Schlüssel zugelassen werden können, die diese Eigenschaft aufweisen, bietet sich Hagens Ansatz, ein zusätzliches Interface einzuführen, geradezu an! Weil Schlüssel jedoch prinzipiell nicht unbedingt "gehasht" werden können müssen, könnte man die Interfaces für Dictionary und Hashtables selbst wieder voneinander erben lassen, etwa in der Art
Delphi-Quellcode:
Ich hoffe, die ADTs
type
IIdentifiable = interface function GetIdentity: Cardinal; function IsSameTo(const AnIdentifiable: IIdentifiable): Boolean; end; IHashable = interface(IIdentifiable) function GetHashValue: Cardinal; function IsSameHashValueAs(const AHashable: IHashable): Boolean; end;
Delphi-Quellcode:
veranschaulichen, was ich meine.
type
TDictionary = class procedure Add(const AKey: IIdentifiable; const AnObject: TObject); procedure Remove(const AKey: IIdentifiable); function Has(const AKey: IIdentifiable): Boolean; function Get(const AKey: IIdentifiable): TObject; //.. end; THashtable = class procedure Add(const AnObject: IHashable); procedure Remove(const AnObject: IHashable); function Has(const AnObject: IHashable): Boolean; //.. end; |
Re: Interface referenzen auf gleichheit prüfen?
Hashtables wären schon sinnvoll, wenn man nicht, so wie in meinem fall, von weniger als 64 elementen ausgeht. In den meisten fällen wohl eher < 16. Deshalb hab ich das jetzt schon in einer verketteten liste angeordnet, was wohl bei den stückzahlen am sinnvolsten sein wird.
Jetzt ist es auch so, dass der interface-type fest steht. Ich muss also nicht gegen beliebige interfaces prüfen. In meinem speziellen falle wäre also höchstens ein downcast nötig, womit der as-operator eigentlich zu viel des guten ist. Dh. ich könnte die identitäts-info auch in dieses interface aufnehmen und hätte somit die aller schnellste lösung, da ich lediglich zwei methoden aufrufen müsste. Ich danke euch :thumb: Daumen hoch. |
Re: Interface referenzen auf gleichheit prüfen?
16 Einträge, ist ja lächerlich ;)
Nimm ein dynamisches Array of IInterface, und suche darin linear, fertig. Hashtabellen, verlinkte Liste, sogar sortierte Arrays sind in diesem Falle zu große Kaliber und bringen keine wesentliche Performancesteigerung, benötigen aber garantiert mehr an Speicher als ein simples dynamisches Array. Bedenke, ca. 4 Taktzyklen pro Assemblerbefehl auf einem 4 GHz Rechner sind 1.000.000.000 in Worten Eine Milliarde Assembleroperationen pro Sekunde. Da fallen die 16 Vergleichsoperationen nun wirklich nicht ins Gewicht. Später, wenn dein Source/Programm all das komplizierte was du geplant hast auch richtig und zur vollsten Zufriedenheit erledigt, kannste dir immer noch überlegen ob du für die 16 Einträge in den Arrays bessere Algorithmen findest. Aber, ich vermute du wirst dann keinen Unteerschied in der Gesamtperformance erkennen können. Gruß Hagen PS: Ausnahme wäre eine verlinkte Liste der Interfaces untereinander. Diese verbrauchen im Vergleich zu einem Dynamischen Array ca. 12 Bytes weniger. Allerdings nur dann wenn es nicht zu viele Interfaces gibt die nicht verlinkt wurden. Denn diese Interfaces haben ja dann NIL Link auf das nächste Interfaces, verbrauchen also zusälich Speicher. |
Re: Interface referenzen auf gleichheit prüfen?
Ja, kann sein, das dies lächerlich ist. Aber von den listen wird es recht viele geben, dh. jedes kompositions-objekt hat eine solche liste, die beim traversieren gefunden werden muss und dann einen iterator für die eigentlichen kind-objekte liefert. Das ist jetzt aber auch egal, da es sich nur um eine machbarkeits-studie handelt und alle algorythmen und strukturen alterniert werden können, ohne das gesamtsystem zu beeinflussen :-D dh. es wäre ein leichtes das ggf. auf dynArray, TList etc. umzubauen. Momentan hab ich das so implementiert, was auch funktioniert...
...aber gut, dass du dir sorgen machst :mrgreen: |
Re: Interface referenzen auf gleichheit prüfen?
Hey Maximov,
wenn es sich Zitat:
Vielleicht hast Du ja die Möglichkeit, Deine Ergebnisse ja in einem neuen Thread zur Diskussion stellen... |
Re: Interface referenzen auf gleichheit prüfen?
sehe ich auch so. In deinem Falle würde ich eine eigene TList Klasse erzeugen, oder ein Interface. Jedes Parent Object enthält eine solche TList/Interface in dem es seine Childrens verwaltet. Später kann man zu jeder Zeit die interne Implementierung dieser TList abändern ohne das das direkte Konzequenzen auf den Rest der Objecte hätte.
Allerings musst du auf eines wirklich exakt achten: Werden in dieser TList Interfaces gerspeichert so musst du auf das Referenececounting acht geben. Es kann sehr leicht vorkommen das sich dann überkreuzende Deadlocks der Interfaces bilden. D.h. das Interface A wird als Child in B eingefügt und A.RefCounter +1, zudem wird Interface B als Child in A eingebunden -> B.RefCounter +1. Passiert dies nun mehrmals über viel solcher verlinkter Interfaces kann es sehr häufig vokommen das duch die überkreuzende Bezüge die Interfaces== Objecte nicht mehr sauber freigegeben werden können. Gruß Hagen |
Re: Interface referenzen auf gleichheit prüfen?
Moin Choose,
vielleicht hast du recht (wie meisstens). Ich muss aber natürlich erstmal sehen ob ich das erreichen kann, was ich erreichen will. Oder ob mein konzept zu überdimensioniert und unpraktisch ist. Dieses spezielle problem war also nur ein kleines teil, eines grösseren teils, eines grösseren ganzen, welches nur eine schicht in einer architektur ist...oder so. Da ist aber noch nix in stein gemeisstelt. Ich denke zum gegebenen zeitpunkt nochmal drüber nach , dass hier zu diskutieren. Bis bald :wink: |
Re: Interface referenzen auf gleichheit prüfen?
Zitat:
...:cat:... |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:18 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz