Während wir DELPHI zur Entwicklung von Software verwenden, sind wir wie eine Gruppe glücklicher Kühe und Schafe auf dem Grasland und genießen unbeschwert den Sonnenschein, den uns die Object Pascal-Sprache und die reichen Wasserpflanzen bieten, die uns verschiedene VCL-Steuerungen bieten. Wer würde beim Blick nach oben in den grenzenlosen blauen Himmel und beim Blick nach unten auf das üppige grüne Gras auf der Erde darüber nachdenken, wie groß das Universum ist und welche Dinge kleiner als Moleküle und Atome sind? Das ist Sache der Philosophen. Zu dieser Zeit saß der Philosoph auf dem Gipfel eines hohen Berges, blickte zu den Veränderungen in den Nebeln des Universums hinauf, starrte auf die kriechenden Insekten auf dem Boden, drehte sich plötzlich um, nickte und lächelte unserer Gruppe Weidender zu Rinder und Schafe. Er nahm ein Stück Gras, hielt es sanft in den Mund, schloss die Augen und probierte es vorsichtig. Ich frage mich, wie dieses Stück Gras im Mund des Philosophen schmeckte. Allerdings hatte er stets ein zufriedenes Lächeln im Gesicht.
Wenn wir die mikroskopische atomare Welt von DELPHI kennen und verstehen, können wir die makroskopische Anwendungsstruktur von DELPHI gründlich verstehen und so unsere Software in einem breiteren ideologischen Raum entwickeln. Es ist, als hätte Newton die Bewegung makroskopischer Objekte entdeckt, war aber beunruhigt, weil er nicht herausfinden konnte, warum sich die Objekte auf diese Weise bewegten. Im Gegenteil, Einstein erlebte das glückliche Leben der Relativität zwischen den Gesetzen der Grundteilchen und der Bewegung makroskopischer Objekte !
Abschnitt 1 TObject Atom
Was ist TObject?
Es ist der grundlegende Kern der Object Pascal-Spracharchitektur und der Ursprung verschiedener VCL-Steuerelemente. Wir können uns TObject als eines der Atome vorstellen, aus denen eine DELPHI-Anwendung besteht. Natürlich bestehen sie aus subtileren Partikeln wie grundlegenden Elementen der Pascal-Syntax.
Es wird gesagt, dass TObject das Atom des DELPHI-Programms ist, da TObject intern vom DELPHI-Compiler unterstützt wird. Alle Objektklassen werden von TObject abgeleitet, auch wenn Sie TObject nicht als Vorgängerklasse angeben. TObject wird in der Systemeinheit definiert, die Teil des Systems ist. Am Anfang der System.pas-Einheit steht dieser Kommentartext:
{Vordefinierte Konstanten, Typen, Prozeduren,}
{ und Funktionen (z. B. True, Integer oder }
{Writeln) haben keine tatsächlichen Deklarationen.}
{ Stattdessen sind sie in den Compiler integriert }
{ und werden so behandelt, als wären sie deklariert }
{ am Anfang der Systemeinheit.}
Dies bedeutet, dass diese Einheit vordefinierte Konstanten, Typen, Prozeduren und Funktionen enthält (z. B. True, Integer oder Writeln). Sie werden nicht tatsächlich deklariert, sondern vom Compiler integriert und zu Beginn der Kompilierung verwendet eine festgelegte Definition sein. Sie können Ihrer Projektdatei andere Quellprogrammdateien wie Classes.pas oder Windows.pas hinzufügen, um den Quellcode zu kompilieren und zu debuggen. Sie können jedoch auf keinen Fall die System.pas-Quellprogrammdatei zur Kompilierung zu Ihrer Projektdatei hinzufügen! DELPHI meldet Kompilierungsfehler für doppelte Definitionen von System!
Daher ist TObject eine intern vom Compiler bereitgestellte Definition. Für diejenigen von uns, die DELPHI zum Entwickeln von Programmen verwenden, ist TObject eine atomare Sache.
Die Definition von TObject in der Systemeinheit lautet wie folgt:
TObject = Klasse
Konstruktor Erstellen;
Verfahren kostenlos;
Klassenfunktion InitInstance(Instance: Pointer): TObject;
Prozedur CleanupInstance;
Funktion ClassType: TClass;
Klassenfunktion ClassName: ShortString;
Klassenfunktion ClassNameIs(const Name: string): Boolean;
Klassenfunktion ClassParent: TClass;
Klassenfunktion ClassInfo: Pointer;
Klassenfunktion InstanceSize: Longint;
Klassenfunktion InheritsFrom(AClass: TClass): Boolean;
Klassenfunktion MethodAddress(const Name: ShortString): Pointer;
Klassenfunktion MethodName(Adresse: Zeiger): ShortString;
Funktion FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
Klassenfunktion GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
Klassenfunktion GetInterfaceTable: PInterfaceTable;
Funktion SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult virtual;
Verfahren AfterConstruction; virtuell;
Prozedur BeforeDestruction; virtuell;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message);
Klassenfunktion NewInstance: TObject virtual;
Prozedur FreeInstance virtuell;
Destruktor Zerstören; virtuell;
Ende;
Als nächstes werden wir nach und nach an die Tür der TObject-Atome klopfen, um zu sehen, welche Struktur sich darin befindet.
Wir wissen, dass TObject die Grundklasse aller Objekte ist. Was genau ist also ein Objekt?
Jedes Objekt in DELPHI ist ein Zeiger, der den vom Objekt im Speicher belegten Platz angibt! Obwohl das Objekt ein Zeiger ist, müssen wir beim Verweisen auf die Mitglieder des Objekts nicht den Code MyObject^.GetName schreiben, sondern können nur MyObject.GetName schreiben. Dies ist eine erweiterte Syntax der Object Pascal-Sprache vom Compiler unterstützt. Freunde, die C++ Builder verwenden, sind sich der Beziehung zwischen Objekten und Zeigern sehr bewusst, da Objekte in C++ Builder als Zeiger definiert werden müssen. Der Ort, auf den der Objektzeiger zeigt, ist der Objektraum, in dem das Objekt Daten speichert. Lassen Sie uns die Datenstruktur des Speicherraums analysieren, auf den der Objektzeiger zeigt.
Die ersten 4 Bytes des Objektraums verweisen auf die Adresstabelle der virtuellen Methode (VMT?C Virtual Method Table) der Objektklasse. Der nächste Raum ist der Raum zum Speichern der Mitgliedsdaten des Objekts selbst und wird in der Gesamtreihenfolge von den Datenmitgliedern der primitivsten Vorgängerklasse des Objekts bis zu den Datenmitgliedern der Objektklasse und in der Reihenfolge gespeichert, in der die Datenelemente werden in jeder Klassenebene definiert.
Die virtuelle Methodentabelle (VMT) einer Klasse enthält die Prozeduradressen der virtuellen Methoden aller Klassen, die von der ursprünglichen Vorgängerklasse der Klasse abgeleitet sind. Die virtuelle Methode einer Klasse ist eine Methode, die mit dem reservierten Wort „virtual“ deklariert wird. Die virtuelle Methode ist der grundlegende Mechanismus zur Erzielung von Objektpolymorphismus. Obwohl dynamische Methoden, die mit dem reservierten Wort „Dynamic“ deklariert werden, auch Objektpolymorphismus erreichen können, werden solche Methoden nicht in der virtuellen Methodenadresstabelle (VMT) gespeichert. Dies ist nur eine weitere von Object Pascal bereitgestellte Methode, mit der Klassenspeicherplatz eingespart werden kann. aber auf Kosten der Anrufgeschwindigkeit.
Selbst wenn wir selbst keine virtuelle Methode der Klasse definieren, verfügt das Objekt der Klasse immer noch über einen Zeiger auf die Adresstabelle der virtuellen Methode, aber die Länge des Adresseintrags ist Null. Wo werden jedoch die in TObject definierten virtuellen Methoden wie Destroy, FreeInstance usw. gespeichert? Es stellt sich heraus, dass ihre Methodenadressen relativ zum VMT-Zeiger in einem in negativer Richtung versetzten Raum gespeichert sind. Tatsächlich ist der um 76 Byte in der negativen Richtung der VMT-Tabelle versetzte Datenraum die Systemdatenstruktur der Objektklasse. Diese Datenstrukturen sind Compiler-bezogen und können in zukünftigen DELPHI-Versionen geändert werden.
Daher kann man davon ausgehen, dass VMT eine Datenstruktur ist, die vom negativen Offset-Adressraum ausgeht. Der negative Offset-Datenbereich ist der Systemdatenbereich von VMT und die positiven Offset-Daten von VMT sind der Benutzerdatenbereich (benutzerdefinierte virtuelle Methode). Adresstabelle). Die in TObject definierten Funktionen und Prozeduren im Zusammenhang mit Klasseninformationen oder Objektlaufzeitinformationen beziehen sich im Allgemeinen auf die Systemdaten von VMT.
VMT-Daten stellen eine Klasse dar. Tatsächlich ist VMT eine Klasse! In Object Pascal verwenden wir Bezeichner wie TObject, TComponent usw. zur Darstellung von Klassen, die intern in DELPHI als ihre jeweiligen VMT-Daten implementiert werden. Der mit der Klasse des reservierten Wortes definierte Klassentyp ist tatsächlich ein Zeiger auf die relevanten VMT-Daten.
Für unsere Anwendung sind VMT-Daten statische Daten. Nachdem der Compiler unsere Anwendung kompiliert hat, wurden diese Dateninformationen ermittelt und initialisiert. Die von uns geschriebenen Programmanweisungen können auf VMT-bezogene Informationen zugreifen, Informationen wie die Größe des Objekts, den Klassennamen oder Laufzeitattributdaten abrufen oder virtuelle Methoden aufrufen oder den Namen und die Adresse der Methode usw. lesen.
Wenn ein Objekt generiert wird, weist das System einen Speicherplatz für das Objekt zu und ordnet das Objekt der relevanten Klasse zu. Daher werden die ersten 4 Bytes im für das Objekt zugewiesenen Datenraum zu Zeigern auf Klassen-VMT-Daten.
Werfen wir einen Blick darauf, wie Objekte entstehen und sterben. Wenn ich meinem dreijährigen Sohn dabei zusehe, wie er im Gras herumspringt, kann ich den Sinn und die Größe des Lebens wirklich verstehen, gerade weil ich den Geburtsprozess des Lebens miterlebt habe. Nur wer den Tod erlebt hat, wird das Leben mehr verstehen und wertschätzen. Lassen Sie uns also den Prozess der Entstehung und Zerstörung von Objekten verstehen!
Wir alle wissen, dass das einfachste Objekt mit der folgenden Anweisung konstruiert werden kann:
AnObject := TObject.Create;
Der Compiler implementiert seine Kompilierung wie folgt:
Rufen Sie basierend auf dem VMT, das TObject entspricht, den Create-Konstruktor von TObject auf. Der Create-Konstruktor ruft den ClassCreate-Prozess des Systems auf, und der ClassCreate-Prozess des Systems ruft die virtuelle Methode NewInstance über die darin gespeicherte Klasse VMT auf. Der Zweck des Aufrufs der NewInstance-Methode besteht darin, den Instanzraum des Objekts festzulegen. Da wir diese Methode nicht überladen haben, handelt es sich um die NewInstance der TObject-Klasse. Die NewInstance-Methode der TObjec-Klasse ruft die GetMem-Prozedur auf, um dem Objekt Speicher basierend auf der vom Compiler in der VMT-Tabelle initialisierten Objektinstanzgröße (InstanceSize) zuzuweisen, und ruft dann die InitInstance-Methode auf, um den zugewiesenen Speicherplatz zu initialisieren. Die InitInstance-Methode initialisiert zunächst die ersten 4 Bytes des Objektraums als Zeiger auf den VMT, der der Objektklasse entspricht, und löscht dann den verbleibenden Raum. Nach dem Einrichten der Objektinstanz wird auch eine virtuelle Methode AfterConstruction aufgerufen. Speichern Sie abschließend den Adresszeiger der Objektinstanzdaten in der AnObject-Variablen, und auf diese Weise wird das AnObject-Objekt geboren.
Ebenso kann ein Objekt mit der folgenden Anweisung zerstört werden:
AnObject.Destroy;
Der Destruktor von TObject, Destroy, wird als virtuelle Methode deklariert, die auch eine der inhärenten virtuellen Methoden des Systems ist. Die Destory-Methode ruft zunächst die virtuelle Methode BeforeDestruction und dann den ClassDestroy-Prozess des Systems auf. Der ClassDestory-Prozess ruft die virtuelle FreeInstance-Methode über die Klasse VMT auf, und die FreeInstance-Methode ruft den FreeMem-Prozess auf, um den Speicherplatz des Objekts freizugeben. Auf diese Weise verschwindet ein Objekt aus dem System.
Der Zerstörungsprozess von Objekten ist einfacher als der Konstruktionsprozess von Objekten, genauso wie die Geburt des Lebens ein langer Entstehungsprozess ist, der Tod jedoch relativ kurzlebig ist. Dies scheint eine unvermeidliche Regel zu sein.
Während des Konstruktions- und Zerstörungsprozesses des Objekts werden zwei virtuelle Funktionen, NewInstance und FreeInstance, aufgerufen, um den Speicherplatz der Objektinstanz zu erstellen und freizugeben. Der Grund, warum diese beiden Funktionen als virtuelle Funktionen deklariert werden, besteht darin, den Benutzern Raum für Erweiterungen zu geben, wenn sie spezielle Objektklassen schreiben, die erfordern, dass Benutzer ihren eigenen Speicher verwalten (z. B. in einigen speziellen industriellen Steuerungsprogrammen).
Durch die Deklaration von AfterConstruction und BeforeDestruction als virtuelle Funktionen soll der abgeleiteten Klasse in Zukunft auch die Möglichkeit gegeben werden, dem neu geborenen Objekt nach der Generierung des Objekts den ersten Atemzug frischer Luft zu ermöglichen und dem Objekt zu ermöglichen, die Nachwirkungen abzuschließen, bevor das Objekt stirbt . Das ist alles etwas, das Sinn macht. Tatsächlich werden das OnCreate-Ereignis und das OnDestroy-Ereignis des TForm-Objekts bzw. des TDataModule-Objekts in den beiden virtuellen Funktionsprozessen der TForm- und TDataModule-Überladung ausgelöst.
Darüber hinaus bietet TObjec auch eine Free-Methode, bei der es sich nicht um eine virtuelle Methode handelt. Sie dient speziell dazu, das Objekt sicher freizugeben, wenn unklar ist, ob das Objekt leer ist (Null). Wenn Sie nicht herausfinden können, ob das Objekt leer ist, liegt tatsächlich ein Problem der unklaren Programmlogik vor. Allerdings ist niemand perfekt und kann Fehler machen. Es ist auch eine gute Sache, Free zu verwenden, um versehentliche Fehler zu vermeiden. Das Schreiben korrekter Programme kann sich jedoch nicht ausschließlich auf solche Lösungen verlassen. Das erste Ziel der Programmierung sollte darin bestehen, die logische Korrektheit des Programms sicherzustellen!
Interessierte Freunde können den Originalcode der Systemeinheit lesen, wobei ein großer Teil des Codes in Assemblersprache geschrieben ist. Aufmerksame Freunde können feststellen, dass der Konstruktor „Create“ und der Destruktor „Destory“ keinen Code geschrieben haben. Tatsächlich kann der Assemblercode von „Create“ und „Destory“ im Debug-Status deutlich wiedergegeben werden. Denn die Meister, die DELPHI geschaffen haben, wollten den Benutzern nicht zu viele komplizierte Dinge bieten. Sie wollten, dass Benutzer Anwendungen schreiben, die auf einfachen Konzepten basieren, und die komplexe Arbeit innerhalb des Systems verbergen, damit sie sie ausführen können. Daher werden beim Veröffentlichen der System.pas-Einheit die Codes dieser beiden Funktionen speziell entfernt, um den Benutzern den Eindruck zu vermitteln, dass TObject die Quelle aller Dinge ist und vom Benutzer abgeleitete Klassen vollständig im Nichts beginnen. Dies ist an sich nicht falsch. Obwohl das Lesen dieser wichtigsten Codes von DELPHI ein geringes Maß an Assemblerkenntnissen erfordert, kann uns das Lesen solcher Codes ein tieferes Verständnis für den Ursprung und die Entwicklung der DELPHI-Welt vermitteln. Selbst wenn Sie nicht viel verstehen, wird es uns beim Schreiben von DELPHI-Programmen eine große Hilfe sein, zumindest einige grundlegende Dinge zu verstehen.
Abschnitt 2 TClass Atom
In der System.pas-Einheit ist TClass wie folgt definiert:
TClass = Klasse von TObject;
Das bedeutet, dass TClass die Klasse von TObject ist. Da TObject selbst eine Klasse ist, ist TClass die sogenannte Klasse von Klassen.
Konzeptionell ist TClass eine Art Klasse, also eine Klasse. Wir wissen jedoch, dass eine Klasse von DELPHI einen Teil der VMT-Daten darstellt. Daher kann die Klasse als der für das VMT-Datenelement definierte Typ betrachtet werden. Tatsächlich handelt es sich um einen Zeigertyp, der auf die VMT-Daten zeigt!
In der vorherigen traditionellen C++-Sprache konnte der Typ einer Klasse nicht definiert werden. Sobald das Objekt kompiliert ist, ist es fixiert, die Strukturinformationen der Klasse wurden in absoluten Maschinencode umgewandelt und die vollständigen Klasseninformationen sind nicht im Speicher vorhanden. Einige objektorientierte Sprachen höherer Ebenen können den dynamischen Zugriff und Aufruf von Klasseninformationen unterstützen, erfordern jedoch häufig einen komplexen internen Interpretationsmechanismus und mehr Systemressourcen. Die Object Pascal-Sprache von DELPHI übernimmt einige der hervorragenden Funktionen objektorientierter Hochsprachensprachen und behält gleichzeitig den traditionellen Vorteil der direkten Kompilierung von Programmen in Maschinencode bei, wodurch die Probleme erweiterter Funktionen und Programmeffizienz perfekt gelöst werden.
Gerade weil DELPHI vollständige Klasseninformationen in der Anwendung behält, kann es erweiterte objektorientierte Funktionen bereitstellen, wie z. B. das Konvertieren und Identifizieren von Klassen zur Laufzeit, wobei die VMT-Daten der Klasse eine Schlüsselrolle spielen. Interessierte Freunde können die beiden Assemblerprozesse von AsClass und IsClass in der Systemeinheit lesen. Sie sind die Implementierungscodes der as- und is-Operatoren, um ihr Verständnis von Klassen und VMT-Daten zu vertiefen.
...
Der folgende Inhalt umfasst auch das Verständnis fiktiver Konstruktoren, den Implementierungsmechanismus der Schnittstelle und den Implementierungsmechanismus der Ausnahmebehandlung usw. Die Grundprinzipien von DLPHI. Ich hoffe, dass ich es nach dem 1. Mai fertigstellen und es allen zur Verfügung stellen kann.