Einzelnen Beitrag anzeigen

Benutzerbild von negaH
negaH

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

Re: Konstruktor Create Virtual oder nicht?

  Alt 29. Aug 2007, 22:26
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.
  Mit Zitat antworten Zitat