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