BenBE hat folgendes geschrieben : |
Die von dir gefundene Tabelle beschreibt die Offsets in einer Tabelle, auf die die Objekt-Instanz zeigt. Wenn Du also von dem dort eingetragenen Pointer um die angegebene Anzahl Bytes vorwärts gehst, gelangst Du zu einem Eintrag, der die in der Tabelle beschriebene Funktion\Angabe enthält. Das kann je nach Information wieder eine Tabelle sein, oder ein Methoden-Zeiger oder ein Zeiger auf eine anders geartete Struktur.
|
Das ist mir schon klar, dass an dieser Stelle meine Variablen, Pointer, usw drinnen stehen. Das ist aber nicht alles, denn der compiler fügt eben auch bestimmten Code in das Objekt ein, und genau um diesen geht es mir. Was alles wird wann und wohin hinzugefügt, damit Interfaces, virtuelle Methoden usw. funktionieren.
Bin auch schon dahinter gekommen, aber dazu gleich.
Zitat: |
Das gesamt Speicherlayout hab ich auch nicht im Kopf. Bei Interfaces hab ich noch nicht in den Memory-Dump geschaut, da ich das bisher nicht brauchte; da Interfaces aber ähnlich wie Objekte funktionieren, ist deren Speicheraufbau auch ähnlich. Wie gesagt, müsste ich da aber auch erst reinschauen ...
|
Naja, nicht ganz! Du vergleichst hier Interfaces mit Objekten und das sind zwei paar Schuhe! Interfaces haben darstellungsmäßig nichts mit Klassen oder Objekten gemeinsam (Bitte um Korrektur, falls ich falsch liegen sollte). Sie schreiben einer Klasse einfach nur vor, welche Funktionen implementiert sein müssen. Der Zugriff auf diese Implementierungen (wenn er über Interfacevariable erfolgt) unterscheidet sich erheblich von einem direkten Methodenaufruf.
Zitat: |
P.S.: Wozu brauch man sowas eigentlich?
|
hmmm, ich weiss nicht! Es Interessiert mich einfach! Außerdem, wenn ich weiss, wie es funktioniert, kann ich sicher irgendwie draus einen nutzen für meine eigenen Programme ziehen.
So, habe etwas Assemblercode gelesen, und dies ist mein derzeitiger Wissensstand (beschränkt sich derzeit auf Klassen mit Interfaces und virtuellen Methoden. Schon möglich, dass bei Verwendung anderer Konstrukte, sich daran etwas ändert).
Ein erzeugtes Objekt schaut im Speicher (Datensegment) folgendemassen aus:
Quelltext
1: 2: 3: 4: 5: 6: 7:
| // Adresse(bzw Offset) Beispieldaten Funktion
0x000000 0x0043DCA4 Das erste DWord enthält die Anfangsadresse seiner Klasse im Codesegment 0x000004 0x00760000 Der Inhalt der ersten Variable des Objekts 0x000008 0x00760000 Der Inhalt der zweiten Variable des Objekts ..... 0x00000n 0x0043DC1E Ein Pointer auf die VTable des implementierten Interfaces |
Dadurch, dass an erster Stelle eines Objekts die Klassenreferenz steht, lässt sich ganz einfach ein Pointer auf die eigene Klasse abfragen:
Delphi-Quelltext
1:
| tClassVar := TClass( Pointer(MeinObjekt)^ ); |
nichts anderes macht auch die Klassenmethode "TObject.ClassType"
Jetzt mal zum Codesegment. Hier muss ich meinen vorigen Post berichtigen bezüglich der Pointeroffsets (vmtIntfTable,...)! Diese beziehen sich nämlich nicht auf das Objekt, sondern auf die KLASSE!!!
Also liegt eine Klasse wie im oberen beispiel auf der Adresse 0x0043DCA4, so liegt der dazugehörige InterfaceTable-Pointer auf der Adresse
Quelltext
1:
| 0x0043DCA4 - vmtIntfTable = 0x0043DCA4 - 72 = 0x0043DC5C |
Wird jetzt die funktion "TObject.GetInterfaceTable" aufgerufen, so macht sie nichts anderes, als diesen Pointer zurück zu liefern.
Wäre das gleiche wie:
Delphi-Quelltext
1:
| ((PInteger(MeinObjekt)^) - 72)^ |
Ein Eintrag der in der InterfaceTable sieht folgendermassen aus:
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| Entry = record ID: TGUID; VTable: Pointer IOffset: Integer; ImplGetter: Integer; end; |
Greift man jetzt über eine InterfaceVariable auf ein Objekt zu:
Delphi-Quelltext
so zeigt der Pointer nicht auf die anfangsadresse des Objektes, sondern auf die Adresse des Pointers auf die VTable. Im oberen beispiel würde myIFVar auf die Adresse "0x00000n" zeigen!
Dereferenziert man diesen Pointer, bekommt man die Adresse: "0x0043DC1E"!
Diese Adresse ist die gleiche, auf welche auch der Pointer "VTable" in der InterfaceTable zeigt (für das gleiche Interface natürlich):
Delphi-Quelltext
1:
| (MyObjekt.GetInterfaceEntry)^.VTable = PInteger(myIFVar)^ |
Ruft man jetzt eine Funktion über die Interfacevariable auf:
Delphi-Quelltext
so dereferenziert der Compiler einfach myIFVar (bekommt dadurch einen Pointer auf eine Funktionstabelle) und addiert einfach den Offset der funktion dazu:
myIFVar^ + FunktionsOffset = Adresse in der Tabelle!
auf dieser Adresse sollte dann die Adresse der Funktion "Funktion1" stehen.
Es ist nicht ganz die direkte Einsprungsadresse zur Funktion, sondern zu einer kurzen Zwischenberechnung, die ich mir noch nicht genauer angeschaut habe.
Die
virtuellen Methoden dagegen funktionieren wieder etwas anders:
Der Pointer auf die Virtual-Method Table liegt am Offset 0 der Klasse im Codesegment, das heisst wenn wir von einem Objekt ausgehen sieht es folgendermassen aus:
Über
Quelltext
bekommen wir die Adresse der Klasse von Mein Objekt. An dieser Stelle, also an der ersten Stelle einer Klasse im Codesegment steht die Adresse der VMT. Wird diese dereferenziert, so bekommen wir den ersten Eintrag in dieser, also die Adresse der ersten funktion.
(IPointer(IPointer(MeinObjekt)^) + FunktionsOffset)^ ... Adressen der virtuellen Funktionen einer Klasse
So, das reicht für heute! Es ist halt das, was ich durch Debugging und Assemblercode Lesen herausgefunden hab. Ich hab keine Ahnung, wie viel davon Blödsinn ist, also falls ich irgendwo Schwachsinn geschrieben hab, dann schreibts mir bitte bescheid!