Einzelnen Beitrag anzeigen

Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#14

Re: Interface referenzen auf gleichheit prüfen?

  Alt 12. Okt 2004, 14:23
Hi Choose,

so langsam nähern wir uns dem Punkt:

Delphi-Quellcode:
  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]
ist natürlich nicht ganz richtig erklärt

Delphi-Quellcode:
  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
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.

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:
type
  PMyVMT = ^TMyVMT;
  TMyVMT = packed record
    _QueryInterface: Pointer;
    _AddRef: Pointer;
    _Release: Pointer;

    AnOperationIntrocuedByMyInterface: Pointer;
  end;
Eine allozierte Interface-Variable sieht dann minimal so aus:

Delphi-Quellcode:
   PMyIntf = ^TMyIntf;
   TMyIntf = packed record
     VMT: PMyVMT;

     Field1: Integer;
     Field2: Integer;
   end;
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.

Ein Object mit Interfaces sieht im Speicher also so aus:
Delphi-Quellcode:
  
type
  PMyObjectIntf = ^TMyObjectIntf;
  TMyObjectIntf = packed record
    VMT_Class: Pointer;
    Field1: type;
    Field2: type;
   
    VMT_Interface1: Pointer;
    VMT_Interface2: Pointer;
    VMT_Interface3: Pointer;
  end;
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.

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
  Mit Zitat antworten Zitat