Zitat:
Das verstehe ich gerade überhaupt nicht. Wieso sind Konstruktoren "automatisch" virtuell? Ich habe mich mal ziemlich weit in die
VMT eingegraben, einen Zeiger auf den aktuell implementierenden Konstruktor habe ich nicht gefunden...
Ok, was unterscheidet primär eine statische Methode von einer virtuellen/dynamsichen Methode ?
Die statische Methode muß nicht überschrieben werden, sie exitiert statisch für alle Klassen der nachfolgenden Hierarchie der abgeleiteten Klassen und es ist immer die gleiche Methode. Dadurch besteht nicht die Notwendigkeit in der Klassenstruktur einen Zeiger zu reservieren der individuell für die nachfolgenden Klassen veränderbar sein muß. Statische Methoden tauchen also in der Klassenstruktur/record im Codesegment garnicht auf.
Der Zeiger auf den Konstruktor einer Klasse findet sich aber in der Klassenstruktur, quasi als "Datenfeld". Somit kann eine abgeleitete Klasse diesen Zeiger auf eine andere Methode umdefinieren. Das ist virtuel und nicht statisch.
Da in den Delphi Klassen ein solcher Slot existiert für den Konstruktor wie auch Destructor sind diese per se virtuelle Methoden, auch wenn wir sie nicht explizit als virtuell deklarieren.
Deklarieren wir eine virtuell Methode in einer Klasse so wird in der dazugehörigen Klassenstruktur in deren
VMT=virtual Method Table am Ende dieser geerbten und vom Vorgänger 1 zu 1 kopierten
VMT diese neue Klassenbezogene
VMT um einen neuen Slot für diese neue virtuelle Methode reserviert. Darin steht der Zeiger auf unsere Methode. Die
VMT einer Klassenhierarchie kann also immer nur additiv arbeiten, logisch da sich der Index einer einmal in einer Vorgängerklasse deklarierten virtuellen Methode in dieser
VMT nie verändern darf. Nur so kann man sicherstellen das innerhalb einer überschriebenen virtuellen Methode das inherited auch funktioniert. Inherited kennt den Index/Offset der eigenen Methoden und schaut nun in der Parent.Class.VMT am gleichen Index nach, lädt den Zeiger der Methode und führt einen CALL dorthin durch. Das geht in der vorherigen überschriebenen Methoden mit inherited immer weiter bist zur Rootklasse die als erste diese virtuelle Methode deklariert hat. Ab diesem Moment ist die
VMT der Parent Classe in ihrer Größe/Anzahl an
VMT Slot kleiner. Dh. diese Vorgänger klasse enthlt diese in der Nachfolgeklasse deklarierte virtuelle Methode nicht in ihrer
VMT.
Das bedeutet:
-
VMT einer Klasse ist ein Array[] of Procedure
- eine Klasse erbt 1 zu 1 als Kopie die
VMT ihrer Vorgängerklasse
- eine Klasse die neue virtuelle Methoden deklariert hängt diese neuen Slots an diese Kopie hinten dran
- damit wird der Speicherverbrauch im Codesegemt für diese Klassenstrukturen immer größer je mehr Klassen und mehr virtuelle Methoden man deklariert
- der Aufrufoverhead für virtuelle Methoden, inklusive des inherited Aufrufs, beschränkt sich auf einen indirekten CALL [Class.VMT.Index]
- die aktive Mitarbeit des Compilers ist von nöten. Man kann also nicht direkt zur Lauzeit die
VMT benutzen um per Index eine virtuelle Methode aufzurufen. Dafür gibt es Compiler Makros VMTOffset() o.ä.
- in der
VMT wird nicht hinterlegt wieviele Slots in der
VMT gespeichert sind. Man kann also nicht direkt über die Klassenstruktur ermitteln wieviele virtuelle Methoden eine Klasse besitzt. Das geht aber über Umwege denoch
Einfach mal in den Borland.Newsgroups nach meinem Posting mit VMTSlotOf() suchen.
Eine Instance einer Klasse enthält als ersten Zeiger in seiner Datenstruktur einen Zeiger direkt in die Klassenstruktur, also in die Mitte derselben. An dieser Addresse steht im Codesegement der
VMT. Danach folgt die DMT = dynamic Method table.
Das hat zwei wichtige Gründe
1.) historisch bedingt. Die alten TObjecte aus Borland Pascal zeiten besaßen eine Klassenstruktur die direkt mit der
VMT began. Damit die alten Objekte zu den neuen Klassen halbwegs kompatibel blieben hat man wohl diesen Weg gewählt.
2.) man kann so sehr schnell ausgehend von einem Instanzzeiger indirekt durch dessen Klassen.VMT eine virtuelle Methode anspringen
Nun die neue Klassenstruktur besitzt aber vor ihrer
VMT, also mit negativem Offset noch weitere Felder. In diesen findest du auch die Einträge für den Konstruktor und Destruktor. Diese sind per se virtuell.
In System.pas findest du diese Konstanten
vmtSelfPtr = -76;
vmtIntfTable = -72;
vmtAutoTable = -68;
vmtInitTable = -64;
vmtTypeInfo = -60;
vmtFieldTable = -56;
vmtMethodTable = -52;
vmtDynamicTable = -48;
vmtClassName = -44;
vmtInstanceSize = -40;
vmtParent = -36;
vmtSafeCallException = -32;
vmtAfterConstruction = -28;
vmtBeforeDestruction = -24;
vmtDispatch = -20;
vmtDefaultHandler = -16;
vmtNewInstance = -12;
vmtFreeInstance = -8;
vmtDestroy = -4;
vmtQueryInterface = 0;
vmtAddRef = 4;
vmtRelease = 8;
vmtCreateObject = 12;
Nun eine Klassenstruktur sieht so aus
Delphi-Quellcode:
Class =
packed record
vmtSelfPtr = -76;
// hm;) ein Sanitycheck, zeigt auf @VMT was gleichbedutend mit dem Klassenzeiger ist
vmtIntfTable = -72;
// Zeiger auf eine Tabelle in der die implementierten Interfaces dieser Klasse stehen
vmtAutoTable = -68;
//
vmtInitTable = -64;
// Zeiger auf Init Tabelle in dieser Struktur, dort steht welche Felder einer Instance autom. initialisert werden müssen und eben auch wie
vmtTypeInfo = -60;
// Zeiger auf die RTTI=Run Time Type Information dieser Klasse
vmtFieldTable = -56;
vmtMethodTable = -52;
vmtDynamicTable = -48;
// Zeiger auf @DMT
vmtClassName = -44;
// Zeiger auf den Klassennamen, folgt der DMT in dieser Struktur
vmtInstanceSize = -40;
// größe einer Instance dieser Klasse im Speicher
vmtParent = -36;
// SelfClass.ClassParent Vorfahrklasse
vmtSafeCallException = -32;
// ab hier alles Zeiger auf Methoden
vmtAfterConstruction = -28;
vmtBeforeDestruction = -24;
vmtDispatch = -20;
vmtDefaultHandler = -16;
vmtNewInstance = -12;
vmtFreeInstance = -8;
vmtDestroy = -4;
// zb. hier der Destruktor einer Klasse
VMT:
array[0..3]
of procedure;
// vmtQueryInterface, vmtAddRef, vmtRelease, vmtCreateObject <- Constructor !!
DMT:
packed record
EntryCount: Word;
Entries:
array[EntryCount]
of packed record
SlotID: Word;
Method:
procedure;
end;
end;
ClassName: ShortString;
InterfaceTable: ....
... blabla
end;
ein Object zeigt nun direkt in diese Struktur an @
VMT, PPointer(Instance)^ = Instance.ClassType = @Instance.ClassType.VMT
Wenn wir also zb. mit Pointer(TComponent)^ in das Codesegement zugreifen so finden wir dort @TComponent.VMT aus obiger Struktur.
Eine Klasse ist also ein Record im Codesegemt desjenigen Modules in das diejenige
Unit eingelinkt wurde in der diese Klasse deklariert wurde. Das erklärt auch warum Packages funktionieren und warum die vielen Beispiele von TForms in
DLL ohne Packages zu benutzen garnicht funktonieren können. Denn zb. der "as" oder "is" Operatior macht nichts anderes als Zeiger auf Klassen im Codesegemtn zu vergleichen.
Nun dieses Pamphlet könnte ich garantiert noch um das 4 fache verlängern, zb. was sind dynamsiche Methoden, was haben sie mit message methoden gemein, wie arbeiten diese, wie ist die Methodtable = publsiched Events und methoden aufgebaut, wie kann man aus dieser Tabelle alle Parameter deren Datentypen usw. ermitteln. Was macht die Interface Table, wir wird ein Interface-Zeiger einer Interface-Instance beim Aufruf einer ihrer Methoden in eine Object-Instance umgeserzt, als wie kommt man von einem Interface-Zeiger zu einem Object-Self-Zeiger damit man eine TObject als Basis für die Implementierung der Interfaces benutzen kann. Was steht so alles in der
RTTI einer Klasse.
Wenn man nun noch die Historie kennt, also zb. seit Borland pascal 5 die Weiterentwicklung gerade dieser Internas mitverfolgen durfte so kann man
1.) sich ein Urteil über die abnehmende Qualität der Entwickler bei Borland in der nähren Vergangenheit erlauben
2.) man kann sehr gut erkennen wie mit jedem neuen Feature -> Delphi-Klassen -> Interfaces -> DispIntfs -> dyn. Message Methoden -> bishin zur
RTTI, nach und nach in dieses Konzept eingebaut wurden.
Ich fands bisher spannend, naja in den letzten Jahren nicht mehr so sehr da sich ja nichts gravierendes mehr getan hat.
Gruß Hagen
PS: sorry für die Tippfehler, im dunkeln auf der Laptoptastur, sollte eure Intelligenz anregen mein Kauderwelsch.